Expand builder (#20)

* Add bool method

* Add proptest

* Add some more consts and stuff

* Refactor Lambda stuff out

* REfactor

* Convert bytestring test to prop test

* Add string constant

* Add char stuff, despite it not being ready

* Add unit

* Add var

* Add delay

* Add apply

* Add force

* Add error

* Add builtin

* Add example, remove feature

* Rename some stuff

Co-authored-by: Turner <mitch@tpfs.io>
This commit is contained in:
MitchTurner
2022-07-01 20:50:58 -07:00
committed by GitHub
parent 7f70ae0f74
commit ada7b00b49
16 changed files with 884 additions and 121 deletions

View File

@@ -4,6 +4,4 @@ mod debruijn;
mod flat;
pub mod parser;
mod pretty;
#[cfg(any(feature = "unstable", test))]
pub mod program_builder;

View File

@@ -1,27 +1,70 @@
#![cfg_attr(test, allow(non_snake_case))]
use crate::ast::{Constant, Name, Program, Term, Unique};
use std::cell::{Cell, RefCell};
use crate::ast::{Name, Program, Term, Unique};
use std::cell::RefCell;
use std::collections::HashMap;
#[cfg(test)]
mod tests;
mod apply;
mod builtin;
mod constant;
mod delay;
mod error;
mod force;
mod lambda;
mod var;
pub use apply::*;
pub use builtin::*;
pub use constant::*;
pub use delay::*;
pub use error::*;
pub use force::*;
pub use lambda::*;
pub use var::*;
pub struct Builder {
version: (usize, usize, usize),
term: Term<Name>,
}
pub struct NeedsTerm {
version: (usize, usize, usize),
// TODO: Hide these two behind interface
next_unique: Cell<isize>,
names: RefCell<HashMap<String, Unique>>,
struct Context {
next_unique: isize,
names: HashMap<String, Unique>,
}
pub struct LambdaBuilder<T> {
outer: T,
parameter_name: Name,
impl Context {
pub fn new() -> Context {
Context {
next_unique: 0,
names: HashMap::new(),
}
}
pub fn get_name(&mut self, name_str: &str) -> Name {
if let Some(unique) = self.names.get(name_str) {
Name {
text: name_str.to_string(),
unique: *unique,
}
} else {
let next_unique = self.next_unique;
self.next_unique = next_unique + 1;
let unique = Unique::new(next_unique);
self.names.insert(name_str.to_string(), unique);
Name {
text: name_str.to_string(),
unique,
}
}
}
}
pub struct Core {
version: (usize, usize, usize),
ctx: RefCell<Context>,
}
pub trait WithTerm
@@ -32,22 +75,9 @@ where
fn next(self, term: Term<Name>) -> Self::Next;
fn get_name(&self, name_str: &str) -> Name;
fn with_constant_int(self, int: isize) -> Self::Next {
let term = Term::Constant(Constant::Integer(int));
self.next(term)
}
fn with_lambda(self, name_str: &str) -> LambdaBuilder<Self> {
let parameter_name = self.get_name(name_str);
LambdaBuilder {
outer: self,
parameter_name,
}
}
}
impl WithTerm for NeedsTerm {
impl WithTerm for Core {
type Next = Builder;
fn next(self, term: Term<Name>) -> Self::Next {
Builder {
@@ -57,48 +87,17 @@ impl WithTerm for NeedsTerm {
}
fn get_name(&self, name_str: &str) -> Name {
let mut names = self.names.borrow_mut();
if let Some(unique) = names.get(name_str) {
Name {
text: name_str.to_string(),
unique: *unique,
}
} else {
let next_unique = self.next_unique.get();
self.next_unique.set(next_unique + 1);
let unique = Unique::new(next_unique);
names.insert(name_str.to_string(), unique);
Name {
text: name_str.to_string(),
unique,
}
}
}
}
impl<T: WithTerm> WithTerm for LambdaBuilder<T> {
type Next = T::Next;
fn next(self, term: Term<Name>) -> Self::Next {
let term = Term::Lambda {
parameter_name: self.parameter_name,
body: Box::new(term),
};
self.outer.next(term)
}
fn get_name(&self, name_str: &str) -> Name {
self.outer.get_name(name_str)
let mut ctx = self.ctx.borrow_mut();
ctx.get_name(name_str)
}
}
impl Builder {
#[allow(clippy::new_ret_no_self)]
pub fn new(maj: usize, min: usize, patch: usize) -> NeedsTerm {
NeedsTerm {
/// Max: `9223372036854775807`
pub fn start(maj: usize, min: usize, patch: usize) -> Core {
Core {
version: (maj, min, patch),
next_unique: Cell::new(0),
names: RefCell::new(HashMap::new()),
ctx: RefCell::new(Context::new()),
}
}

View File

@@ -0,0 +1,95 @@
use crate::ast::{Name, Term};
use crate::program_builder::WithTerm;
pub struct ApplyBuilderFunction<T> {
outer: T,
}
pub struct ApplyBuilderArgument<T> {
outer: T,
function: Term<Name>,
}
impl<T: WithTerm> WithTerm for ApplyBuilderFunction<T> {
type Next = ApplyBuilderArgument<T>;
fn next(self, term: Term<Name>) -> Self::Next {
ApplyBuilderArgument {
outer: self.outer,
function: term,
}
}
fn get_name(&self, name_str: &str) -> Name {
self.outer.get_name(name_str)
}
}
impl<T: WithTerm> WithTerm for ApplyBuilderArgument<T> {
type Next = T::Next;
fn next(self, term: Term<Name>) -> Self::Next {
let term = Term::Apply {
function: Box::new(self.function),
argument: Box::new(term),
};
self.outer.next(term)
}
fn get_name(&self, name_str: &str) -> Name {
self.outer.get_name(name_str)
}
}
pub trait WithApply: WithTerm {
fn with_apply(self) -> ApplyBuilderFunction<Self> {
ApplyBuilderFunction { outer: self }
}
}
// This is a naive blanket impl. If needed, we can control which states of the builder can
// call this by implementing manually.
impl<T: WithTerm> WithApply for T {}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser;
use crate::program_builder::{Builder, WithConstant, WithLambda, WithVar};
#[test]
fn build_named__with_apply() {
let my_var = "i_0";
let code = r"(program
1.2.3
[(lam i_0 i_0) (con integer 1)]
)";
let expected = parser::program(code).unwrap();
let actual = Builder::start(1, 2, 3)
.with_apply()
.with_lambda(my_var)
.with_var(my_var)
.with_int(1)
.build_named();
assert_eq!(expected, actual);
}
#[test]
fn build_named__with_apply__with_lambda_as_arg() {
let my_var = "i_0";
let their_var = "i_1";
let code = r"(program
1.2.3
[(lam i_0 i_0) (lam i_1 (con integer 1))]
)";
let expected = parser::program(code).unwrap();
let actual = Builder::start(1, 2, 3)
.with_apply()
.with_lambda(my_var)
.with_var(my_var)
.with_lambda(their_var)
.with_int(1)
.build_named();
assert_eq!(expected, actual);
}
}

View File

@@ -0,0 +1,33 @@
use crate::ast::Term;
use crate::builtins::DefaultFunction;
use crate::program_builder::WithTerm;
pub trait WithBuiltin: WithTerm {
// TODO: Add all the builtin variants explicitly
fn with_builtin(self, builtin: DefaultFunction) -> Self::Next {
let term = Term::Builtin(builtin);
self.next(term)
}
}
impl<T: WithTerm> WithBuiltin for T {}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser;
use crate::program_builder::Builder;
#[test]
fn build_named() {
let code = r"(program
11.22.33
(builtin addInteger)
)";
let expected = parser::program(code).unwrap();
let actual = Builder::start(11, 22, 33)
.with_builtin(DefaultFunction::AddInteger)
.build_named();
assert_eq!(expected, actual);
}
}

View File

@@ -0,0 +1,173 @@
use crate::ast::{Constant, Term};
use crate::program_builder::WithTerm;
pub trait WithConstant: WithTerm {
fn with_int(self, int: isize) -> Self::Next {
let term = Term::Constant(Constant::Integer(int));
self.next(term)
}
fn with_byte_string(self, bytes: Vec<u8>) -> Self::Next {
let term = Term::Constant(Constant::ByteString(bytes));
self.next(term)
}
fn with_string(self, string: String) -> Self::Next {
let term = Term::Constant(Constant::String(string));
self.next(term)
}
// TODO: After https://github.com/txpipe/aiken/issues/18 is completed
// fn with_char(self, a: char) -> Self::Next {
// let term = Term::Constant(Constant::Char(a));
// self.next(term)
// }
fn with_unit(self) -> Self::Next {
let term = Term::Constant(Constant::Unit);
self.next(term)
}
fn with_bool(self, bool: bool) -> Self::Next {
let term = Term::Constant(Constant::Bool(bool));
self.next(term)
}
}
// This is a naive blanket impl. If needed, we can control which states of the builder can
// call this by implementing manually.
impl<T: WithTerm> WithConstant for T {}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser;
use crate::program_builder::Builder;
use proptest::prelude::*;
proptest! {
#[test]
fn build_named__with_const(
int: isize
) {
let code = format!(r"(program
11.22.33
(con integer {})
)", int);
let expected = parser::program(&code).unwrap();
let actual = Builder::start(11, 22, 33).with_int(int).build_named();
assert_eq!(expected, actual);
}
}
proptest! {
#[test]
fn build_named__with_bytestring(
bytes: Vec<u8>
) {
let bstring = hex::encode(&bytes);
let code = format!(r"(program
11.22.33
(con bytestring #{})
)", bstring);
let expected = parser::program(&code).unwrap();
let actual = Builder::start(11, 22, 33)
.with_byte_string(bytes)
.build_named();
assert_eq!(expected, actual);
}
}
prop_compose! {
fn safe_string()(
some_string: String
) -> String {
some_string.chars().filter(|a| *a != '\"').collect()
}
}
proptest! {
#[test]
fn build_named__with_string(
some_string in safe_string()
) {
let code = format!(
r#"(program
11.22.33
(con string "{}")
)"#,
&some_string
);
let expected = parser::program(&code).unwrap();
let actual = Builder::start(11, 22, 33)
.with_string(some_string)
.build_named();
assert_eq!(expected, actual);
}
}
// prop_compose! {
// fn some_char()(
// some_char: char
// ) -> char {
// some_char
// }
// }
// TODO: After https://github.com/txpipe/aiken/issues/18 is completed
// proptest! {
// #[test]
// fn build_named__with_char(
// some_char in some_char().prop_filter("Cannot be a double quote", |a| *a != '\"')
// ) {
// let char_as_string = &some_char.to_string();
// let code = format!(
// r#"(program
// 11.22.33
// (con char '{}')
// )"#,
// char_as_string
// );
//
// println!("{:#?}", &code);
// let expected = parser::program(&code).unwrap();
// let actual = Builder::start(11, 22, 33)
// .with_char(some_char)
// .build_named();
// assert_eq!(expected, actual);
// }
// }
#[test]
fn build_named__with_unit() {
let code = r"(program
11.22.33
(con unit ())
)";
let expected = parser::program(code).unwrap();
let actual = Builder::start(11, 22, 33).with_unit().build_named();
assert_eq!(expected, actual);
}
#[test]
fn build_named__with_true() {
let code = r"(program
11.22.33
(con bool True)
)";
let expected = parser::program(code).unwrap();
let actual = Builder::start(11, 22, 33).with_bool(true).build_named();
assert_eq!(expected, actual);
}
#[test]
fn build_named__with_false() {
let code = r"(program
11.22.33
(con bool False)
)";
let expected = parser::program(code).unwrap();
let actual = Builder::start(11, 22, 33).with_bool(false).build_named();
assert_eq!(expected, actual);
}
}

