diff --git a/Cargo.lock b/Cargo.lock index 58c81c4..903555b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,6 +99,12 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "equivalent" version = "1.0.2" @@ -206,6 +212,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "proc-macro2" version = "1.0.104" @@ -232,6 +248,7 @@ dependencies = [ "ariadne", "chumsky", "logos", + "pretty_assertions", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8d6ae4e..1f26477 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,6 @@ edition = "2024" ariadne = { version = "0.6.0", features = ["auto-color"]} chumsky = { version = "0.12.0", features = ["pratt"] } logos = "0.16.0" + +[dev-dependencies] +pretty_assertions = "1.4.1" diff --git a/README.md b/README.md index a763a9f..604ce3f 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Values: `double x = x * 2` ```rust double = fn([x] [x]) ( - ^x = x * 2; + ^x := x * 2; ); ``` @@ -22,7 +22,7 @@ double = fn([x] [x]) ( ```rust ;; same as apply = fn([f x] [x]) ( ... ) fn apply([f x] [x]) ( - ^x = f x; + ^x := f x; ); ;; f would have to be like fn([a] [a]) ( ... ) @@ -37,10 +37,10 @@ fn apply([f x] [x]) ( ``` ```rust fn make_counter([] [inc]) ( - i = 0; - ^inc = fn([] [i]) ( - i = i + 1; - ^i = i; + i := 0; + ^inc := fn([] [i]) ( + i := i + 1; + ^i := i; ); ;; or fn ^inc(...) @@ -51,20 +51,20 @@ fn make_counter([] [inc]) ( ```rust ;; omitting the return means it's the same fn desat([h s l a]) ( - ^s = ^s / 2; + ^s := ^s / 2; ) ``` `compose f g x = f (g x)` ```rust fn compose([f g x] [x]) ( - ^x = g x; - ^x = f x; + ^x := g x; + ^x := f x; ) -f = fn([x]) (^x = x + 2;); -g = fn([x]) (^x = x * 3;); -f2 = f; +f := fn([x]) (^x := x + 2;); +g := fn([x]) (^x := x * 3;); +f2 := f; compose f g x == (((compose f) g) x) == compose {f: f2, g} x == compose {x} f g ``` diff --git a/src/ast.rs b/src/ast.rs index 5d300eb..24a65de 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -85,13 +85,17 @@ impl fmt::Display for Ast { body, } => { let body: Vec<_> = body.iter().map(|stmt| format!("{stmt}")).collect(); - write!( - f, - "(fn ([{}] [{}]) {})", - inputs.join(" "), - outputs.join(" "), - body.join(" ") - ) + if body.is_empty() { + write!(f, "(fn ([{}] [{}]))", inputs.join(" "), outputs.join(" "),) + } else { + write!( + f, + "(fn ([{}] [{}]) {})", + inputs.join(" "), + outputs.join(" "), + body.join(" ") + ) + } } Ast::Map(body) => { let body: Vec<_> = body.iter().map(|stmt| format!("{stmt}")).collect(); diff --git a/src/tests/parser.rs b/src/tests/parser.rs index 67714e3..723e858 100644 --- a/src/tests/parser.rs +++ b/src/tests/parser.rs @@ -1,7 +1,7 @@ use crate::parser::*; fn test(src: &str, expected: &str) { - assert_eq!( + pretty_assertions::assert_eq!( parse(src) .into_iter() .map(|stmt| format!("{stmt}")) @@ -16,10 +16,27 @@ fn double() { test( " fn double([x]) ( - ^x = x * 2; + ^x := x * 2; ) ", - "(set! double (fn ([x] [x]) (^x = (x * 2))))", + " +(set! double (fn ([x] [x]) (set! ^x (x * 2)))) +", + ); +} + +#[test] +fn apply() { + test( + " +# same as apply = fn([f x] [x]) ( ... ) +fn apply([f x] [x]) ( + ^x := f x; +) + +# f would have to be like fn([a] [a]) ( ... ) + ", + "(set! apply (fn ([f x] [x]) (set! ^x (f x))))", ); } @@ -28,15 +45,92 @@ fn make_counter() { test( " fn make_counter([] [inc]) ( - i = 0; - ^inc = fn([] [i]) ( - i = i + 1; - ^i = i; + i := 0; + ^inc := fn([] [i]) ( + i := i + 1; + ^i := i; ); # or fn ^inc(...) ) ", - "(set! make_counter (fn ([] [inc]) (i = 0) (^inc = (fn ([] [i]) (i = (i + 1)) (^i = i)))))", + " +(set! make_counter \ + (fn ([] [inc]) \ + (set! i 0) \ + (set! ^inc (fn ([] [i]) \ + (set! i (i + 1)) \ + (set! ^i i))))) +", + ); +} + +#[test] +fn desat() { + test( + " +fn desat([h s l a]) ( + ^s := ^s / 2; +) +", + "(set! desat (fn ([h s l a] [h s l a]) (set! ^s (^s / 2))))", + ); +} + +#[test] +fn compose() { + test( + " +fn compose([f g x] [x]) ( + ^x := g x; + ^x := f x; +); + +f := fn([x]) (^x := x + 2;); +g := fn([x]) (^x := x * 3;); +f2 := f; + +compose f g x = (((compose f) g) x); +compose f g x = compose {^f := f2; ^g} x; +compose f g x = compose {^x} f g; +", + " +(set! compose (fn ([f g x] [x]) (set! ^x (g x)) (set! ^x (f x)))); +(set! f (fn ([x] [x]) (set! ^x (x + 2)))); +(set! g (fn ([x] [x]) (set! ^x (x * 3)))); +(set! f2 f); +((((compose f) g) x) = ((((((compose f)) g)) x))); +((((compose f) g) x) = ((compose {(set! ^f f2) ^g}) x)); +((((compose f) g) x) = (((compose {^x}) f) g)) + ", + ); +} + +#[test] +fn id() { + test("fn id([x]) ()", "(set! id (fn ([x] [x])))"); +} + +#[test] +fn map() { + test( + " +foo := 3; +{ + x := 42; + ^twice_x := x * 2; + ^thrice_x := x * 3; + ^foo; +} +", + " +(set! foo 3); +{\ + (set! x 42) \ + (set! ^twice_x (x * 2)) \ + (set! ^thrice_x (x * 3)) \ + ^foo\ +} +", ); }