use proptest::{ prelude::*, string::{string_regex, RegexGeneratorStrategy}, }; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display}; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub enum Value { Num(i32), Bool(bool), Sym(String), App(Box, Box), Lam(String, Box), Def(String, Box), Let(String, Box, Box), } impl Display for Value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Value::Num(i) => write!(f, "{}", i), Value::Bool(b) => write!(f, "{}", b), Value::Sym(s) => write!(f, "{}", s), Value::App(l, r) => { if let App(l1, l2) = *l.clone() { write!(f, "({} {} {})", l1, l2, r) } else { write!(f, "({} {})", l, r) } } Value::Lam(var, body) => write!(f, "(lam {} {})", var, body), Value::Def(var, value) => write!(f, "(def {} {})", var, value), Value::Let(var, value, body) => write!(f, "(let ({} {}) {})", var, value, body), } } } use Value::*; pub const IDENTIFIER: &str = "\\pL(\\pL|\\pN)*"; pub fn identifier() -> RegexGeneratorStrategy { string_regex(IDENTIFIER).unwrap() } pub fn ascii_identifier() -> RegexGeneratorStrategy { string_regex("[a-zA-Z][a-zA-Z0-9]*").unwrap() } impl Arbitrary for Value { type Parameters = (); type Strategy = BoxedStrategy; fn arbitrary_with(_args: ()) -> Self::Strategy { let any_num = any::().prop_map(Num); let any_bool = any::().prop_map(Bool); let leaf = prop_oneof![ any_num, any_bool, // see https://unicode.org/reports/tr18/#General_Category_Property for one letter unicode categories identifier().prop_map(Sym), ]; let expr = leaf.prop_recursive(4, 128, 5, move |inner| { prop_oneof![ (inner.clone(), inner.clone()).prop_map(|(l, r)| App(Box::new(l), Box::new(r))), (identifier(), inner.clone()).prop_map(|(var, body)| Lam(var, Box::new(body))), (identifier(), inner.clone(), inner.clone()).prop_map(|(var, body, expr)| { Value::Let(var, Box::new(body), Box::new(expr)) }), ] }); prop_oneof![ expr.clone(), (identifier(), expr).prop_map(|(var, body)| Def(var, Box::new(body))) ] .boxed() } } #[cfg(test)] mod ast_tests { use super::{ identifier, Value::{self, *}, }; use proptest::collection::vec; use proptest::prelude::*; fn any_sym() -> impl Strategy { identifier().prop_map(Sym) } proptest! { #[test] fn display_multiple_applications_as_a_sequence(atoms in vec("[a-z]".prop_map(Sym), 2..10)) { let init = atoms.first().unwrap().clone(); let value = atoms.iter().skip(1).fold(init, |acc, expr| { Value::App(Box::new(acc.clone()), Box::new(expr.clone())) }); assert_eq!(value.to_string(), format!("({})", atoms.iter().map(|v| v.to_string()).collect::>().join(" "))); } } }