View File

@@ -0,0 +1,65 @@
use crate::ast::{Name, Term};
use crate::program_builder::WithTerm;
pub struct DelayBuilder<T> {
outer: T,
}
impl<T: WithTerm> WithTerm for DelayBuilder<T> {
type Next = T::Next;
fn next(self, term: Term<Name>) -> Self::Next {
let term = Term::Delay(Box::new(term));
self.outer.next(term)
}
fn get_name(&self, name_str: &str) -> Name {
self.outer.get_name(name_str)
}
}
pub trait WithDelay: WithTerm {
fn with_delay(self) -> DelayBuilder<Self> {
DelayBuilder { outer: self }
}
}
// This is a naive blanket impl. If needed, we can control which states of the builder can
// call this by implementing manually.
impl<T: WithTerm> WithDelay for T {}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser;
use crate::program_builder::{Builder, WithConstant, WithLambda};
#[test]
fn build_named__with_delay() {
let code = r"(program
1.2.3
(delay (con integer 1))
)";
let expected = parser::program(code).unwrap();
let actual = Builder::start(1, 2, 3)
.with_delay()
.with_int(1)
.build_named();
assert_eq!(expected, actual);
}
#[test]
fn build_named__with_delay__with_lambda() {
let code = r"(program
1.2.3
(delay (lam i_0 (con integer 1)))
)";
let expected = parser::program(code).unwrap();
let actual = Builder::start(1, 2, 3)
.with_delay()
.with_lambda("i_0")
.with_int(1)
.build_named();
assert_eq!(expected, actual);
}
}

