Implement reification from Maps.
This commit is contained in:
parent
5272f5ecee
commit
bfcfc5c41b
|
@ -1,4 +1,5 @@
|
||||||
use std::{collections::HashMap, rc::Rc};
|
use std::{collections::HashMap, rc::Rc};
|
||||||
|
use uplc::KeyValuePairs;
|
||||||
|
|
||||||
use vec1::Vec1;
|
use vec1::Vec1;
|
||||||
|
|
||||||
|
@ -607,32 +608,38 @@ impl UntypedExpr {
|
||||||
preferred_format: ByteArrayFormatPreference::HexadecimalString,
|
preferred_format: ByteArrayFormatPreference::HexadecimalString,
|
||||||
}),
|
}),
|
||||||
PlutusData::Array(args) => {
|
PlutusData::Array(args) => {
|
||||||
let inner;
|
|
||||||
match tipo {
|
match tipo {
|
||||||
Type::App {
|
Type::App {
|
||||||
module, name, args, ..
|
module,
|
||||||
|
name,
|
||||||
|
args: type_args,
|
||||||
|
..
|
||||||
} if module.is_empty() && name.as_str() == "List" => {
|
} if module.is_empty() && name.as_str() == "List" => {
|
||||||
if let [arg] = &args[..] {
|
if let [inner] = &type_args[..] {
|
||||||
inner = arg.clone()
|
|
||||||
} else {
|
|
||||||
return Err("invalid List type annotation: the list has multiple type-parameters.".to_string());
|
|
||||||
};
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(format!(
|
|
||||||
"invalid type annotation. expected List but got: {tipo:?}"
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(UntypedExpr::List {
|
Ok(UntypedExpr::List {
|
||||||
location: Span::empty(),
|
location: Span::empty(),
|
||||||
elements: args
|
elements: args
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|arg| UntypedExpr::reify(data_types, arg, &inner))
|
.map(|arg| UntypedExpr::reify(data_types, arg, inner))
|
||||||
.collect::<Result<Vec<_>, _>>()?,
|
.collect::<Result<Vec<_>, _>>()?,
|
||||||
tail: None,
|
tail: None,
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
Err("invalid List type annotation: the list has multiple type-parameters.".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Type::Tuple { elems } => Ok(UntypedExpr::Tuple {
|
||||||
|
location: Span::empty(),
|
||||||
|
elems: args
|
||||||
|
.into_iter()
|
||||||
|
.zip(elems)
|
||||||
|
.map(|(arg, arg_type)| UntypedExpr::reify(data_types, arg, arg_type))
|
||||||
|
.collect::<Result<Vec<_>, _>>()?,
|
||||||
|
}),
|
||||||
|
_ => Err(format!(
|
||||||
|
"invalid type annotation. expected List but got: {tipo:?}"
|
||||||
|
)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PlutusData::Constr(Constr {
|
PlutusData::Constr(Constr {
|
||||||
|
@ -716,7 +723,22 @@ impl UntypedExpr {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
PlutusData::Map(..) => todo!("reify Map"),
|
PlutusData::Map(indef_or_def) => {
|
||||||
|
let kvs = match indef_or_def {
|
||||||
|
KeyValuePairs::Def(kvs) => kvs,
|
||||||
|
KeyValuePairs::Indef(kvs) => kvs,
|
||||||
|
};
|
||||||
|
|
||||||
|
UntypedExpr::reify(
|
||||||
|
data_types,
|
||||||
|
PlutusData::Array(
|
||||||
|
kvs.into_iter()
|
||||||
|
.map(|(k, v)| PlutusData::Array(vec![k, v]))
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
tipo,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,10 @@ pub struct CodeGenerator<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CodeGenerator<'a> {
|
impl<'a> CodeGenerator<'a> {
|
||||||
|
pub fn data_types(&self) -> &IndexMap<DataTypeKey, &'a TypedDataType> {
|
||||||
|
&self.data_types
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
functions: IndexMap<FunctionAccessKey, &'a TypedFunction>,
|
functions: IndexMap<FunctionAccessKey, &'a TypedFunction>,
|
||||||
data_types: IndexMap<DataTypeKey, &'a TypedDataType>,
|
data_types: IndexMap<DataTypeKey, &'a TypedDataType>,
|
||||||
|
|
|
@ -57,6 +57,18 @@ pub enum Type {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Type {
|
impl Type {
|
||||||
|
pub fn qualifier(&self) -> Option<(String, String)> {
|
||||||
|
match self {
|
||||||
|
Type::App { module, name, .. } => Some((module.to_string(), name.to_string())),
|
||||||
|
Type::Fn { .. } => None,
|
||||||
|
Type::Var { ref tipo } => match &*tipo.borrow() {
|
||||||
|
TypeVar::Link { ref tipo } => tipo.qualifier(),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
Type::Tuple { .. } => Some((String::new(), "Tuple".to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_result_constructor(&self) -> bool {
|
pub fn is_result_constructor(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Type::Fn { ret, .. } => ret.is_result(),
|
Type::Fn { ret, .. } => ret.is_result(),
|
||||||
|
|
|
@ -34,6 +34,7 @@ use aiken_lang::{
|
||||||
},
|
},
|
||||||
builtins,
|
builtins,
|
||||||
expr::{TypedExpr, UntypedExpr},
|
expr::{TypedExpr, UntypedExpr},
|
||||||
|
gen_uplc::builder::convert_opaque_type,
|
||||||
tipo::{Type, TypeInfo},
|
tipo::{Type, TypeInfo},
|
||||||
IdGenerator,
|
IdGenerator,
|
||||||
};
|
};
|
||||||
|
@ -46,7 +47,7 @@ use pallas::ledger::{
|
||||||
primitives::babbage::{self as cardano, PolicyId},
|
primitives::babbage::{self as cardano, PolicyId},
|
||||||
traverse::ComputeHash,
|
traverse::ComputeHash,
|
||||||
};
|
};
|
||||||
use script::{Assertion, Test, TestResult};
|
use script::{Assertion, Fuzzer, Test, TestResult};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fs::{self, File},
|
fs::{self, File},
|
||||||
|
@ -844,7 +845,7 @@ where
|
||||||
|
|
||||||
let via = parameter.via.clone();
|
let via = parameter.via.clone();
|
||||||
|
|
||||||
let type_info = parameter.tipo.clone();
|
let type_info = convert_opaque_type(¶meter.tipo, generator.data_types());
|
||||||
|
|
||||||
let body = TypedExpr::Fn {
|
let body = TypedExpr::Fn {
|
||||||
location: Span::empty(),
|
location: Span::empty(),
|
||||||
|
@ -876,7 +877,10 @@ where
|
||||||
name.to_string(),
|
name.to_string(),
|
||||||
*can_error,
|
*can_error,
|
||||||
program,
|
program,
|
||||||
(fuzzer, type_info),
|
Fuzzer {
|
||||||
|
program: fuzzer,
|
||||||
|
type_info,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
programs.push(prop);
|
programs.push(prop);
|
||||||
|
|
|
@ -73,7 +73,7 @@ impl Test {
|
||||||
name: String,
|
name: String,
|
||||||
can_error: bool,
|
can_error: bool,
|
||||||
program: Program<NamedDeBruijn>,
|
program: Program<NamedDeBruijn>,
|
||||||
fuzzer: (Program<NamedDeBruijn>, Rc<Type>),
|
fuzzer: Fuzzer<NamedDeBruijn>,
|
||||||
) -> Test {
|
) -> Test {
|
||||||
Test::PropertyTest(PropertyTest {
|
Test::PropertyTest(PropertyTest {
|
||||||
input_path,
|
input_path,
|
||||||
|
@ -130,11 +130,17 @@ pub struct PropertyTest {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub can_error: bool,
|
pub can_error: bool,
|
||||||
pub program: Program<NamedDeBruijn>,
|
pub program: Program<NamedDeBruijn>,
|
||||||
pub fuzzer: (Program<NamedDeBruijn>, Rc<Type>),
|
pub fuzzer: Fuzzer<NamedDeBruijn>,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Send for PropertyTest {}
|
unsafe impl Send for PropertyTest {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Fuzzer<T> {
|
||||||
|
pub program: Program<T>,
|
||||||
|
pub type_info: Rc<Type>,
|
||||||
|
}
|
||||||
|
|
||||||
impl PropertyTest {
|
impl PropertyTest {
|
||||||
const MAX_TEST_RUN: usize = 100;
|
const MAX_TEST_RUN: usize = 100;
|
||||||
|
|
||||||
|
@ -177,7 +183,7 @@ impl PropertyTest {
|
||||||
|
|
||||||
fn run_once(&self, seed: u32) -> (u32, Option<PlutusData>) {
|
fn run_once(&self, seed: u32) -> (u32, Option<PlutusData>) {
|
||||||
let (next_prng, value) = Prng::from_seed(seed)
|
let (next_prng, value) = Prng::from_seed(seed)
|
||||||
.sample(&self.fuzzer.0)
|
.sample(&self.fuzzer.program)
|
||||||
.expect("running seeded Prng cannot fail.");
|
.expect("running seeded Prng cannot fail.");
|
||||||
|
|
||||||
let result = self.eval(&value);
|
let result = self.eval(&value);
|
||||||
|
@ -207,7 +213,7 @@ impl PropertyTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eval(&self, value: &PlutusData) -> EvalResult {
|
pub fn eval(&self, value: &PlutusData) -> EvalResult {
|
||||||
let term = convert_data_to_type(Term::data(value.clone()), &self.fuzzer.1)
|
let term = convert_data_to_type(Term::data(value.clone()), &self.fuzzer.type_info)
|
||||||
.try_into()
|
.try_into()
|
||||||
.expect("safe conversion from Name -> NamedDeBruijn");
|
.expect("safe conversion from Name -> NamedDeBruijn");
|
||||||
self.program.apply_term(&term).eval(ExBudget::max())
|
self.program.apply_term(&term).eval(ExBudget::max())
|
||||||
|
@ -399,7 +405,7 @@ impl<'a> Counterexample<'a> {
|
||||||
// test cases many times. Given that tests are fully deterministic, we can
|
// test cases many times. Given that tests are fully deterministic, we can
|
||||||
// memoize the already seen choices to avoid re-running the generators and
|
// memoize the already seen choices to avoid re-running the generators and
|
||||||
// the test (which can be quite expensive).
|
// the test (which can be quite expensive).
|
||||||
match Prng::from_choices(choices).sample(&self.property.fuzzer.0) {
|
match Prng::from_choices(choices).sample(&self.property.fuzzer.program) {
|
||||||
// Shrinked choices led to an impossible generation.
|
// Shrinked choices led to an impossible generation.
|
||||||
None => false,
|
None => false,
|
||||||
|
|
||||||
|
@ -653,7 +659,7 @@ impl PropertyTestResult<PlutusData> {
|
||||||
counterexample: match self.counterexample {
|
counterexample: match self.counterexample {
|
||||||
None => None,
|
None => None,
|
||||||
Some(counterexample) => Some(
|
Some(counterexample) => Some(
|
||||||
UntypedExpr::reify(data_types, counterexample, &self.test.fuzzer.1)
|
UntypedExpr::reify(data_types, counterexample, &self.test.fuzzer.type_info)
|
||||||
.expect("Failed to reify counterexample?"),
|
.expect("Failed to reify counterexample?"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
# This file was generated by Aiken
|
# This file was generated by Aiken
|
||||||
# You typically do not need to edit this file
|
# You typically do not need to edit this file
|
||||||
|
|
||||||
|
[[requirements]]
|
||||||
|
name = "aiken-lang/stdlib"
|
||||||
|
version = "main"
|
||||||
|
source = "github"
|
||||||
|
|
||||||
|
[[packages]]
|
||||||
|
name = "aiken-lang/stdlib"
|
||||||
|
version = "main"
|
||||||
requirements = []
|
requirements = []
|
||||||
packages = []
|
source = "github"
|
||||||
|
|
||||||
[etags]
|
[etags]
|
||||||
|
"aiken-lang/stdlib@main" = [{ secs_since_epoch = 1709304545, nanos_since_epoch = 842241000 }, "cf946239d3dd481ed41f20e56bf24910b5229ea35aa171a708edc2a47fc20a7b"]
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
name = "aiken-lang/acceptance_test_095"
|
name = "aiken-lang/acceptance_test_095"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
description = ""
|
description = ""
|
||||||
|
|
||||||
|
[[dependencies]]
|
||||||
|
name = "aiken-lang/stdlib"
|
||||||
|
version = "main"
|
||||||
|
source = "github"
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
use aiken/builtin
|
||||||
|
|
||||||
|
const max_int: Int = 255
|
||||||
|
|
||||||
|
pub type PRNG {
|
||||||
|
Seeded { seed: Int, choices: List<Int> }
|
||||||
|
Replayed { choices: List<Int> }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Fuzzer<a> =
|
||||||
|
fn(PRNG) -> Option<(PRNG, a)>
|
||||||
|
|
||||||
|
// Primitives
|
||||||
|
|
||||||
|
pub fn any_int() -> Fuzzer<Int> {
|
||||||
|
fn(prng: PRNG) -> Option<(PRNG, Int)> {
|
||||||
|
when prng is {
|
||||||
|
Seeded { seed, choices } -> {
|
||||||
|
let digest =
|
||||||
|
seed
|
||||||
|
|> builtin.integer_to_bytearray(True, 32, _)
|
||||||
|
|> builtin.blake2b_256()
|
||||||
|
|
||||||
|
let choice =
|
||||||
|
digest
|
||||||
|
|> builtin.index_bytearray(0)
|
||||||
|
|
||||||
|
let new_seed =
|
||||||
|
digest
|
||||||
|
|> builtin.slice_bytearray(1, 4, _)
|
||||||
|
|> builtin.bytearray_to_integer(True, _)
|
||||||
|
|
||||||
|
Some((Seeded { seed: new_seed, choices: [choice, ..choices] }, choice))
|
||||||
|
}
|
||||||
|
|
||||||
|
Replayed { choices } ->
|
||||||
|
when choices is {
|
||||||
|
[] -> None
|
||||||
|
[head, ..tail] ->
|
||||||
|
if head >= 0 && head <= max_int {
|
||||||
|
Some((Replayed { choices: tail }, head))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn constant(a: a) -> Fuzzer<a> {
|
||||||
|
fn(s0) { Some((s0, a)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn and_then(fuzz_a: Fuzzer<a>, f: fn(a) -> Fuzzer<b>) -> Fuzzer<b> {
|
||||||
|
fn(s0) {
|
||||||
|
when fuzz_a(s0) is {
|
||||||
|
Some((s1, a)) -> f(a)(s1)
|
||||||
|
None -> None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map(fuzz_a: Fuzzer<a>, f: fn(a) -> b) -> Fuzzer<b> {
|
||||||
|
fn(s0) {
|
||||||
|
when fuzz_a(s0) is {
|
||||||
|
Some((s1, a)) -> Some((s1, f(a)))
|
||||||
|
None -> None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map2(fuzz_a: Fuzzer<a>, fuzz_b: Fuzzer<b>, f: fn(a, b) -> c) -> Fuzzer<c> {
|
||||||
|
fn(s0) {
|
||||||
|
when fuzz_a(s0) is {
|
||||||
|
Some((s1, a)) ->
|
||||||
|
when fuzz_b(s1) is {
|
||||||
|
Some((s2, b)) -> Some((s2, f(a, b)))
|
||||||
|
None -> None
|
||||||
|
}
|
||||||
|
None -> None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map4(
|
||||||
|
fuzz_a: Fuzzer<a>,
|
||||||
|
fuzz_b: Fuzzer<b>,
|
||||||
|
fuzz_c: Fuzzer<c>,
|
||||||
|
fuzz_d: Fuzzer<d>,
|
||||||
|
f: fn(a, b, c, d) -> result,
|
||||||
|
) -> Fuzzer<result> {
|
||||||
|
fn(s0) {
|
||||||
|
when fuzz_a(s0) is {
|
||||||
|
Some((s1, a)) ->
|
||||||
|
when fuzz_b(s1) is {
|
||||||
|
Some((s2, b)) ->
|
||||||
|
when fuzz_c(s2) is {
|
||||||
|
Some((s3, c)) ->
|
||||||
|
when fuzz_d(s3) is {
|
||||||
|
Some((s4, d)) -> Some((s4, f(a, b, c, d)))
|
||||||
|
None -> None
|
||||||
|
}
|
||||||
|
None -> None
|
||||||
|
}
|
||||||
|
None -> None
|
||||||
|
}
|
||||||
|
|
||||||
|
None -> None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builders
|
||||||
|
|
||||||
|
fn any_bool() -> Fuzzer<Bool> {
|
||||||
|
any_int() |> map(fn(n) { n <= 127 })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn any_list(fuzz_a: Fuzzer<a>) -> Fuzzer<List<a>> {
|
||||||
|
any_bool()
|
||||||
|
|> and_then(
|
||||||
|
fn(continue) {
|
||||||
|
if continue {
|
||||||
|
map2(fuzz_a, any_list(fuzz_a), fn(head, tail) { [head, ..tail] })
|
||||||
|
} else {
|
||||||
|
constant([])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,166 +1,47 @@
|
||||||
use aiken/builtin
|
use aiken/dict.{Dict}
|
||||||
|
use aiken/fuzz.{Fuzzer}
|
||||||
const max_int: Int = 255
|
use aiken/int
|
||||||
|
|
||||||
pub type PRNG {
|
|
||||||
Seeded { seed: Int, choices: List<Int> }
|
|
||||||
Replayed { choices: List<Int> }
|
|
||||||
}
|
|
||||||
|
|
||||||
type Fuzzer<a> =
|
|
||||||
fn(PRNG) -> Option<(PRNG, a)>
|
|
||||||
|
|
||||||
// Primitives
|
|
||||||
|
|
||||||
fn any_int(prng: PRNG) -> Option<(PRNG, Int)> {
|
|
||||||
when prng is {
|
|
||||||
Seeded { seed, choices } -> {
|
|
||||||
let digest =
|
|
||||||
seed
|
|
||||||
|> builtin.integer_to_bytearray(True, 32, _)
|
|
||||||
|> builtin.blake2b_256()
|
|
||||||
|
|
||||||
let choice =
|
|
||||||
digest
|
|
||||||
|> builtin.index_bytearray(0)
|
|
||||||
|
|
||||||
let new_seed =
|
|
||||||
digest
|
|
||||||
|> builtin.slice_bytearray(1, 4, _)
|
|
||||||
|> builtin.bytearray_to_integer(True, _)
|
|
||||||
|
|
||||||
Some((Seeded { seed: new_seed, choices: [choice, ..choices] }, choice))
|
|
||||||
}
|
|
||||||
|
|
||||||
Replayed { choices } ->
|
|
||||||
when choices is {
|
|
||||||
[] -> None
|
|
||||||
[head, ..tail] ->
|
|
||||||
if head >= 0 && head <= max_int {
|
|
||||||
Some((Replayed { choices: tail }, head))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn constant(a: a) -> Fuzzer<a> {
|
|
||||||
fn(s0) { Some((s0, a)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn and_then(fuzz_a: Fuzzer<a>, f: fn(a) -> Fuzzer<b>) -> Fuzzer<b> {
|
|
||||||
fn(s0) {
|
|
||||||
when fuzz_a(s0) is {
|
|
||||||
Some((s1, a)) -> f(a)(s1)
|
|
||||||
None -> None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn map(fuzz_a: Fuzzer<a>, f: fn(a) -> b) -> Fuzzer<b> {
|
|
||||||
fn(s0) {
|
|
||||||
when fuzz_a(s0) is {
|
|
||||||
Some((s1, a)) -> Some((s1, f(a)))
|
|
||||||
None -> None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn map2(fuzz_a: Fuzzer<a>, fuzz_b: Fuzzer<b>, f: fn(a, b) -> c) -> Fuzzer<c> {
|
|
||||||
fn(s0) {
|
|
||||||
when fuzz_a(s0) is {
|
|
||||||
Some((s1, a)) ->
|
|
||||||
when fuzz_b(s1) is {
|
|
||||||
Some((s2, b)) -> Some((s2, f(a, b)))
|
|
||||||
None -> None
|
|
||||||
}
|
|
||||||
None -> None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Builders
|
|
||||||
|
|
||||||
fn any_bool() -> Fuzzer<Bool> {
|
|
||||||
any_int |> map(fn(n) { n <= 127 })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn any_list(fuzz_a: Fuzzer<a>) -> Fuzzer<List<a>> {
|
|
||||||
any_bool()
|
|
||||||
|> and_then(
|
|
||||||
fn(continue) {
|
|
||||||
if continue {
|
|
||||||
map2(fuzz_a, any_list(fuzz_a), fn(head, tail) { [head, ..tail] })
|
|
||||||
} else {
|
|
||||||
constant([])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn any_season() -> Fuzzer<Season> {
|
|
||||||
any_int
|
|
||||||
|> and_then(
|
|
||||||
fn(i) {
|
|
||||||
let n = i % 3
|
|
||||||
if n == 0 {
|
|
||||||
any_bool() |> map(Winter)
|
|
||||||
} else if n == 1 {
|
|
||||||
constant(Spring(i))
|
|
||||||
} else if n == 2 {
|
|
||||||
constant(Summer)
|
|
||||||
} else {
|
|
||||||
constant(Fall)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn length(xs: List<a>) -> Int {
|
|
||||||
when xs is {
|
|
||||||
[] -> 0
|
|
||||||
[_, ..tail] -> 1 + length(tail)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn filter(xs: List<a>, f: fn(a) -> Bool) -> List<a> {
|
|
||||||
when xs is {
|
|
||||||
[] ->
|
|
||||||
[]
|
|
||||||
[head, ..tail] ->
|
|
||||||
if f(head) {
|
|
||||||
[head, ..filter(tail, f)]
|
|
||||||
} else {
|
|
||||||
filter(tail, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Properties
|
|
||||||
|
|
||||||
pub type Season {
|
pub type Season {
|
||||||
Winter(Bool)
|
Winter
|
||||||
Spring(Int)
|
Spring
|
||||||
Summer
|
Summer
|
||||||
Fall
|
Fall
|
||||||
}
|
}
|
||||||
|
|
||||||
// test prop_is_never_summer(xs via any_list(any_season())) {
|
fn compare_season(a: Season, b: Season) -> Ordering {
|
||||||
// filter(xs, fn(x) { x == Summer }) == []
|
let season_to_int =
|
||||||
// }
|
fn(season) {
|
||||||
|
when season is {
|
||||||
|
Winter -> 0
|
||||||
|
Spring -> 1
|
||||||
|
Summer -> 2
|
||||||
|
Fall -> 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test prop_is_always_cold_in_winter(xs via any_list(any_season())) {
|
int.compare(season_to_int(a), season_to_int(b))
|
||||||
is_always_cold_in_winter(xs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test prop_is_always_cold_in_winter_2() {
|
fn any_year() -> Fuzzer<Dict<Season, Int>> {
|
||||||
is_always_cold_in_winter([Winter(True)])
|
fuzz.map4(
|
||||||
|
fuzz.any_int(),
|
||||||
|
fuzz.any_int(),
|
||||||
|
fuzz.any_int(),
|
||||||
|
fuzz.any_int(),
|
||||||
|
fn(a, b, c, d) {
|
||||||
|
dict.new()
|
||||||
|
|> dict.insert(Winter, a, compare_season)
|
||||||
|
|> dict.insert(Spring, b, compare_season)
|
||||||
|
|> dict.insert(Summer, c, compare_season)
|
||||||
|
|> dict.insert(Fall, d, compare_season)
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_always_cold_in_winter(xs: List<Season>) -> Bool {
|
test prop_always_cold_in_winter(year via any_year()) {
|
||||||
when xs is {
|
when dict.get(year, Winter) is {
|
||||||
[Winter(cold), ..tail] -> cold && is_always_cold_in_winter(tail)
|
Some(temperature) -> temperature <= 10
|
||||||
_ -> True
|
_ -> fail @"failed to get?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue