summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--rust/src/ast.rs37
-rw-r--r--rust/src/lambda.rs14
-rw-r--r--rust/src/web.rs24
3 files changed, 74 insertions, 1 deletions
diff --git a/rust/src/ast.rs b/rust/src/ast.rs
index e70f323..29d548f 100644
--- a/rust/src/ast.rs
+++ b/rust/src/ast.rs
@@ -22,7 +22,13 @@ impl Display for Value {
Value::Num(i) => write!(f, "{}", i),
Value::Bool(b) => write!(f, "{}", b),
Value::Sym(s) => write!(f, "{}", s),
- Value::App(l, r) => write!(f, "({} {})", l, r),
+ 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),
@@ -71,3 +77,32 @@ impl Arbitrary for Value {
.boxed()
}
}
+
+#[cfg(test)]
+mod ast_tests {
+
+ use super::{
+ identifier,
+ Value::{self, *},
+ };
+ use proptest::collection::vec;
+ use proptest::prelude::*;
+
+ fn any_sym() -> impl Strategy<Value = Value> {
+ 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::<Vec<String>>().join(" ")));
+ }
+ }
+}
diff --git a/rust/src/lambda.rs b/rust/src/lambda.rs
index e6102d8..ee5cb1f 100644
--- a/rust/src/lambda.rs
+++ b/rust/src/lambda.rs
@@ -115,6 +115,8 @@ pub fn generate_expr(size: u32, runner: &mut TestRunner) -> Value {
4 => simple_app().new_tree(runner).unwrap().current(),
5 => nested_simple_app().new_tree(runner).unwrap().current(),
6 => simple_lambda().new_tree(runner).unwrap().current(),
+ 7 => app_to_lambda().new_tree(runner).unwrap().current(),
+ 8 => multi_app().new_tree(runner).unwrap().current(),
_ => todo!(),
}
}
@@ -124,6 +126,11 @@ fn simple_app() -> impl Strategy<Value = Value> {
(leaf.clone(), leaf.clone()).prop_map(|(l, r)| Value::App(Box::new(l), Box::new(r)))
}
+fn multi_app() -> impl Strategy<Value = Value> {
+ let leaf = prop_oneof![any_num(), any_sym()];
+ (leaf.clone(), leaf.clone()).prop_map(|(l, r)| Value::App(Box::new(l), Box::new(r)))
+}
+
fn any_num() -> impl Strategy<Value = Value> {
any::<i32>().prop_map(Value::Num)
}
@@ -140,9 +147,16 @@ fn any_sym() -> impl Strategy<Value = Value> {
}
fn simple_lambda() -> impl Strategy<Value = Value> {
+ // TODO: there's nothing to guarantee the variable appears in the body
(ascii_identifier(), nested_simple_app()).prop_map(|(v, b)| Value::Lam(v, Box::new(b)))
}
+fn app_to_lambda() -> impl Strategy<Value = Value> {
+ let lam = simple_lambda();
+ let arg = prop_oneof![any_num(), any_sym(), nested_simple_app()];
+ (lam, arg).prop_map(|(l, a)| Value::App(Box::new(l), Box::new(a)))
+}
+
#[cfg(test)]
mod lambda_test {
use crate::parser::parse;
diff --git a/rust/src/web.rs b/rust/src/web.rs
index de87ad6..8fa9452 100644
--- a/rust/src/web.rs
+++ b/rust/src/web.rs
@@ -465,6 +465,30 @@ mod app_tests {
}
#[test]
+ async fn client_generates_application_with_lambda_terms_at_level_7() {
+ let mut client = client();
+ client.grade = 7;
+
+ let (input, _) = client.generate_expr();
+
+ let parsed = parse(&input);
+ match &parsed[..] {
+ [Value::App(t1, _)] if matches!(**t1, Value::Lam(_, _)) => (),
+ _ => panic!("Expected symbol, got {:?}", parsed),
+ }
+ }
+
+ #[test]
+ async fn client_generates_applications_with_more_than_2_terms_at_level_8() {
+ let mut client = client();
+ client.grade = 8;
+
+ let (input, _) = client.generate_expr();
+
+ assert!(input.split(' ').count() > 2);
+ }
+
+ #[test]
async fn client_increases_grade_on_successful_test() {
let mut client = client();
let expected = "1".to_string();