diff --git a/Cargo.lock b/Cargo.lock index 12706516..971ac785 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,12 +40,33 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bit-set" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "cfg-if" version = "1.0.0" @@ -91,6 +112,15 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + [[package]] name = "flat-rs" version = "0.0.2" @@ -99,6 +129,23 @@ dependencies = [ "thiserror", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "hashbrown" version = "0.11.2" @@ -136,6 +183,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -157,6 +213,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + [[package]] name = "os_str_bytes" version = "6.0.1" @@ -190,6 +255,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9b0efd3ba03c3a409d44d60425f279ec442bcf0b9e63ff4e410da31c8b0f69f" +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + [[package]] name = "pretty" version = "0.11.3" @@ -235,6 +306,38 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d9cc07f18492d879586c92b485def06bc850da3118075cd45d50e9c95b0e5" +dependencies = [ + "bit-set", + "bitflags", + "byteorder", + "lazy_static", + "num-traits", + "quick-error 2.0.1", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quote" version = "1.0.18" @@ -244,6 +347,81 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex-syntax" +version = "0.6.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error 1.2.3", + "tempfile", + "wait-timeout", +] + [[package]] name = "strsim" version = "0.10.0" @@ -261,6 +439,20 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "termcolor" version = "1.1.3" @@ -322,6 +514,7 @@ dependencies = [ "hex", "peg", "pretty", + "proptest", "thiserror", ] @@ -331,6 +524,21 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "winapi" version = "0.3.9" diff --git a/crates/uplc/Cargo.toml b/crates/uplc/Cargo.toml index c4eb8674..64b1d690 100644 --- a/crates/uplc/Cargo.toml +++ b/crates/uplc/Cargo.toml @@ -19,5 +19,9 @@ peg = "0.8.0" pretty = "0.11.3" thiserror = "1.0.31" +[dev-dependencies] +hex = "0.4.3" +proptest = "1.0.0" + [features] unstable = [] diff --git a/crates/uplc/examples/identity_builder_ext.rs b/crates/uplc/examples/identity_builder_ext.rs new file mode 100644 index 00000000..0a7fc4fd --- /dev/null +++ b/crates/uplc/examples/identity_builder_ext.rs @@ -0,0 +1,15 @@ +use uplc::program_builder::{Builder, WithLambda, WithTerm, WithVar}; + +trait WithIdentity: WithTerm + WithLambda + WithVar { + fn with_identity(self, name_str: &str) -> Self::Next { + self.with_lambda(name_str).with_var(name_str) + } +} + +impl WithIdentity for T {} + +fn main() { + let my_var = "some_var"; + let program = Builder::start(1, 2, 3).with_identity(my_var).build_named(); + println!("{:#?}", program); +} diff --git a/crates/uplc/proptest-regressions/program_builder/constant.txt b/crates/uplc/proptest-regressions/program_builder/constant.txt new file mode 100644 index 00000000..574947f8 --- /dev/null +++ b/crates/uplc/proptest-regressions/program_builder/constant.txt @@ -0,0 +1,8 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc a6cf04d97e92892f9c9342f5df205d81b7d686231ba69c9f36ba1166bd21d9a7 # shrinks to int = 0 +cc ccfa96fb2d6133adc65044ad94e69d282fa104c68128782ecfa2badb8c79b12c # shrinks to some_string = "\"" diff --git a/crates/uplc/proptest-regressions/program_builder/tests.txt b/crates/uplc/proptest-regressions/program_builder/tests.txt new file mode 100644 index 00000000..73181782 --- /dev/null +++ b/crates/uplc/proptest-regressions/program_builder/tests.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc db95a6ed96ee7cee987ba4b71bc6ffa9cf5abe295fa57a0eade27ed47335c1a3 # shrinks to maj = 0, min = 0, patch = 0 diff --git a/crates/uplc/src/lib.rs b/crates/uplc/src/lib.rs index e13a58ad..6d22da98 100644 --- a/crates/uplc/src/lib.rs +++ b/crates/uplc/src/lib.rs @@ -4,6 +4,4 @@ mod debruijn; mod flat; pub mod parser; mod pretty; - -#[cfg(any(feature = "unstable", test))] pub mod program_builder; diff --git a/crates/uplc/src/program_builder.rs b/crates/uplc/src/program_builder.rs index 688ed3c0..44ba1932 100644 --- a/crates/uplc/src/program_builder.rs +++ b/crates/uplc/src/program_builder.rs @@ -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, } -pub struct NeedsTerm { - version: (usize, usize, usize), - // TODO: Hide these two behind interface - next_unique: Cell, - names: RefCell>, +struct Context { + next_unique: isize, + names: HashMap, } -pub struct LambdaBuilder { - 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, } pub trait WithTerm @@ -32,22 +75,9 @@ where fn next(self, term: Term) -> 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 { - 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) -> 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 WithTerm for LambdaBuilder { - type Next = T::Next; - - fn next(self, term: Term) -> 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()), } } diff --git a/crates/uplc/src/program_builder/apply.rs b/crates/uplc/src/program_builder/apply.rs new file mode 100644 index 00000000..b6808523 --- /dev/null +++ b/crates/uplc/src/program_builder/apply.rs @@ -0,0 +1,95 @@ +use crate::ast::{Name, Term}; +use crate::program_builder::WithTerm; + +pub struct ApplyBuilderFunction { + outer: T, +} + +pub struct ApplyBuilderArgument { + outer: T, + function: Term, +} + +impl WithTerm for ApplyBuilderFunction { + type Next = ApplyBuilderArgument; + + fn next(self, term: Term) -> Self::Next { + ApplyBuilderArgument { + outer: self.outer, + function: term, + } + } + + fn get_name(&self, name_str: &str) -> Name { + self.outer.get_name(name_str) + } +} + +impl WithTerm for ApplyBuilderArgument { + type Next = T::Next; + + fn next(self, term: Term) -> 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 { + 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 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); + } +} diff --git a/crates/uplc/src/program_builder/builtin.rs b/crates/uplc/src/program_builder/builtin.rs new file mode 100644 index 00000000..300a8647 --- /dev/null +++ b/crates/uplc/src/program_builder/builtin.rs @@ -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 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); + } +} diff --git a/crates/uplc/src/program_builder/constant.rs b/crates/uplc/src/program_builder/constant.rs new file mode 100644 index 00000000..bde7b749 --- /dev/null +++ b/crates/uplc/src/program_builder/constant.rs @@ -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) -> 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 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 + ) { + 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); + } +} diff --git a/crates/uplc/src/program_builder/delay.rs b/crates/uplc/src/program_builder/delay.rs new file mode 100644 index 00000000..eb2850bd --- /dev/null +++ b/crates/uplc/src/program_builder/delay.rs @@ -0,0 +1,65 @@ +use crate::ast::{Name, Term}; +use crate::program_builder::WithTerm; + +pub struct DelayBuilder { + outer: T, +} + +impl WithTerm for DelayBuilder { + type Next = T::Next; + + fn next(self, term: Term) -> 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 { + 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 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); + } +} diff --git a/crates/uplc/src/program_builder/error.rs b/crates/uplc/src/program_builder/error.rs new file mode 100644 index 00000000..b5bed3c5 --- /dev/null +++ b/crates/uplc/src/program_builder/error.rs @@ -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 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); + } +} diff --git a/crates/uplc/src/program_builder/force.rs b/crates/uplc/src/program_builder/force.rs new file mode 100644 index 00000000..3c2ff90f --- /dev/null +++ b/crates/uplc/src/program_builder/force.rs @@ -0,0 +1,51 @@ +use crate::ast::{Name, Term}; +use crate::program_builder::WithTerm; + +pub struct ForceBuilder { + outer: T, +} + +impl WithTerm for ForceBuilder { + type Next = T::Next; + + fn next(self, term: Term) -> 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 { + 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 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); + } +} diff --git a/crates/uplc/src/program_builder/lambda.rs b/crates/uplc/src/program_builder/lambda.rs new file mode 100644 index 00000000..7518298c --- /dev/null +++ b/crates/uplc/src/program_builder/lambda.rs @@ -0,0 +1,74 @@ +use crate::ast::{Name, Term}; +use crate::program_builder::WithTerm; + +pub struct LambdaBuilder { + outer: T, + parameter_name: Name, +} + +impl WithTerm for LambdaBuilder { + type Next = T::Next; + + fn next(self, term: Term) -> 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 { + 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 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); + } +} diff --git a/crates/uplc/src/program_builder/tests.rs b/crates/uplc/src/program_builder/tests.rs index 22e2acfb..f85fee1e 100644 --- a/crates/uplc/src/program_builder/tests.rs +++ b/crates/uplc/src/program_builder/tests.rs @@ -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); + } } diff --git a/crates/uplc/src/program_builder/var.rs b/crates/uplc/src/program_builder/var.rs new file mode 100644 index 00000000..44e88930 --- /dev/null +++ b/crates/uplc/src/program_builder/var.rs @@ -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 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); + } +}