View File

@@ -0,0 +1,29 @@
use crate::ast::Term;
use crate::program_builder::WithTerm;
pub trait WithError: WithTerm {
fn with_error(self) -> Self::Next {
let term = Term::Error;
self.next(term)
}
}
impl<T: WithTerm> WithError for T {}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser;
use crate::program_builder::Builder;
#[test]
fn build_named__with_error() {
let code = r"(program
11.22.33
(error)
)";
let expected = parser::program(code).unwrap();
let actual = Builder::start(11, 22, 33).with_error().build_named();
assert_eq!(expected, actual);
}
}

View File

@@ -0,0 +1,51 @@
use crate::ast::{Name, Term};
use crate::program_builder::WithTerm;
pub struct ForceBuilder<T> {
outer: T,
}
impl<T: WithTerm> WithTerm for ForceBuilder<T> {
type Next = T::Next;
fn next(self, term: Term<Name>) -> Self::Next {
let term = Term::Force(Box::new(term));
self.outer.next(term)
}
fn get_name(&self, name_str: &str) -> Name {
self.outer.get_name(name_str)
}
}
pub trait WithForce: WithTerm {
fn with_force(self) -> ForceBuilder<Self> {
ForceBuilder { outer: self }
}
}
// This is a naive blanket impl. If needed, we can control which states of the builder can
// call this by implementing manually.
impl<T: WithTerm> WithForce for T {}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser;
use crate::program_builder::{Builder, WithConstant, WithLambda};
#[test]
fn build_named__with_force() {
let code = r"(program
1.2.3
(force (lam i_0 (con integer 1)))
)";
let expected = parser::program(code).unwrap();
let actual = Builder::start(1, 2, 3)
.with_force()
.with_lambda("i_0")
.with_int(1)
.build_named();
assert_eq!(expected, actual);
}
}

