diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index 0569595c..7d45ab8c 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -258,7 +258,7 @@ fn str_to_keyword(word: &str) -> Option { "or" => Some(Token::Or), "validator" => Some(Token::Validator), "via" => Some(Token::Via), - "benchmark" => Some(Token::Benchmark), + "bench" => Some(Token::Benchmark), _ => None, } } diff --git a/crates/aiken-lang/src/ast/well_known.rs b/crates/aiken-lang/src/ast/well_known.rs index 04624571..adf415be 100644 --- a/crates/aiken-lang/src/ast/well_known.rs +++ b/crates/aiken-lang/src/ast/well_known.rs @@ -8,7 +8,8 @@ pub const BOOL: &str = "Bool"; pub const BOOL_CONSTRUCTORS: &[&str] = &["False", "True"]; pub const BYTE_ARRAY: &str = "ByteArray"; pub const DATA: &str = "Data"; -pub const GENERATOR: &str = "Generator"; +pub const FUZZER: &str = "Fuzzer"; +pub const SAMPLER: &str = "Sampler"; pub const G1_ELEMENT: &str = "G1Element"; pub const G2_ELEMENT: &str = "G2Element"; pub const INT: &str = "Int"; @@ -179,7 +180,7 @@ impl Type { }) } - pub fn generator(c: Rc, a: Rc) -> Rc { + pub fn fuzzer(a: Rc) -> Rc { let prng_annotation = Annotation::Constructor { location: Span::empty(), module: None, @@ -188,15 +189,15 @@ impl Type { }; Rc::new(Type::Fn { - args: vec![c, Type::prng()], + args: vec![Type::prng()], ret: Type::option(Type::tuple(vec![Type::prng(), a])), alias: Some( TypeAliasAnnotation { - alias: GENERATOR.to_string(), - parameters: vec!["c".to_string(), "a".to_string()], + alias: FUZZER.to_string(), + parameters: vec!["a".to_string()], annotation: Annotation::Fn { location: Span::empty(), - arguments: vec![Annotation::data(Span::empty()), prng_annotation.clone()], + arguments: vec![prng_annotation.clone()], ret: Annotation::Constructor { location: Span::empty(), module: None, @@ -220,6 +221,53 @@ impl Type { }) } + pub fn sampler(a: Rc) -> Rc { + let prng_annotation = Annotation::Constructor { + location: Span::empty(), + module: None, + name: PRNG.to_string(), + arguments: vec![], + }; + + Rc::new(Type::Fn { + args: vec![Type::int()], + ret: Type::fuzzer(a), + alias: Some( + TypeAliasAnnotation { + alias: SAMPLER.to_string(), + parameters: vec!["a".to_string()], + annotation: Annotation::Fn { + location: Span::empty(), + arguments: vec![Annotation::int(Span::empty())], + ret: Annotation::Fn { + location: Span::empty(), + arguments: vec![prng_annotation.clone()], + ret: Annotation::Constructor { + location: Span::empty(), + module: None, + name: OPTION.to_string(), + arguments: vec![Annotation::Tuple { + location: Span::empty(), + elems: vec![ + prng_annotation, + Annotation::Var { + location: Span::empty(), + name: "a".to_string(), + }, + ], + }], + } + .into(), + } + .into(), + } + .into(), + } + .into(), + ), + }) + } + pub fn map(k: Rc, v: Rc) -> Rc { Rc::new(Type::App { public: true, diff --git a/crates/aiken-lang/src/builtins.rs b/crates/aiken-lang/src/builtins.rs index ff7891a1..4a4da8aa 100644 --- a/crates/aiken-lang/src/builtins.rs +++ b/crates/aiken-lang/src/builtins.rs @@ -477,23 +477,38 @@ pub fn prelude(id_gen: &IdGenerator) -> TypeInfo { ), ); - // Generator + // Fuzzer // - // pub type Generator = - // fn(Data, PRNG) -> Option<(PRNG, a)> - let generator_context = Type::generic_var(id_gen.next()); - let generator_value = Type::generic_var(id_gen.next()); + // pub type Fuzzer = + // fn(PRNG) -> Option<(PRNG, a)> + let fuzzer_generic = Type::generic_var(id_gen.next()); prelude.types.insert( - well_known::GENERATOR.to_string(), + well_known::FUZZER.to_string(), TypeConstructor { location: Span::empty(), - parameters: vec![generator_context.clone(), generator_value.clone()], - tipo: Type::generator(generator_context, generator_value), + parameters: vec![fuzzer_generic.clone()], + tipo: Type::fuzzer(fuzzer_generic), module: "".to_string(), public: true, }, ); + // Sampler + // + // pub type Sampler = + // fn(Int) -> Fuzzer + let sampler_generic = Type::generic_var(id_gen.next()); + prelude.types.insert( + well_known::SAMPLER.to_string(), + TypeConstructor { + location: Span::empty(), + parameters: vec![sampler_generic.clone()], + tipo: Type::sampler(sampler_generic), + module: "".to_string(), + public: true, + } + ); + prelude } diff --git a/crates/aiken-lang/src/parser/definition/benchmark.rs b/crates/aiken-lang/src/parser/definition/benchmark.rs index 85c31acc..c09609e2 100644 --- a/crates/aiken-lang/src/parser/definition/benchmark.rs +++ b/crates/aiken-lang/src/parser/definition/benchmark.rs @@ -135,7 +135,7 @@ mod tests { fn def_benchmark() { assert_definition!( r#" - benchmark foo(x via fuzz.any_int) { + bench foo(x via fuzz.any_int) { True } "# @@ -146,7 +146,7 @@ mod tests { fn def_invalid_benchmark() { assert_definition!( r#" - benchmark foo(x via f, y via g) { + bench foo(x via f, y via g) { True } "# @@ -157,7 +157,7 @@ mod tests { fn def_benchmark_annotated_fuzzer() { assert_definition!( r#" - benchmark foo(x: Int via foo()) { + bench foo(x: Int via foo()) { True } "# diff --git a/crates/aiken-lang/src/parser/definition/snapshots/def_benchmark.snap b/crates/aiken-lang/src/parser/definition/snapshots/def_benchmark.snap index 083e5d15..e1925dda 100644 --- a/crates/aiken-lang/src/parser/definition/snapshots/def_benchmark.snap +++ b/crates/aiken-lang/src/parser/definition/snapshots/def_benchmark.snap @@ -1,7 +1,7 @@ --- source: crates/aiken-lang/src/parser/definition/benchmark.rs assertion_line: 136 -description: "Code:\n\nbenchmark foo(x via fuzz.any_int) {\n True\n}\n" +description: "Code:\n\nbench foo(x via fuzz.any_int) {\n True\n}\n" snapshot_kind: text --- Benchmark( @@ -13,35 +13,35 @@ Benchmark( Named { name: "x", label: "x", - location: 14..15, + location: 10..11, }, ), - location: 14..15, + location: 10..11, annotation: None, doc: None, is_validator_param: false, }, via: FieldAccess { - location: 20..32, + location: 16..28, label: "any_int", container: Var { - location: 20..24, + location: 16..20, name: "fuzz", }, }, }, ], body: Var { - location: 40..44, + location: 36..40, name: "True", }, doc: None, - location: 0..33, + location: 0..29, name: "foo", public: false, return_annotation: None, return_type: (), - end_position: 45, + end_position: 41, on_test_failure: FailImmediately, }, ) diff --git a/crates/aiken-lang/src/parser/definition/snapshots/def_benchmark_annotated_fuzzer.snap b/crates/aiken-lang/src/parser/definition/snapshots/def_benchmark_annotated_fuzzer.snap index 9a0fadb0..463ec5fb 100644 --- a/crates/aiken-lang/src/parser/definition/snapshots/def_benchmark_annotated_fuzzer.snap +++ b/crates/aiken-lang/src/parser/definition/snapshots/def_benchmark_annotated_fuzzer.snap @@ -1,7 +1,7 @@ --- source: crates/aiken-lang/src/parser/definition/benchmark.rs assertion_line: 158 -description: "Code:\n\nbenchmark foo(x: Int via foo()) {\n True\n}\n" +description: "Code:\n\nbench foo(x: Int via foo()) {\n True\n}\n" snapshot_kind: text --- Benchmark( @@ -13,13 +13,13 @@ Benchmark( Named { name: "x", label: "x", - location: 14..15, + location: 10..11, }, ), - location: 14..20, + location: 10..16, annotation: Some( Constructor { - location: 17..20, + location: 13..16, module: None, name: "Int", arguments: [], @@ -31,24 +31,24 @@ Benchmark( via: Call { arguments: [], fun: Var { - location: 25..28, + location: 21..24, name: "foo", }, - location: 25..30, + location: 21..26, }, }, ], body: Var { - location: 38..42, + location: 34..38, name: "True", }, doc: None, - location: 0..31, + location: 0..27, name: "foo", public: false, return_annotation: None, return_type: (), - end_position: 43, + end_position: 39, on_test_failure: FailImmediately, }, ) diff --git a/crates/aiken-lang/src/parser/definition/snapshots/def_invalid_benchmark.snap b/crates/aiken-lang/src/parser/definition/snapshots/def_invalid_benchmark.snap index db2962df..66ca0e2c 100644 --- a/crates/aiken-lang/src/parser/definition/snapshots/def_invalid_benchmark.snap +++ b/crates/aiken-lang/src/parser/definition/snapshots/def_invalid_benchmark.snap @@ -1,7 +1,7 @@ --- source: crates/aiken-lang/src/parser/definition/benchmark.rs assertion_line: 147 -description: "Code:\n\nbenchmark foo(x via f, y via g) {\n True\n}\n" +description: "Code:\n\nbench foo(x via f, y via g) {\n True\n}\n" snapshot_kind: text --- Benchmark( @@ -13,16 +13,16 @@ Benchmark( Named { name: "x", label: "x", - location: 14..15, + location: 10..11, }, ), - location: 14..15, + location: 10..11, annotation: None, doc: None, is_validator_param: false, }, via: Var { - location: 20..21, + location: 16..17, name: "f", }, }, @@ -32,31 +32,31 @@ Benchmark( Named { name: "y", label: "y", - location: 23..24, + location: 19..20, }, ), - location: 23..24, + location: 19..20, annotation: None, doc: None, is_validator_param: false, }, via: Var { - location: 29..30, + location: 25..26, name: "g", }, }, ], body: Var { - location: 38..42, + location: 34..38, name: "True", }, doc: None, - location: 0..31, + location: 0..27, name: "foo", public: false, return_annotation: None, return_type: (), - end_position: 43, + end_position: 39, on_test_failure: FailImmediately, }, ) diff --git a/crates/aiken-lang/src/parser/lexer.rs b/crates/aiken-lang/src/parser/lexer.rs index 78ac38f3..aba131e5 100644 --- a/crates/aiken-lang/src/parser/lexer.rs +++ b/crates/aiken-lang/src/parser/lexer.rs @@ -243,7 +243,7 @@ pub fn lexer() -> impl Parser, Error = ParseError> { "when" => Token::When, "validator" => Token::Validator, "via" => Token::Via, - "benchmark" => Token::Benchmark, + "bench" => Token::Benchmark, _ => { if s.chars().next().map_or(false, |c| c.is_uppercase()) { Token::UpName { diff --git a/crates/aiken-lang/src/parser/token.rs b/crates/aiken-lang/src/parser/token.rs index f4d2e7e6..4c928e32 100644 --- a/crates/aiken-lang/src/parser/token.rs +++ b/crates/aiken-lang/src/parser/token.rs @@ -183,7 +183,7 @@ impl fmt::Display for Token { Token::Once => "once", Token::Validator => "validator", Token::Via => "via", - Token::Benchmark => "benchmark", + Token::Benchmark => "bench", }; write!(f, "{s}") } diff --git a/crates/aiken-lang/src/test_framework.rs b/crates/aiken-lang/src/test_framework.rs index 31e226ed..f308f807 100644 --- a/crates/aiken-lang/src/test_framework.rs +++ b/crates/aiken-lang/src/test_framework.rs @@ -332,14 +332,12 @@ impl PropertyTest { ) -> Result>, FuzzerError> { let mut prng = initial_prng; let mut counterexample = None; - let mut iteration = 0; while *remaining > 0 && counterexample.is_none() { - let (next_prng, cex) = self.run_once(prng, labels, plutus_version, iteration)?; + let (next_prng, cex) = self.run_once(prng, labels, plutus_version)?; prng = next_prng; counterexample = cex; *remaining -= 1; - iteration += 1; } Ok(counterexample) @@ -350,12 +348,11 @@ impl PropertyTest { prng: Prng, labels: &mut BTreeMap, plutus_version: &'a PlutusVersion, - iteration: usize, ) -> Result<(Prng, Option>), FuzzerError> { use OnTestFailure::*; let (next_prng, value) = prng - .sample(&self.fuzzer.program, iteration)? + .sample(&self.fuzzer.program)? .expect("A seeded PRNG returned 'None' which indicates a fuzzer is ill-formed and implemented wrongly; please contact library's authors."); let mut result = self.eval(&value, plutus_version); @@ -386,8 +383,8 @@ impl PropertyTest { value, choices: next_prng.choices(), cache: Cache::new(move |choices| { - match Prng::from_choices(choices, iteration) - .sample(&self.fuzzer.program, iteration) + match Prng::from_choices(choices) + .sample(&self.fuzzer.program) { Err(..) => Status::Invalid, Ok(None) => Status::Invalid, @@ -454,7 +451,7 @@ pub struct Benchmark { pub name: String, pub on_test_failure: OnTestFailure, pub program: Program, - pub fuzzer: Fuzzer, + pub sampler: Fuzzer, } unsafe impl Send for Benchmark {} @@ -467,11 +464,12 @@ impl Benchmark { plutus_version: &PlutusVersion, ) -> Vec { let mut results = Vec::with_capacity(n); - let mut remaining = n; + let mut iteration = 0; let mut prng = Prng::from_seed(seed); - while remaining > 0 { - match prng.sample(&self.fuzzer.program, n - remaining) { + while n > iteration { + let fuzzer = self.sampler.program.apply_data(Data::integer(num_bigint::BigInt::from(iteration as i64))); + match prng.sample(&fuzzer) { Ok(Some((new_prng, value))) => { prng = new_prng; let mut eval_result = self.eval(&value, plutus_version); @@ -496,7 +494,7 @@ impl Benchmark { break; } } - remaining -= 1; + iteration += 1; } results @@ -534,12 +532,10 @@ pub enum Prng { Seeded { choices: Vec, uplc: PlutusData, - iteration: usize, }, Replayed { choices: Vec, uplc: PlutusData, - iteration: usize, }, } @@ -588,12 +584,11 @@ impl Prng { Data::bytestring(vec![]), // Random choices ], ), - iteration: 0, } } /// Construct a Pseudo-random number generator from a pre-defined list of choices. - pub fn from_choices(choices: &[u8], iteration: usize) -> Prng { + pub fn from_choices(choices: &[u8]) -> Prng { Prng::Replayed { uplc: Data::constr( Prng::REPLAYED, @@ -603,7 +598,6 @@ impl Prng { ], ), choices: choices.to_vec(), - iteration, } } @@ -611,11 +605,10 @@ impl Prng { pub fn sample( &self, fuzzer: &Program, - iteration: usize, + // iteration: usize, ) -> Result, FuzzerError> { let program = Program::::try_from( fuzzer - .apply_data(Data::integer(num_bigint::BigInt::from(iteration as i64))) .apply_data(self.uplc())).unwrap(); let mut result = program.eval(ExBudget::max()); result @@ -624,7 +617,7 @@ impl Prng { traces: result.logs(), uplc_error, }) - .map(|term| Prng::from_result(term, iteration)) + .map(|term| Prng::from_result(term)) } /// Obtain a Prng back from a fuzzer execution. As a reminder, fuzzers have the following @@ -639,10 +632,9 @@ impl Prng { /// aborted altogether with 'None'. pub fn from_result( result: Term, - iteration: usize, ) -> Option<(Self, PlutusData)> { /// Interpret the given 'PlutusData' as one of two Prng constructors. - fn as_prng(cst: &PlutusData, iteration: usize) -> Prng { + fn as_prng(cst: &PlutusData) -> Prng { if let PlutusData::Constr(Constr { tag, fields, .. }) = cst { if *tag == 121 + Prng::SEEDED { if let [PlutusData::BoundedBytes(bytes), PlutusData::BoundedBytes(choices)] = @@ -659,7 +651,6 @@ impl Prng { PlutusData::BoundedBytes(vec![].into()), ], ), - iteration, }; } } @@ -670,7 +661,6 @@ impl Prng { return Prng::Replayed { choices: choices.to_vec(), uplc: cst.clone(), - iteration, }; } } @@ -684,7 +674,7 @@ impl Prng { if *tag == 121 + Prng::SOME { if let [PlutusData::Array(elems)] = &fields[..] { if let [new_seed, value] = &elems[..] { - return Some((as_prng(new_seed, iteration), value.clone())); + return Some((as_prng(new_seed), value.clone())); } } } diff --git a/crates/aiken-lang/src/tests/check.rs b/crates/aiken-lang/src/tests/check.rs index 5028e3e9..cc70b408 100644 --- a/crates/aiken-lang/src/tests/check.rs +++ b/crates/aiken-lang/src/tests/check.rs @@ -1773,17 +1773,27 @@ fn pipe_wrong_arity_fully_saturated_return_fn() { #[test] fn fuzzer_ok_basic() { let source_code = r#" - fn int() -> Generator { todo } + fn int() -> Fuzzer { todo } test prop(n via int()) { True } "#; assert!(check(parse(source_code)).is_ok()); } +#[test] +fn sampler_ok_basic() { + let source_code = r#" + fn int() -> Sampler { todo } + bench prop(n via int()) { True } + "#; + + assert!(check(parse(source_code)).is_ok()); +} + #[test] fn fuzzer_ok_explicit() { let source_code = r#" - fn int(void: Void, prng: PRNG) -> Option<(PRNG, Int)> { todo } + fn int(prng: PRNG) -> Option<(PRNG, Int)> { todo } test prop(n via int) { Void } "#; @@ -1793,8 +1803,8 @@ fn fuzzer_ok_explicit() { #[test] fn fuzzer_ok_list() { let source_code = r#" - fn int() -> Generator { todo } - fn list(a: Generator) -> Generator> { todo } + fn int() -> Fuzzer { todo } + fn list(a: Fuzzer) -> Fuzzer> { todo } test prop(xs via list(int())) { True } "#; @@ -1805,8 +1815,8 @@ fn fuzzer_ok_list() { #[test] fn fuzzer_err_unbound() { let source_code = r#" - fn any() -> Generator { todo } - fn list(a: Generator) -> Generator> { todo } + fn any() -> Fuzzer { todo } + fn list(a: Fuzzer) -> Fuzzer> { todo } test prop(xs via list(any())) { todo } "#; @@ -1838,7 +1848,7 @@ fn fuzzer_err_unify_1() { #[test] fn fuzzer_err_unify_2() { let source_code = r#" - fn any() -> Generator { todo } + fn any() -> Fuzzer { todo } test prop(xs via any) { todo } "#; @@ -1857,8 +1867,8 @@ fn fuzzer_err_unify_2() { #[test] fn fuzzer_err_unify_3() { let source_code = r#" - fn list(a: Generator) -> Generator> { todo } - fn int() -> Generator { todo } + fn list(a: Fuzzer) -> Fuzzer> { todo } + fn int() -> Fuzzer { todo } test prop(xs: Int via list(int())) { todo } "#; diff --git a/crates/aiken-lang/src/tipo/infer.rs b/crates/aiken-lang/src/tipo/infer.rs index 042fdd3b..adc2e37e 100644 --- a/crates/aiken-lang/src/tipo/infer.rs +++ b/crates/aiken-lang/src/tipo/infer.rs @@ -838,8 +838,7 @@ fn infer_fuzzer( ) -> Result<(Annotation, Rc), Error> { let could_not_unify = || Error::CouldNotUnify { location: *location, - expected: Type::generator( - Type::void(), + expected: Type::fuzzer( expected_inner_type .clone() .unwrap_or_else(|| Type::generic_var(0)), @@ -863,7 +862,7 @@ fn infer_fuzzer( contains_opaque: _, alias: _, } if module.is_empty() && name == "Option" && args.len() == 1 => { - match args.first().expect("args.len() == 2 && args[0].is_void()").borrow() { + match args.first().expect("args.len() == 1").borrow() { Type::Tuple { elems, .. } if elems.len() == 2 => { let wrapped = elems.get(1).expect("Tuple has two elements"); @@ -878,7 +877,7 @@ fn infer_fuzzer( // `unify` now that we have figured out the type carried by the fuzzer. environment.unify( tipo.clone(), - Type::generator(Type::void(), wrapped.clone()), + Type::fuzzer(wrapped.clone()), *location, false, )?; @@ -916,8 +915,7 @@ fn infer_sampler( ) -> Result<(Annotation, Rc), Error> { let could_not_unify = || Error::CouldNotUnify { location: *location, - expected: Type::generator( - Type::int(), + expected: Type::sampler( expected_inner_type .clone() .unwrap_or_else(|| Type::generic_var(0)), @@ -930,34 +928,12 @@ fn infer_sampler( match tipo.borrow() { Type::Fn { ret, - args: _, + args, alias: _, - } => match ret.borrow() { - Type::App { - module, - name, - args, - public: _, - contains_opaque: _, - alias: _, - } if module.is_empty() && name == "Option" && args.len() == 1 => { - match args.first().expect("args.len() == 2 && args[0].is_int()").borrow() { - Type::Tuple { elems, .. } if elems.len() == 2 => { - let wrapped = elems.get(1).expect("Tuple has two elements"); - - environment.unify( - tipo.clone(), - Type::generator(Type::int(), wrapped.clone()), - *location, - false, - )?; - - Ok((annotate_fuzzer(wrapped, location)?, wrapped.clone())) - } - _ => Err(could_not_unify()), - } - } - _ => Err(could_not_unify()), + } => if args.len() == 1 && args[0].is_int() { + infer_fuzzer(environment, expected_inner_type, ret, &Span::empty()) + } else { + Err(could_not_unify()) }, Type::Var { tipo, alias } => match &*tipo.deref().borrow() { diff --git a/crates/aiken-project/src/error.rs b/crates/aiken-project/src/error.rs index 85a8c4e5..97e99aea 100644 --- a/crates/aiken-project/src/error.rs +++ b/crates/aiken-project/src/error.rs @@ -193,7 +193,7 @@ impl Error { test.input_path.to_path_buf(), test.program.to_pretty(), ), - TestResult::Benchmark(_) => ("benchmark".to_string(), PathBuf::new(), String::new()), // todo + TestResult::Benchmark(_) => ("bench".to_string(), PathBuf::new(), String::new()), // todo }; Error::TestFailure { diff --git a/crates/aiken-project/src/test_framework.rs b/crates/aiken-project/src/test_framework.rs index e12072bc..38db882a 100644 --- a/crates/aiken-project/src/test_framework.rs +++ b/crates/aiken-project/src/test_framework.rs @@ -112,10 +112,8 @@ mod test { const max_int: Int = 255 - pub type Fuzzer = Generator - pub fn int() -> Fuzzer { - fn(v: Void, prng: PRNG) -> Option<(PRNG, Int)> { + fn(prng: PRNG) -> Option<(PRNG, Int)> { when prng is { Seeded { seed, choices } -> { let choice = @@ -163,21 +161,21 @@ mod test { } pub fn constant(a: a) -> Fuzzer { - fn(v, s0) { Some((s0, a)) } + fn(s0) { Some((s0, a)) } } pub fn and_then(fuzz_a: Fuzzer, f: fn(a) -> Fuzzer) -> Fuzzer { - fn(v, s0) { - when fuzz_a(v, s0) is { - Some((s1, a)) -> f(a)(v, s1) + fn(s0) { + when fuzz_a(s0) is { + Some((s1, a)) -> f(a)(s1) None -> None } } } pub fn map(fuzz_a: Fuzzer, f: fn(a) -> b) -> Fuzzer { - fn(v, s0) { - when fuzz_a(v, s0) is { + fn(s0) { + when fuzz_a(s0) is { Some((s1, a)) -> Some((s1, f(a))) None -> None } @@ -185,10 +183,10 @@ mod test { } pub fn map2(fuzz_a: Fuzzer, fuzz_b: Fuzzer, f: fn(a, b) -> c) -> Fuzzer { - fn(v, s0) { - when fuzz_a(v, s0) is { + fn(s0) { + when fuzz_a(s0) is { Some((s1, a)) -> - when fuzz_b(Void, s1) is { + when fuzz_b(s1) is { Some((s2, b)) -> Some((s2, f(a, b))) None -> None }