View File

@@ -0,0 +1,74 @@
use crate::ast::{Name, Term};
use crate::program_builder::WithTerm;
pub struct LambdaBuilder<T> {
outer: T,
parameter_name: Name,
}
impl<T: WithTerm> WithTerm for LambdaBuilder<T> {
type Next = T::Next;
fn next(self, term: Term<Name>) -> Self::Next {
let term = Term::Lambda {
parameter_name: self.parameter_name,
body: Box::new(term),
};
self.outer.next(term)
}
fn get_name(&self, name_str: &str) -> Name {
self.outer.get_name(name_str)
}
}
pub trait WithLambda: WithTerm {
fn with_lambda(self, name_str: &str) -> LambdaBuilder<Self> {
let parameter_name = self.get_name(name_str);
LambdaBuilder {
outer: self,
parameter_name,
}
}
}
// This is a naive blanket impl. If needed, we can control which states of the builder can
// call this by implementing manually.
impl<T: WithTerm> WithLambda for T {}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser;
use crate::program_builder::constant::WithConstant;
use crate::program_builder::Builder;
#[test]
fn build_named__with_lam() {
let code = r"(program
1.2.3
(lam i_0 (con integer 1))
)";
let expected = parser::program(code).unwrap();
let actual = Builder::start(1, 2, 3)
.with_lambda("i_0")
.with_int(1)
.build_named();
assert_eq!(expected, actual);
}
#[test]
fn build_named__with_nested_lam() {
let code = r"(program
1.2.3
(lam i_0 (lam i_1 (con integer 1)))
)";
let expected = parser::program(code).unwrap();
let actual = Builder::start(1, 2, 3)
.with_lambda("i_0")
.with_lambda("i_1")
.with_int(1)
.build_named();
assert_eq!(expected, actual);
}
}

View File

@@ -1,64 +1,32 @@
use super::*;
use crate::parser;
use crate::program_builder::constant::WithConstant;
use proptest::prelude::*;
#[test]
fn build_named__with_const() {
let code = r"(program
11.22.33
(con integer 11)
)";
let expected = parser::program(code).unwrap();
let actual = Builder::new(11, 22, 33).with_constant_int(11).build_named();
assert_eq!(expected, actual);
prop_compose! {
fn arb_version()(
maj: isize,
min: isize,
patch: isize,
) -> (usize, usize, usize){
let maj = maj.abs() as usize;
let min = min.abs() as usize;
let patch = patch.abs() as usize;
(maj, min, patch)
}
}
#[test]
fn build_named__with_different_const() {
let code = r"(program
11.22.33
(con integer 22)
)";
let expected = parser::program(code).unwrap();
let actual = Builder::new(11, 22, 33).with_constant_int(22).build_named();
assert_eq!(expected, actual);
}
#[test]
fn build_named__with_const_different_version() {
let code = r"(program
44.55.66
(con integer 11)
)";
let expected = parser::program(code).unwrap();
let actual = Builder::new(44, 55, 66).with_constant_int(11).build_named();
assert_eq!(expected, actual);
}
#[test]
fn build_named__with_lam() {
let code = r"(program
1.2.3
(lam i_0 (con integer 1))
)";
let expected = parser::program(code).unwrap();
let actual = Builder::new(1, 2, 3)
.with_lambda("i_0")
.with_constant_int(1)
.build_named();
assert_eq!(expected, actual);
}
#[test]
fn build_named__with_nested_lam() {
let code = r"(program
1.2.3
(lam i_0 (lam i_1 (con integer 1)))
)";
let expected = parser::program(code).unwrap();
let actual = Builder::new(1, 2, 3)
.with_lambda("i_0")
.with_lambda("i_1")
.with_constant_int(1)
.build_named();
assert_eq!(expected, actual);
proptest! {
#[test]
fn build_named__with_version(
(maj, min, patch) in arb_version(),
) {
let code = format!(r"(program
{}.{}.{}
(con integer 11)
)", maj, min, patch);
let expected = parser::program(&code).unwrap();
let actual = Builder::start(maj, min, patch).with_int(11).build_named();
assert_eq!(expected, actual);
}
}

View File

@@ -0,0 +1,36 @@
use crate::ast::Term;
use crate::program_builder::WithTerm;
pub trait WithVar: WithTerm {
fn with_var(self, name_str: &str) -> Self::Next {
let name = self.get_name(name_str);
let term = Term::Var(name);
self.next(term)
}
}
// This is a naive blanket impl. If needed, we can control which states of the builder can
// call this by implementing manually.
impl<T: WithTerm> WithVar for T {}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser;
use crate::program_builder::{Builder, WithLambda};
#[test]
fn build_named__with_var() {
let var_name = "i_0";
let code = r"(program
1.2.3
(lam i_0 i_0)
)";
let expected = parser::program(code).unwrap();
let actual = Builder::start(1, 2, 3)
.with_lambda(var_name)
.with_var(var_name)
.build_named();
assert_eq!(expected, actual);
}
}