From 4c7692448b5d67a5da9fa95215f90b693d5de8b6 Mon Sep 17 00:00:00 2001 From: Greg Hawkins Date: Sun, 27 Feb 2022 14:48:50 +0000 Subject: [PATCH] EDN import and export (#178) Adds EDN import and export. - feature: `eu` now has experimental support import of `.edn` files and export to edn (using `-x edn`). Currently only single top-level forms are supported so, for example, reading streamed logs in edn is not possible. See https://github.com/edn-format/edn for the EDN spec. --- Cargo.lock | 58 ++++++++++++++++ Cargo.toml | 2 + benches/alloc.rs | 49 ++++---------- harness/test/050_edn.edn | 5 ++ src/core/anaphora.rs | 13 ++-- src/core/cook/fill.rs | 2 +- src/core/cook/fixity.rs | 12 ++-- src/core/cook/mod.rs | 72 +++++++------------- src/core/desugar/ast.rs | 48 +++++++------- src/core/export/embed.rs | 13 ++-- src/core/export/pretty.rs | 2 +- src/core/expr.rs | 76 ++++++++------------- src/core/inline/reduce.rs | 50 +++++--------- src/core/inline/tag.rs | 4 +- src/core/simplify/compress.rs | 13 ++-- src/core/simplify/prune.rs | 28 ++++---- src/core/transform/dynamise.rs | 10 +-- src/driver/error.rs | 3 - src/driver/options.rs | 2 + src/driver/source.rs | 117 ++++++++++----------------------- src/driver/tester.rs | 2 +- src/eval/machine/vm.rs | 25 ++----- src/eval/memory/syntax.rs | 4 +- src/eval/stg/compiler.rs | 22 +++---- src/eval/stg/eq.rs | 4 +- src/eval/stg/testing.rs | 2 +- src/export/edn.rs | 71 ++++++++++++++++++++ src/export/mod.rs | 3 + src/import/edn.rs | 110 +++++++++++++++++++++++++++++++ src/import/error.rs | 17 ++++- src/import/mod.rs | 32 +++++++++ src/import/yaml.rs | 2 +- src/syntax/input.rs | 1 + tests/harness_test.rs | 15 +++++ 34 files changed, 522 insertions(+), 367 deletions(-) create mode 100644 harness/test/050_edn.edn create mode 100644 src/export/edn.rs create mode 100644 src/import/edn.rs diff --git a/Cargo.lock b/Cargo.lock index e9c4d6f..8c2591d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,6 +63,17 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bigdecimal" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aaf33151a6429fe9211d1b276eafdf70cdff28b071e76c0b0e1503221ea3744" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "bit-set" version = "0.5.2" @@ -347,6 +358,22 @@ dependencies = [ "winapi", ] +[[package]] +name = "edn-format" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c462f3c16bda8581446b1432b3182e8f2964dd5fcf900e7715cb0543898540" +dependencies = [ + "bigdecimal", + "chrono", + "internship", + "itertools", + "num-bigint", + "ordered-float", + "thiserror", + "uuid", +] + [[package]] name = "either" version = "1.6.1" @@ -376,6 +403,7 @@ dependencies = [ "criterion", "csv", "dirs", + "edn-format", "html5ever", "indexmap", "itertools", @@ -385,6 +413,7 @@ dependencies = [ "lru", "matches", "moniker", + "ordered-float", "petgraph 0.6.0", "pretty", "pretty-hex", @@ -532,6 +561,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "internship" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75b80c06d9124692b2927086ed75c8721d4061f9c159d9675d3f6d63729b597" +dependencies = [ + "serde", +] + [[package]] name = "itertools" version = "0.10.3" @@ -706,6 +744,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -747,6 +796,15 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "ordered-float" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +dependencies = [ + "num-traits", +] + [[package]] name = "parking_lot" version = "0.11.2" diff --git a/Cargo.toml b/Cargo.toml index 5785bec..c809b53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,8 @@ uuid = { version = "0.8.2", features = ["serde", "v4"] } webbrowser = "0.5.5" bitmaps = "3.1.0" pretty-hex = "0.2.1" +edn-format = "3.2.2" +ordered-float = "2.10.0" [[bench]] harness = false diff --git a/benches/alloc.rs b/benches/alloc.rs index 3a5d3a5..a479a0d 100644 --- a/benches/alloc.rs +++ b/benches/alloc.rs @@ -22,7 +22,7 @@ use eucalypt::{ use criterion::{black_box, criterion_group, criterion_main, Criterion}; -fn box_one<'guard>(view: MutatorHeapView<'guard>, empty: RefPtr) -> Closure { +fn box_one(view: MutatorHeapView, empty: RefPtr) -> Closure { Closure::new( view.data( DataConstructor::BoxedString.tag(), @@ -34,16 +34,7 @@ fn box_one<'guard>(view: MutatorHeapView<'guard>, empty: RefPtr) -> Cl ) } -fn identity<'guard>(view: MutatorHeapView<'guard>, empty: RefPtr) -> RefPtr { - view.alloc(Closure::close( - &LambdaForm::new(1, view.atom(Ref::L(0)).unwrap().as_ptr(), Smid::default()), - empty, - )) - .unwrap() - .as_ptr() -} - -fn fake_bindings<'guard>(view: MutatorHeapView<'guard>, width: usize) -> Vec { +fn fake_bindings(view: MutatorHeapView, width: usize) -> Vec { iter::repeat_with(|| { LambdaForm::new(1, view.atom(Ref::L(0)).unwrap().as_ptr(), Smid::default()) }) @@ -51,8 +42,8 @@ fn fake_bindings<'guard>(view: MutatorHeapView<'guard>, width: usize) -> Vec( - view: MutatorHeapView<'guard>, +fn fake_env_stack( + view: MutatorHeapView, empty: RefPtr, width: usize, height: usize, @@ -68,8 +59,8 @@ fn fake_env_stack<'guard>( } /// Allocate a letrec of identify function bindings -fn alloc_let<'guard>( - view: MutatorHeapView<'guard>, +fn alloc_let( + view: MutatorHeapView, empty: RefPtr, bindings: &[LambdaForm], ) -> RefPtr { @@ -77,8 +68,8 @@ fn alloc_let<'guard>( } /// Allocate a letrec of identify function bindings -fn alloc_letrec<'guard>( - view: MutatorHeapView<'guard>, +fn alloc_letrec( + view: MutatorHeapView, empty: RefPtr, bindings: &[LambdaForm], ) -> RefPtr { @@ -86,30 +77,21 @@ fn alloc_letrec<'guard>( } /// Access deep closure -fn access<'guard>( - view: MutatorHeapView<'guard>, - env: RefPtr, - depth: usize, -) -> Option { +fn access(view: MutatorHeapView, env: RefPtr, depth: usize) -> Option { let e = view.scoped(env); (*e).get(&view, depth) } /// Update deep closure with a new value -fn update<'guard>( - view: MutatorHeapView<'guard>, - empty: RefPtr, - env: RefPtr, - depth: usize, -) { +fn update(view: MutatorHeapView, empty: RefPtr, env: RefPtr, depth: usize) { let e = view.scoped(env); let value = box_one(view, empty); (*e).update(&view, depth, value).unwrap(); } /// Create an identity lambda and saturate it -fn create_and_saturate_lambda<'guard>(view: MutatorHeapView<'guard>, empty: RefPtr) { - let mut lambda = Closure::close( +fn create_and_saturate_lambda(view: MutatorHeapView, empty: RefPtr) { + let lambda = Closure::close( &LambdaForm::new(1, view.atom(Ref::L(0)).unwrap().as_ptr(), Smid::default()), empty, ); @@ -118,11 +100,8 @@ fn create_and_saturate_lambda<'guard>(view: MutatorHeapView<'guard>, empty: RefP } /// Create an identity lambda and saturate it -fn create_partially_apply_and_saturate_lambda<'guard>( - view: MutatorHeapView<'guard>, - empty: RefPtr, -) { - let mut lambda = Closure::close( +fn create_partially_apply_and_saturate_lambda(view: MutatorHeapView, empty: RefPtr) { + let lambda = Closure::close( &LambdaForm::new(2, view.atom(Ref::L(0)).unwrap().as_ptr(), Smid::default()), empty, ); diff --git a/harness/test/050_edn.edn b/harness/test/050_edn.edn new file mode 100644 index 0000000..1b5e886 --- /dev/null +++ b/harness/test/050_edn.edn @@ -0,0 +1,5 @@ +{:k "foo" + :k1 "bar" + :α :blah + :quux [:x :y :z] + :RESULT :PASS} diff --git a/src/core/anaphora.rs b/src/core/anaphora.rs index 9c0a89e..f34559f 100644 --- a/src/core/anaphora.rs +++ b/src/core/anaphora.rs @@ -227,15 +227,12 @@ pub mod tests { #[test] pub fn test_to_binding_pattern() { - let _0 = free("_0"); - let _1 = free("_1"); + let ana0 = free("_0"); + let ana1 = free("_1"); let mut hm: HashMap, FreeVar> = HashMap::new(); - hm.insert(Anaphor::ExplicitNumbered(0), _0.clone()); - hm.insert(Anaphor::ExplicitNumbered(1), _1.clone()); - assert_eq!( - to_binding_pattern(&hm).unwrap(), - vec![_0.clone(), _1.clone()] - ); + hm.insert(Anaphor::ExplicitNumbered(0), ana0.clone()); + hm.insert(Anaphor::ExplicitNumbered(1), ana1.clone()); + assert_eq!(to_binding_pattern(&hm).unwrap(), vec![ana0, ana1]); } } diff --git a/src/core/cook/fill.rs b/src/core/cook/fill.rs index f32a451..84b392b 100644 --- a/src/core/cook/fill.rs +++ b/src/core/cook/fill.rs @@ -159,7 +159,7 @@ pub mod tests { assert_term_eq!( soup(fill(&[ana.clone(), plus.clone(), var(x.clone())])), - soup(vec![ana.clone(), plus.clone(), var(x.clone())]) + soup(vec![ana, plus, var(x)]) ); } } diff --git a/src/core/cook/fixity.rs b/src/core/cook/fixity.rs index 008d305..6d4f7fc 100644 --- a/src/core/cook/fixity.rs +++ b/src/core/cook/fixity.rs @@ -126,8 +126,8 @@ pub mod tests { ); let expected = let_( - vec![(plus.clone(), bif("FOO")), (minus.clone(), bif("BAR"))], - soup(vec![num(1), infixl(50, var(plus.clone())), num(2)]), + vec![(plus.clone(), bif("FOO")), (minus, bif("BAR"))], + soup(vec![num(1), infixl(50, var(plus)), num(2)]), ); assert_term_eq!(distribute(expr).unwrap(), expected); @@ -149,8 +149,8 @@ pub mod tests { let expected = let_( vec![(plus.clone(), bif("FOO"))], let_( - vec![(minus.clone(), bif("BAR"))], - soup(vec![num(1), infixl(50, var(plus.clone())), num(2)]), + vec![(minus, bif("BAR"))], + soup(vec![num(1), infixl(50, var(plus)), num(2)]), ), ); @@ -171,8 +171,8 @@ pub mod tests { ); let expected = let_( - vec![(plus.clone(), bif("FOO")), (minus.clone(), bif("BAR"))], - soup(vec![num(1), infixr(90, var(minus.clone())), num(2)]), + vec![(plus, bif("FOO")), (minus.clone(), bif("BAR"))], + soup(vec![num(1), infixr(90, var(minus)), num(2)]), ); assert_term_eq!(distribute(expr).unwrap(), expected); diff --git a/src/core/cook/mod.rs b/src/core/cook/mod.rs index a4af564..4b89e00 100644 --- a/src/core/cook/mod.rs +++ b/src/core/cook/mod.rs @@ -163,7 +163,7 @@ pub mod tests { use moniker::assert_term_eq; fn cook_soup(exprs: &[RcExpr]) -> RcExpr { - cook(RcExpr::from(soup(exprs.to_vec()))).unwrap() + cook(soup(exprs.to_vec())).unwrap() } #[test] @@ -178,8 +178,8 @@ pub mod tests { let post100 = core::postfix(Smid::fake(8), 100, bif("POST100")); let pre10 = core::prefix(Smid::fake(9), 10, bif("PRE10")); let post10 = core::postfix(Smid::fake(10), 10, bif("POST10")); - let _0 = free("_0"); - let _1 = free("_1"); + let ana0 = free("_0"); + let ana1 = free("_1"); assert_term_eq!( cook_soup(&[num(5), l50.clone(), num(7)]), @@ -191,7 +191,7 @@ pub mod tests { assert_term_eq!( cook_soup(&[var(x.clone()), var(f.clone())]), - app(var(f.clone()), vec![var(x.clone())]) + app(var(f), vec![var(x)]) ); // associates left @@ -214,15 +214,7 @@ pub mod tests { // respects precedence in left assert_term_eq!( - cook_soup(&[ - num(1), - l40.clone(), - num(2), - l50.clone(), - num(3), - l60.clone(), - num(4) - ]), + cook_soup(&[num(1), l40, num(2), l50.clone(), num(3), l60, num(4)]), app( bif("L40"), vec![ @@ -237,15 +229,7 @@ pub mod tests { // respects precedence in right assert_term_eq!( - cook_soup(&[ - num(1), - r60.clone(), - num(2), - r50.clone(), - num(3), - r40.clone(), - num(4) - ]), + cook_soup(&[num(1), r60, num(2), r50, num(3), r40, num(4)]), app( bif("R40"), vec![ @@ -322,8 +306,8 @@ pub mod tests { assert_term_eq!( cook_soup(&[l50.clone(), num(20)]), lam( - vec![_0.clone()], - app(bif("L50"), vec![var(_0.clone()), num(20)]) + vec![ana0.clone()], + app(bif("L50"), vec![var(ana0.clone()), num(20)]) ) ); @@ -331,8 +315,8 @@ pub mod tests { assert_term_eq!( cook_soup(&[num(20), l50.clone()]), lam( - vec![_0.clone()], - app(bif("L50"), vec![num(20), var(_0.clone())]) + vec![ana0.clone()], + app(bif("L50"), vec![num(20), var(ana0.clone())]) ) ); @@ -340,12 +324,12 @@ pub mod tests { assert_term_eq!( cook_soup(&[pre10.clone(), l50.clone(), num(30), post10.clone()]), lam( - vec![_0.clone()], + vec![ana0.clone()], app( bif("PRE10"), vec![app( bif("POST10"), - vec![app(bif("L50"), vec![var(_0.clone()), num(30)])] + vec![app(bif("L50"), vec![var(ana0.clone()), num(30)])] )] ) ) @@ -354,20 +338,14 @@ pub mod tests { // fills ... (binary) (unary post) ... with anaphor and // abstracts assert_term_eq!( - cook_soup(&[ - num(30), - l50.clone(), - post100.clone(), - pre100.clone(), - num(20) - ]), + cook_soup(&[num(30), l50.clone(), post100, pre100, num(20)]), lam( - vec![_0.clone()], + vec![ana0.clone()], app( app(bif("PRE100"), vec![num(20)]), vec![app( bif("L50"), - vec![num(30), app(bif("POST100"), vec![var(_0.clone())])] + vec![num(30), app(bif("POST100"), vec![var(ana0.clone())])] )] ) ) @@ -377,14 +355,14 @@ pub mod tests { assert_term_eq!( cook_soup(&[pre10.clone(), pre10.clone(), pre10.clone(), pre10.clone()]), lam( - vec![_0.clone()], + vec![ana0.clone()], app( bif("PRE10"), vec![app( bif("PRE10"), vec![app( bif("PRE10"), - vec![app(bif("PRE10"), vec![var(_0.clone())])] + vec![app(bif("PRE10"), vec![var(ana0.clone())])] )] )] ) @@ -393,9 +371,9 @@ pub mod tests { // fills pre10 l50 post10 assert_term_eq!( - cook_soup(&[pre10.clone(), l50.clone(), post10.clone()]), + cook_soup(&[pre10, l50, post10]), lam( - vec![_0.clone(), _1.clone()], + vec![ana0.clone(), ana1.clone()], app( bif("PRE10"), vec![app( @@ -403,8 +381,8 @@ pub mod tests { vec![app( bif("L50"), vec![ - core::var(Smid::fake(11), _0.clone()), - core::var(Smid::fake(12), _1.clone()) + core::var(Smid::fake(11), ana0), + core::var(Smid::fake(12), ana1) ] )] )] @@ -444,17 +422,17 @@ pub mod tests { #[test] pub fn test_anaphoric_operation() { let l50 = core::infixl(Smid::fake(1), 50, bif("MUL")); - let _0 = free("_e_n0"); + let ana0 = free("_e_n0"); assert_term_eq!( cook_soup(&[ core::expr_anaphor(Smid::fake(2), Some(0)), - l50.clone(), + l50, core::expr_anaphor(Smid::fake(2), Some(0)) ]), lam( - vec![_0.clone()], - app(bif("MUL"), vec![var(_0.clone()), var(_0.clone())]) + vec![ana0.clone()], + app(bif("MUL"), vec![var(ana0.clone()), var(ana0)]) ) ); } diff --git a/src/core/desugar/ast.rs b/src/core/desugar/ast.rs index 8d17672..b7a7941 100644 --- a/src/core/desugar/ast.rs +++ b/src/core/desugar/ast.rs @@ -552,13 +552,6 @@ pub mod tests { } impl Fixture { - pub fn new() -> Self { - let source_map = SourceMap::new(); - let files = SimpleFiles::::new(); - - Fixture { source_map, files } - } - pub fn desugar(mut self, ast: impl Desugarable) -> RcExpr { let file_id = self.files.add("".to_string(), "".into()); let mut hm: HashMap = HashMap::new(); @@ -571,25 +564,34 @@ pub mod tests { } } + impl Default for Fixture { + fn default() -> Self { + let source_map = SourceMap::new(); + let files = SimpleFiles::::new(); + + Fixture { source_map, files } + } + } + #[test] pub fn test_literals() { assert_eq!( - Fixture::new().desugar(ast::str("blah")), + Fixture::default().desugar(ast::str("blah")), core::str(Smid::from(1), "blah") ); assert_eq!( - Fixture::new().desugar(ast::sym("blah")), + Fixture::default().desugar(ast::sym("blah")), core::sym(Smid::from(1), "blah") ); assert_eq!( - Fixture::new().desugar(ast::num(900)), + Fixture::default().desugar(ast::num(900)), core::num(Smid::from(1), 900) ); } #[test] pub fn test_inserts_call_operator() { - let fixture = Fixture::new(); + let fixture = Fixture::default(); let soup = vec![ ast::lit(ast::num(5)), @@ -617,7 +619,7 @@ pub mod tests { #[test] pub fn test_handles_iterated_calls() { - let fixture = Fixture::new(); + let fixture = Fixture::default(); let soup = vec![ ast::name(ast::normal("f")), @@ -638,7 +640,7 @@ pub mod tests { #[test] pub fn test_handles_relative_names() { - let fixture = Fixture::new(); + let fixture = Fixture::default(); let soup = vec![ ast::name(ast::normal("x")), @@ -661,7 +663,7 @@ pub mod tests { #[test] pub fn test_creates_var_for_lonely_names() { - let fixture = Fixture::new(); + let fixture = Fixture::default(); let block = ast::block( None, @@ -679,7 +681,7 @@ pub mod tests { #[test] pub fn test_handles_built_ins() { - let fixture = Fixture::new(); + let fixture = Fixture::default(); let block = ast::block( None, @@ -694,7 +696,7 @@ pub mod tests { let core_block = acore::default_let(vec![ (null.clone(), acore::bif("NULL")), - (a.clone(), acore::var(null.clone())), + (a, acore::var(null)), ]); assert_term_eq!(bound(fixture.desugar(block)), bound(core_block)); @@ -715,22 +717,22 @@ pub mod tests { let t = free("t"); let core_expr = acore::soup(vec![ - acore::var(eq.clone()), + acore::var(eq), acore::call(), acore::arg_tuple(vec![ acore::soup(vec![ - acore::var(or.clone()), + acore::var(or), acore::call(), acore::arg_tuple(vec![ - acore::var(f.clone()), + acore::var(f), acore::soup(vec![ - acore::var(and.clone()), + acore::var(and), acore::call(), acore::arg_tuple(vec![acore::var(t.clone()), acore::var(t.clone())]), ]), ]), ]), - acore::var(t.clone()), + acore::var(t), ]), ]); @@ -796,9 +798,9 @@ pub mod tests { let x = FreeVar::fresh_named("x"); let core_expr = acore::soup(vec![ - acore::var(f.clone()), + acore::var(f), acore::call(), - acore::arg_tuple(vec![acore::var(x.clone())]), + acore::arg_tuple(vec![acore::var(x)]), acore::dot(), acore::name("v"), ]); diff --git a/src/core/export/embed.rs b/src/core/export/embed.rs index 2270fb1..105e7c9 100644 --- a/src/core/export/embed.rs +++ b/src/core/export/embed.rs @@ -5,9 +5,12 @@ use crate::syntax::ast::*; use crate::syntax::export::embed::Embed; use crate::syntax::export::pretty; -/// Embed the core expression as AST then render as parse-embed unit. +/// Embed a representation of the core expression in an AST then +/// render as parse-embed unit. /// -/// The core emitted can be consumed by `parse-embed` functionality. +/// The core emitted can be consumed by `parse-embed` functionality +/// which reads such a representation out of the AST to build core, +/// instead of using the normal translation process. pub fn quote_embed_core_unit(expr: &RcExpr) -> String { format!( " {{ parse-embed: :CORE }} @@ -217,9 +220,9 @@ pub mod tests { let x = FreeVar::fresh_named("x"); let core_expr = acore::soup(vec![ - acore::var(f.clone()), + acore::var(f), acore::call(), - acore::arg_tuple(vec![acore::var(x.clone())]), + acore::arg_tuple(vec![acore::var(x)]), acore::dot(), acore::name("v"), ]); @@ -247,7 +250,7 @@ pub mod tests { acore::soup(vec![ acore::var(x.clone()), acore::op(Fixity::InfixLeft, 40, acore::bif("__ADD")), - acore::var(x.clone()), + acore::var(x), ]), ); diff --git a/src/core/export/pretty.rs b/src/core/export/pretty.rs index 59b8626..743196a 100644 --- a/src/core/export/pretty.rs +++ b/src/core/export/pretty.rs @@ -271,7 +271,7 @@ pub mod tests { vec![list(vec![num(1), num(2), num(3)]), bif("HEAD")], ), app( - pseudocat.clone(), + pseudocat, vec![list(vec![num(1), num(2), num(3)]), bif("TAIL")], ), ], diff --git a/src/core/expr.rs b/src/core/expr.rs index d6e73e2..3e1a0c8 100644 --- a/src/core/expr.rs +++ b/src/core/expr.rs @@ -345,7 +345,7 @@ where /// Block (in contrast to the haskell implementation we're storing /// an ordered record right here) Block(Smid, BlockMap), - /// Metadata annotation (span, expr, meta) - TODO: struct? + /// Metadata annotation (span, expr, meta) Meta(Smid, T, T), /// Tuple of arguments to apply ArgTuple(Smid, Vec), @@ -880,10 +880,12 @@ where } } -/// Extract data from an expression using only basic inline / +/// Extract data from a core expression. +/// +/// Implementations are used to read processing-time metadata from +/// core representations and therefore using only basic inline / /// substitution techniques such as are available during core -/// processing and transformation. These require mutable self as -/// transformations applied are kept. +/// processing and transformation. pub trait Extract { fn extract(&self) -> Option; } @@ -1403,8 +1405,8 @@ pub mod tests { assert_term_eq!( lam(vec![a.clone(), b.clone(), c.clone()], var(d.clone())) - .substs(&[(d.clone(), var(e.clone()))]), - lam(vec![a.clone(), b.clone(), c.clone()], var(e.clone())), + .substs(&[(d, var(e.clone()))]), + lam(vec![a, b, c], var(e)), ); } @@ -1421,8 +1423,8 @@ pub mod tests { let sub = |n: &str| if n == "d" { Some(var(e.clone())) } else { None }; assert_term_eq!( - lam(vec![a.clone(), b.clone(), c.clone()], var(d.clone())).substs_free(&sub), - lam(vec![a.clone(), b.clone(), c.clone()], var(e.clone())), + lam(vec![a.clone(), b.clone(), c.clone()], var(d)).substs_free(&sub), + lam(vec![a, b, c], var(e)), ); } @@ -1434,12 +1436,9 @@ pub mod tests { let y = free("y"); let original = default_let(vec![(x.clone(), num(22)), (y.clone(), num(23))]); - let expected = let_( - vec![(x.clone(), num(22)), (y.clone(), num(23))], - var(y.clone()), - ); + let expected = let_(vec![(x, num(22)), (y.clone(), num(23))], var(y.clone())); - assert_term_eq!(original.rebody(var(y.clone())), expected); + assert_term_eq!(original.rebody(var(y)), expected); } #[test] @@ -1455,11 +1454,11 @@ pub mod tests { default_let(vec![(z.clone(), num(24))]), ); let expected = let_( - vec![(x.clone(), num(22)), (y.clone(), num(23))], - let_(vec![(z.clone(), num(24))], var(x.clone())), + vec![(x.clone(), num(22)), (y, num(23))], + let_(vec![(z, num(24))], var(x.clone())), ); - assert_term_eq!(original.rebody(var(x.clone())), expected); + assert_term_eq!(original.rebody(var(x)), expected); } #[test] @@ -1530,9 +1529,9 @@ pub mod tests { vec![ (a.clone(), num(1)), (b.clone(), var(a.clone())), - (c.clone(), var(y_other.clone())), + (c.clone(), var(y_other)), ], - var(x_other.clone()), + var(x_other), ); let unit_c = unit_a.merge_in(unit_b); @@ -1540,15 +1539,8 @@ pub mod tests { let expected = let_( vec![(x.clone(), num(22)), (y.clone(), var(x.clone()))], let_( - vec![(z.clone(), num(24))], - let_( - vec![ - (a.clone(), num(1)), - (b.clone(), var(a.clone())), - (c.clone(), var(y.clone())), - ], - var(x.clone()), - ), + vec![(z, num(24))], + let_(vec![(a.clone(), num(1)), (b, var(a)), (c, var(y))], var(x)), ), ); @@ -1579,25 +1571,18 @@ pub mod tests { vec![ (a.clone(), num(1)), (b.clone(), var(a.clone())), - (c.clone(), var(y_other.clone())), + (c.clone(), var(y_other)), ], - var(x_other.clone()), + var(x_other), ); let unit_c = unit_a.merge_in(unit_b); let expected = let_( - vec![(x.clone(), num(22)), (y.clone(), var(a_other.clone()))], + vec![(x.clone(), num(22)), (y.clone(), var(a_other))], let_( - vec![(z.clone(), num(24))], - let_( - vec![ - (a.clone(), num(1)), - (b.clone(), var(a.clone())), - (c.clone(), var(y.clone())), - ], - var(x.clone()), - ), + vec![(z, num(24))], + let_(vec![(a.clone(), num(1)), (b, var(a)), (c, var(y))], var(x)), ), ); @@ -1626,23 +1611,20 @@ pub mod tests { ); let unit_b = let_( - vec![(a.clone(), num(2)), (b.clone(), var(x_other.clone()))], + vec![(a.clone(), num(2)), (b.clone(), var(x_other))], var(a.clone()), ); let unit_c = let_( - vec![(m.clone(), num(3)), (n.clone(), var(b_other.clone()))], - var(y_other.clone()), + vec![(m.clone(), num(3)), (n.clone(), var(b_other))], + var(y_other), ); let expected = let_( vec![(x.clone(), num(1)), (y.clone(), var(x.clone()))], let_( - vec![(a.clone(), num(2)), (b.clone(), var(x.clone()))], - let_( - vec![(m.clone(), num(3)), (n.clone(), var(b.clone()))], - var(y.clone()), - ), + vec![(a, num(2)), (b.clone(), var(x))], + let_(vec![(m, num(3)), (n, var(b))], var(y)), ), ); diff --git a/src/core/inline/reduce.rs b/src/core/inline/reduce.rs index 2cff17e..2f87bcc 100644 --- a/src/core/inline/reduce.rs +++ b/src/core/inline/reduce.rs @@ -115,13 +115,7 @@ pub mod tests { app(var(f.clone()), vec![num(22), num(23)]), ); - let expected = let_( - vec![( - f.clone(), - inline(vec![x.clone(), y.clone()], var(y.clone())), - )], - num(23), - ); + let expected = let_(vec![(f, inline(vec![x, y.clone()], var(y)))], num(23)); assert_term_eq!(inline_pass(&original).unwrap(), expected); } @@ -167,12 +161,9 @@ pub mod tests { ); let expected = let_( vec![ + (z.clone(), app(var(compose.clone()), vec![var(n), var(m)])), ( - z.clone(), - app(var(compose.clone()), vec![var(n.clone()), var(m.clone())]), - ), - ( - compose.clone(), + compose, inline( vec![f.clone(), g.clone(), x.clone()], app( @@ -182,23 +173,20 @@ pub mod tests { ), ), ( - r.clone(), + r, inline( vec![j.clone(), k.clone()], app( inline( vec![f.clone(), g.clone(), x.clone()], - app( - var(f.clone()), - vec![app(var(g.clone()), vec![var(x.clone())])], - ), + app(var(f), vec![app(var(g), vec![var(x)])]), ), - vec![var(j.clone()), var(k.clone())], + vec![var(j), var(k)], ), ), ), ], - var(z.clone()), + var(z), ); assert_term_eq!(inline_pass(&original).unwrap(), expected); @@ -281,22 +269,19 @@ pub mod tests { let_( vec![( d.clone(), - app( - var(compose.clone()), - vec![var(n.clone()), var(m.clone())], - ), + app(var(compose.clone()), vec![var(n), var(m)]), )], - block(iter::once(("d".to_string(), var(d.clone())))), + block(iter::once(("d".to_string(), var(d)))), ), )], - block(iter::once(("c".to_string(), var(c.clone())))), + block(iter::once(("c".to_string(), var(c)))), ), )], - block(iter::once(("b".to_string(), var(b.clone())))), + block(iter::once(("b".to_string(), var(b)))), ), ), ( - compose.clone(), + compose, inline( vec![f.clone(), g.clone(), x.clone()], app( @@ -306,23 +291,20 @@ pub mod tests { ), ), ( - r.clone(), + r, inline( vec![j.clone(), k.clone()], app( inline( vec![f.clone(), g.clone(), x.clone()], - app( - var(f.clone()), - vec![app(var(g.clone()), vec![var(x.clone())])], - ), + app(var(f), vec![app(var(g), vec![var(x)])]), ), - vec![var(j.clone()), var(k.clone())], + vec![var(j), var(k)], ), ), ), ], - var(a.clone()), + var(a), ); let inlined = inline_pass(&original).unwrap(); diff --git a/src/core/inline/tag.rs b/src/core/inline/tag.rs index 3f49dc2..1ca809f 100644 --- a/src/core/inline/tag.rs +++ b/src/core/inline/tag.rs @@ -64,10 +64,10 @@ pub mod tests { f.clone(), inline( vec![y.clone(), z.clone()], - app(bif("F"), vec![var(y.clone()), var(z.clone())]), + app(bif("F"), vec![var(y), var(z)]), ), )], - app(var(f.clone()), vec![num(22), num(23)]), + app(var(f), vec![num(22), num(23)]), ); assert_term_eq!(tag_combinators(&original).unwrap(), expected); diff --git a/src/core/simplify/compress.rs b/src/core/simplify/compress.rs index 3435434..41810f8 100644 --- a/src/core/simplify/compress.rs +++ b/src/core/simplify/compress.rs @@ -153,23 +153,20 @@ pub mod tests { ( a.clone(), let_( - vec![ - (d.clone(), RcExpr::from(Expr::ErrEliminated)), - (e.clone(), num(2)), - ], + vec![(d, RcExpr::from(Expr::ErrEliminated)), (e.clone(), num(2))], var(e.clone()), ), ), - (b.clone(), RcExpr::from(Expr::ErrEliminated)), + (b, RcExpr::from(Expr::ErrEliminated)), ], var(a.clone()), ); let expected = let_( - vec![(a.clone(), let_(vec![(e.clone(), num(2))], var(e.clone())))], - var(a.clone()), + vec![(a.clone(), let_(vec![(e.clone(), num(2))], var(e)))], + var(a), ); - assert_term_eq!(compress(&sample.clone()).unwrap(), expected); + assert_term_eq!(compress(&sample).unwrap(), expected); } } diff --git a/src/core/simplify/prune.rs b/src/core/simplify/prune.rs index 4884dc1..f48c03e 100644 --- a/src/core/simplify/prune.rs +++ b/src/core/simplify/prune.rs @@ -354,14 +354,11 @@ pub mod tests { ); let expected = let_( - vec![ - (x.clone(), num(1)), - (y.clone(), RcExpr::from(Expr::ErrEliminated)), - ], - var(x.clone()), + vec![(x.clone(), num(1)), (y, RcExpr::from(Expr::ErrEliminated))], + var(x), ); - assert_term_eq!(prune(&simple.clone()), expected) + assert_term_eq!(prune(&simple), expected) } #[test] @@ -380,20 +377,17 @@ pub mod tests { ); let expected = let_( - vec![ - (x.clone(), num(1)), - (y.clone(), RcExpr::from(Expr::ErrEliminated)), - ], + vec![(x.clone(), num(1)), (y, RcExpr::from(Expr::ErrEliminated))], let_( vec![ - (z.clone(), RcExpr::from(Expr::ErrEliminated)), - (v.clone(), RcExpr::from(Expr::ErrEliminated)), + (z, RcExpr::from(Expr::ErrEliminated)), + (v, RcExpr::from(Expr::ErrEliminated)), ], - var(x.clone()), + var(x), ), ); - assert_term_eq!(prune(&nested.clone()), expected) + assert_term_eq!(prune(&nested), expected) } #[test] @@ -417,7 +411,7 @@ pub mod tests { (b.clone(), num(2)), (c, RcExpr::from(Expr::ErrEliminated)), ], - app(bif("BLAH"), vec![var(a), var(b.clone())]), + app(bif("BLAH"), vec![var(a), var(b)]), ); assert_term_eq!(prune(&expr), expected) @@ -453,9 +447,9 @@ pub mod tests { var(e), ), ), - (b.clone(), RcExpr::from(Expr::ErrEliminated)), + (b, RcExpr::from(Expr::ErrEliminated)), ], - var(a.clone()), + var(a), ); assert_term_eq!(prune(&expr), expected) diff --git a/src/core/transform/dynamise.rs b/src/core/transform/dynamise.rs index 0cd76e0..fce39e4 100644 --- a/src/core/transform/dynamise.rs +++ b/src/core/transform/dynamise.rs @@ -132,7 +132,7 @@ pub mod tests { let original = var(x.clone()); let expected = lam( vec![implicit.clone()], - lookup(var(implicit.clone()), "x", Some(var(x.clone()))), + lookup(var(implicit), "x", Some(var(x))), ); assert_term_eq!(dynamise(&original).unwrap(), expected); @@ -146,8 +146,8 @@ pub mod tests { let implicit = free("implicit"); let original = let_( - vec![(x.clone(), num(22)), (y.clone(), num(23))], - let_(vec![(z.clone(), num(24))], var(x.clone())), + vec![(x.clone(), num(22)), (y, num(23))], + let_(vec![(z.clone(), num(24))], var(x)), ); assert_term_eq!(dynamise(&original).unwrap(), original); @@ -157,9 +157,9 @@ pub mod tests { let expected = lam( vec![implicit.clone()], let_( - vec![(z.clone(), num(24))], + vec![(z, num(24))], lookup( - var(implicit.clone()), + var(implicit), "x", Some(RcExpr::from(Expr::Var( Smid::default(), diff --git a/src/driver/error.rs b/src/driver/error.rs index fdad4d3..630b0a5 100644 --- a/src/driver/error.rs +++ b/src/driver/error.rs @@ -7,7 +7,6 @@ use crate::syntax::error::ParserError; use crate::syntax::error::SyntaxError; use crate::syntax::import::ImportError; use codespan_reporting::diagnostic::Diagnostic; -use codespan_reporting::files; use std::fmt::Display; use std::io; use thiserror::Error; @@ -28,8 +27,6 @@ pub enum EucalyptError { Execution(#[from] ExecutionError), #[error(transparent)] Io(#[from] io::Error), - #[error(transparent)] - Files(#[from] files::Error), #[error("unknown resource {0}")] UnknownResource(String), #[error("path {0} could not be read")] diff --git a/src/driver/options.rs b/src/driver/options.rs index 27c4e03..1cddc20 100644 --- a/src/driver/options.rs +++ b/src/driver/options.rs @@ -498,6 +498,8 @@ impl EucalyptOptions { Some("yaml") | Some("yml") => Some("yaml".to_string()), Some("json") => Some("json".to_string()), Some("txt") => Some("text".to_string()), + Some("edn") => Some("edn".to_string()), + Some("toml") => Some("toml".to_string()), _ => None, }; self.export_type = format; diff --git a/src/driver/source.rs b/src/driver/source.rs index d3c5408..242daa8 100644 --- a/src/driver/source.rs +++ b/src/driver/source.rs @@ -1,3 +1,4 @@ +use crate::common::sourcemap::*; use crate::core::cook; use crate::core::desugar::{Content, Desugarer}; use crate::core::error::CoreError; @@ -10,17 +11,16 @@ use crate::core::unit::TranslationUnit; use crate::core::verify::content; use crate::driver::error::EucalyptError; use crate::driver::resources::Resources; -use crate::import::{csv, text, xml, yaml}; +use crate::import::read_to_core; use crate::syntax::ast::*; use crate::syntax::import::ImportGraph; use crate::syntax::input::Input; use crate::syntax::input::Locator; use crate::syntax::parser; -use crate::{common::sourcemap::*, import::toml}; +use codespan_reporting::diagnostic::Diagnostic; use codespan_reporting::files::SimpleFiles; use codespan_reporting::term::emit; use codespan_reporting::term::termcolor::{ColorChoice, NoColor, StandardStream}; -use codespan_reporting::{diagnostic::Diagnostic, files::Files}; use std::io::{self, Read}; use std::path::PathBuf; use std::{collections::HashMap, path::Path}; @@ -104,17 +104,31 @@ impl SourceLoader { /// Load an input (and transitive imports) pub fn load(&mut self, input: &Input) -> Result { - match input.format() { - "yaml" | "json" => self.load_yaml(input), // direct to core - "toml" => self.load_toml(input), - "text" => self.load_text(input), - "csv" => self.load_csv(input), - "xml" => self.load_xml(input), - "core" => self.load_core(input), // load pseudoblock - _ => self.load_tree(input), // to ASTs needing desugaring + let fmt = input.format(); + + if fmt == "eu" { + self.load_tree(input) + } else if fmt == "core" { + self.load_core(input) + } else { + self.load_simple(input) } } + /// Loads from a data format that does not support imports + fn load_simple(&mut self, input: &Input) -> Result { + let file_id = self.load_source(input.locator())?; + let core = read_to_core( + input.format(), + &mut self.files, + &mut self.source_map, + file_id, + )?; + self.imports.add_leaf(input.clone())?; + self.cores.insert(input.clone(), core); + Ok(file_id) + } + /// Load and parse import-graph of eucalypt files starting with the one /// specified by locator fn load_tree(&mut self, input: &Input) -> Result { @@ -152,60 +166,6 @@ impl SourceLoader { Ok(id) } - /// Load YAML - /// - /// YAML is a special case because of available eu embeddings. - fn load_yaml(&mut self, input: &Input) -> Result { - let file_id = self.load_source(input.locator())?; - // the copy is necessary as yaml needs a mutable files db for - // adding embeddings so cannot have immutable ref into it at - // the same time - let text = self.files.source(file_id)?.to_string(); - let core = yaml::read_yaml(&mut self.files, &mut self.source_map, file_id, &text)?; - self.imports.add_leaf(input.clone())?; - self.cores.insert(input.clone(), core); - Ok(file_id) - } - - /// Load TOML - fn load_toml(&mut self, input: &Input) -> Result { - let file_id = self.load_source(input.locator())?; - let text = self.files.source(file_id)?; - let core = toml::read_toml(&mut self.source_map, file_id, text)?; - self.imports.add_leaf(input.clone())?; - self.cores.insert(input.clone(), core); - Ok(file_id) - } - - /// Load text as list of lines - fn load_text(&mut self, input: &Input) -> Result { - let file_id = self.load_source(input.locator())?; - let text = self.files.source(file_id)?; - let core = text::read_text(&mut self.source_map, file_id, text)?; - self.imports.add_leaf(input.clone())?; - self.cores.insert(input.clone(), core); - Ok(file_id) - } - - /// Load text as list of lines - fn load_csv(&mut self, input: &Input) -> Result { - let file_id = self.load_source(input.locator())?; - let text = self.files.source(file_id)?; - let core = csv::read_csv(&mut self.source_map, file_id, text)?; - self.imports.add_leaf(input.clone())?; - self.cores.insert(input.clone(), core); - Ok(file_id) - } - - fn load_xml(&mut self, input: &Input) -> Result { - let file_id = self.load_source(input.locator())?; - let text = self.files.source(file_id)?; - let core = xml::read_xml(&mut self.source_map, file_id, text)?; - self.imports.add_leaf(input.clone())?; - self.cores.insert(input.clone(), core); - Ok(file_id) - } - /// Load __io pseudoblock as core fn load_core(&mut self, input: &Input) -> Result { let file_id = self.load_source(input.locator())?; @@ -467,12 +427,9 @@ pub mod tests { ]); for f in eu_paths() { - match loader.load_eucalypt(&Locator::Fs(f.clone())) { - Err(e) => { - let diag = loader.diagnose_to_string(&e.to_diagnostic(loader.source_map())); - panic!("Failed to parse {:?}.\n{}", &f, diag); - } - Ok(_) => {} + if let Err(e) = loader.load_eucalypt(&Locator::Fs(f.clone())) { + let diag = loader.diagnose_to_string(&e.to_diagnostic(loader.source_map())); + panic!("Failed to parse {:?}.\n{}", &f, diag); } } } @@ -484,20 +441,14 @@ pub mod tests { for f in eu_paths() { let loc = Locator::Fs(f.clone()); - match loader.load_tree(&Input::from(loc.clone())) { - Err(e) => { - let diag = loader.diagnose_to_string(&e.to_diagnostic(loader.source_map())); - panic!("Failed to parse {:?}.\n{}", &f, diag); - } - Ok(_) => {} + if let Err(e) = loader.load_tree(&Input::from(loc.clone())) { + let diag = loader.diagnose_to_string(&e.to_diagnostic(loader.source_map())); + panic!("Failed to parse {:?}.\n{}", &f, diag); } - match loader.translate(&Input::from(loc.clone())) { - Err(e) => { - let diag = loader.diagnose_to_string(&e.to_diagnostic(loader.source_map())); - panic!("Failed to desugar {:?}.\n{}", &f, diag); - } - Ok(_) => {} + if let Err(e) = loader.translate(&Input::from(loc.clone())) { + let diag = loader.diagnose_to_string(&e.to_diagnostic(loader.source_map())); + panic!("Failed to desugar {:?}.\n{}", &f, diag); }; } } diff --git a/src/driver/tester.rs b/src/driver/tester.rs index cedb5d2..865b538 100644 --- a/src/driver/tester.rs +++ b/src/driver/tester.rs @@ -125,7 +125,7 @@ fn directory_plans( .filter(|f| { matches!( f.path().extension().and_then(|s| s.to_str()), - Some("eu") | Some("yaml") | Some("json") + Some("eu") | Some("yaml") | Some("json") | Some("toml") | Some("edn") ) }) .map(|f| f.path()) diff --git a/src/eval/machine/vm.rs b/src/eval/machine/vm.rs index 6ab139a..e3ee2c2 100644 --- a/src/eval/machine/vm.rs +++ b/src/eval/machine/vm.rs @@ -813,7 +813,7 @@ impl<'a> Machine<'a> { if self.state.terminated() { if let HeapSyn::Atom { evaluand: r } = &*code { - self.nav().resolve_native(&r).ok() + self.nav().resolve_native(r).ok() } else { None } @@ -831,12 +831,8 @@ impl<'a> Machine<'a> { if self.state.terminated() { if let HeapSyn::Atom { evaluand: r } = &*code { - if let Ok(n) = self.nav().resolve_native(&r) { - if let Native::Str(rp) = n { - Some((*view.scoped(rp)).as_str().to_string()) - } else { - None - } + if let Ok(Native::Str(rp)) = self.nav().resolve_native(r) { + Some((*view.scoped(rp)).as_str().to_string()) } else { None } @@ -879,11 +875,7 @@ impl<'a> Machine<'a> { if self.state.terminated() { if let HeapSyn::Cons { tag, .. } = &*code { - if *tag == DataConstructor::Unit.tag() { - true - } else { - false - } + *tag == DataConstructor::Unit.tag() } else { false } @@ -955,14 +947,7 @@ pub mod tests { fn machine(syn: Rc) -> Machine<'static> { let mut m = Machine::new(Box::new(DebugEmitter::default()), true); let blank = m.mutate(Init, ()).unwrap(); - let closure = m - .mutate( - Load { - syntax: syn.clone(), - }, - blank, - ) - .unwrap(); + let closure = m.mutate(Load { syntax: syn }, blank).unwrap(); m.initialise(blank, blank, closure, vec![]).unwrap(); m } diff --git a/src/eval/memory/syntax.rs b/src/eval/memory/syntax.rs index a3582da..88ccadb 100644 --- a/src/eval/memory/syntax.rs +++ b/src/eval/memory/syntax.rs @@ -577,13 +577,13 @@ pub mod tests { view.nil().unwrap(); let id = LambdaForm::new(1, view.atom(Ref::L(0)).unwrap().as_ptr(), Smid::default()); view.let_( - view.singleton(id.clone()), + view.singleton(id), view.app(Ref::L(0), view.singleton(view.sym_ref("foo").unwrap())) .unwrap(), ) .unwrap(); view.letrec( - view.singleton(id.clone()), + view.singleton(id), view.app(Ref::L(0), view.singleton(view.sym_ref("foo").unwrap())) .unwrap(), ) diff --git a/src/eval/stg/compiler.rs b/src/eval/stg/compiler.rs index d84b835..cbd43aa 100644 --- a/src/eval/stg/compiler.rs +++ b/src/eval/stg/compiler.rs @@ -1095,8 +1095,8 @@ pub mod tests { let y = free("y"); let core = acore::let_( - vec![(x.clone(), acore::num(2)), (y.clone(), acore::num(3))], - acore::var(x.clone()), + vec![(x.clone(), acore::num(2)), (y, acore::num(3))], + acore::var(x), ); assert_eq!( @@ -1121,11 +1121,11 @@ pub mod tests { vec![ ( x.clone(), - acore::let_(vec![(z.clone(), acore::num(2))], acore::var(y.clone())), + acore::let_(vec![(z, acore::num(2))], acore::var(y.clone())), ), - (y.clone(), acore::num(3)), + (y, acore::num(3)), ], - acore::var(x.clone()), + acore::var(x), ); assert_eq!( @@ -1153,19 +1153,13 @@ pub mod tests { let core = acore::let_( vec![ - ( - f.clone(), - acore::lam(vec![x.clone(), y.clone()], acore::var(x.clone())), - ), + (f.clone(), acore::lam(vec![x.clone(), y], acore::var(x))), ( z.clone(), - acore::app( - acore::var(f.clone()), - vec![acore::num(999), acore::num(-999)], - ), + acore::app(acore::var(f), vec![acore::num(999), acore::num(-999)]), ), ], - acore::var(z.clone()), + acore::var(z), ); let expected = dsl::letrec_( diff --git a/src/eval/stg/eq.rs b/src/eval/stg/eq.rs index d11dc9d..ae9940d 100644 --- a/src/eval/stg/eq.rs +++ b/src/eval/stg/eq.rs @@ -251,8 +251,8 @@ pub mod tests { pub fn test_box_nums() { let syntax = letrec_( vec![ - value(box_num(Number::from_f64(3.14159265).unwrap())), - value(box_num(Number::from_f64(3.14159265).unwrap())), + value(box_num(Number::from_f64(std::f64::consts::PI).unwrap())), + value(box_num(Number::from_f64(std::f64::consts::PI).unwrap())), ], Eq.global(lref(0), lref(1)), ); diff --git a/src/eval/stg/testing.rs b/src/eval/stg/testing.rs index 1759ed1..bddb154 100644 --- a/src/eval/stg/testing.rs +++ b/src/eval/stg/testing.rs @@ -41,7 +41,7 @@ pub fn runtime(mut bifs: Vec>) -> Box { /// Create a machine for standard unit tests equipped with the /// specified runtime -pub fn machine<'r>(runtime: &'r dyn Runtime, syntax: Rc) -> Machine<'r> { +pub fn machine(runtime: &dyn Runtime, syntax: Rc) -> Machine { standard_machine( &SETTINGS, syntax, diff --git a/src/export/edn.rs b/src/export/edn.rs new file mode 100644 index 0000000..5d98c92 --- /dev/null +++ b/src/export/edn.rs @@ -0,0 +1,71 @@ +//! EDN export + +use super::table::{AsKey, FromPairs, FromPrimitive, FromVec, TableAccumulator}; +use crate::eval::emit::{Emitter, Event, RenderMetadata}; +use crate::eval::primitive::Primitive; +use edn_format::{emit_str, Keyword, Value}; +use ordered_float::OrderedFloat; +use std::io::Write; + +impl AsKey for Value { + fn as_key(&self) -> Value { + self.clone() + } +} + +impl FromPrimitive for Value { + fn from_primitive(_metadata: RenderMetadata, primitive: &Primitive) -> Self { + match primitive { + Primitive::Null => Value::Nil, + Primitive::Bool(b) => Value::Boolean(*b), + Primitive::Sym(s) => Value::Keyword(Keyword::from_name(s)), + Primitive::Str(s) => Value::String(s.clone()), + Primitive::Num(n) => { + if let Some(i) = n.as_i64() { + Value::Integer(i) + } else if let Some(f) = n.as_f64() { + Value::Float(OrderedFloat(f)) + } else { + panic!("unrepresentable number") + } + } + Primitive::ZonedDateTime(dt) => Value::Inst(*dt), + } + } +} + +impl FromVec for Value { + fn from_vec(_metadata: RenderMetadata, slice: Vec) -> Self { + Value::Vector(slice) + } +} + +impl FromPairs for Value { + fn from_pairs(_metadata: RenderMetadata, pairs: Vec<(Value, Value)>) -> Self { + Value::Map(pairs.into_iter().collect()) + } +} + +/// Emitter for EDN +pub struct EdnEmitter<'a> { + accum: TableAccumulator, + out: &'a mut (dyn Write + 'a), +} + +impl<'a> EdnEmitter<'a> { + pub fn new(out: &'a mut (dyn Write + 'a)) -> Self { + EdnEmitter { + accum: Default::default(), + out, + } + } +} + +impl<'a> Emitter for EdnEmitter<'a> { + fn emit(&mut self, event: Event) { + self.accum.consume(event); + if let Some(result) = self.accum.result() { + writeln!(self.out, "{}", emit_str(result)).unwrap(); + } + } +} diff --git a/src/export/mod.rs b/src/export/mod.rs index 4fa4c16..3f1d17c 100644 --- a/src/export/mod.rs +++ b/src/export/mod.rs @@ -1,3 +1,4 @@ +pub mod edn; pub mod error; pub mod html; pub mod json; @@ -11,6 +12,7 @@ use self::html::HtmlMarkupSerialiser; use super::export::toml::TomlEmitter; use crate::eval::emit::Emitter; +use edn::EdnEmitter; use html::HtmlEmitter; use json::JsonEmitter; use std::io::Write; @@ -29,6 +31,7 @@ pub fn create_emitter<'a, S: AsRef>( "toml" => Some(Box::new(TomlEmitter::new(output))), "json" => Some(Box::new(JsonEmitter::new(output))), "text" => Some(Box::new(TextEmitter::new(output))), + "edn" => Some(Box::new(EdnEmitter::new(output))), "html" => Some(Box::new(HtmlEmitter::new(HtmlMarkupSerialiser::new( output, )))), diff --git a/src/import/edn.rs b/src/import/edn.rs new file mode 100644 index 0000000..2473633 --- /dev/null +++ b/src/import/edn.rs @@ -0,0 +1,110 @@ +//! EDN import + +use std::iter; + +use super::error::SourceError; +use crate::{ + common::sourcemap::SourceMap, + core::expr::{acore, RcExpr}, +}; +use codespan::Span; +use edn_format::{parse_str, Value}; +use serde_json::Number; + +/// Read EDN format data +/// +/// Note that EDN files may have many top-level items, until we +/// support streaming, we need to read on and return as a list. +pub fn read_edn<'smap>( + _source_map: &'smap mut SourceMap, + file_id: usize, + text: &'smap str, +) -> Result { + let edn = parse_str(text).map_err(|e| SourceError::InvalidEdn(e, file_id))?; + value_to_core(&edn, file_id) +} + +fn value_to_key(edn: &Value, file_id: usize) -> Result { + match edn { + Value::Character(c) => Ok(c.to_string()), + Value::String(s) => Ok(s.to_string()), + Value::Symbol(sym) => Ok(sym.name().to_string()), + Value::Keyword(kw) => Ok(kw.name().to_string()), + v => Err(SourceError::InvalidBlockKey( + v.to_string(), + file_id, + Span::default(), + )), + } +} + +fn value_to_core(edn: &Value, file_id: usize) -> Result { + match edn { + Value::Nil => Ok(acore::null()), + Value::Boolean(b) => Ok(acore::bool_(*b)), + Value::Character(c) => Ok(acore::str(c.to_string())), + Value::String(s) => Ok(acore::str(s)), + Value::Symbol(sym) => Ok(acore::sym(sym.name())), + Value::Keyword(kw) => Ok(acore::sym(kw.name())), + Value::Integer(i) => Ok(acore::num(*i)), + Value::Float(f) => match Number::from_f64(f.into_inner()) { + Some(n) => Ok(acore::num(n)), + None => Err(SourceError::InvalidNumber( + f.to_string(), + file_id, + Span::default(), + )), + }, + Value::BigInt(i) => Err(SourceError::InvalidNumber( + i.to_string(), + file_id, + Span::default(), + )), + Value::BigDec(d) => Err(SourceError::InvalidNumber( + d.to_string(), + file_id, + Span::default(), + )), + Value::List(xs) => xs + .iter() + .map(|v| value_to_core(v, file_id)) + .collect::, SourceError>>() + .map(acore::list), + Value::Vector(xs) => xs + .iter() + .map(|v| value_to_core(v, file_id)) + .collect::, SourceError>>() + .map(acore::list), + Value::Map(m) => m + .iter() + .map( + |(k, v)| match (value_to_key(k, file_id), value_to_core(v, file_id)) { + (Ok(k), Ok(v)) => Ok((k, v)), + (Err(e), _) => Err(e), + (_, Err(e)) => Err(e), + }, + ) + .collect::, SourceError>>() + .map(|entries| acore::block(entries.into_iter())), + Value::Set(xs) => xs + .iter() + .map(|v| value_to_core(v, file_id)) + .collect::, SourceError>>() + .map(acore::list), + Value::Inst(dt) => Ok(acore::app( + acore::bif("ZDT.PARSE"), + vec![acore::str(dt.to_string())], + )), // NB. no core primitive for datetime right now + Value::Uuid(uuid) => Ok(acore::meta( + acore::str(uuid.to_string()), + acore::block(iter::once(( + "tag".to_string(), + acore::str("uuid".to_string()), + ))), + )), + Value::TaggedElement(t, e) => Ok(acore::meta( + value_to_core(e, file_id)?, + acore::block(iter::once(("tag".to_string(), acore::str(t.name())))), + )), + } +} diff --git a/src/import/error.rs b/src/import/error.rs index e37feaf..429c7ff 100644 --- a/src/import/error.rs +++ b/src/import/error.rs @@ -6,7 +6,10 @@ use crate::{ syntax::{error::ParserError, span::HasSpan}, }; use codespan::Span; -use codespan_reporting::diagnostic::{Diagnostic, Label}; +use codespan_reporting::{ + diagnostic::{Diagnostic, Label}, + files, +}; use thiserror::Error; /// An error forming the AST in semantic action, often wrapped by @@ -19,6 +22,8 @@ pub enum SourceError { InvalidXml(String, usize, Span), #[error("invalid toml syntax {0}")] InvalidToml(String, usize), + #[error("invalid edn: {0}")] + InvalidEdn(edn_format::ParserErrorWithContext, usize), #[error("character set error after {0}")] CharSetError(String, usize), #[error("invalid key {0}")] @@ -31,6 +36,10 @@ pub enum SourceError { EmbeddedCoreError(CoreError, usize, Span), #[error("embedded eucalypt parse error {0}")] EmbeddedParserError(ParserError, usize, Span), + #[error("unknown source format {0}")] + UnknownSourceFormat(String, usize), + #[error(transparent)] + Files(#[from] files::Error), } impl HasSpan for SourceError { @@ -45,6 +54,9 @@ impl HasSpan for SourceError { SourceError::InvalidSource(_, _) => Span::default(), SourceError::EmbeddedCoreError(_, _, s) => s, SourceError::EmbeddedParserError(_, _, s) => s, + SourceError::Files(_) => Span::default(), + SourceError::UnknownSourceFormat(_, _) => Span::default(), + SourceError::InvalidEdn(_, _) => Span::default(), } } } @@ -61,6 +73,9 @@ impl SourceError { SourceError::InvalidSource(_, f) => f, SourceError::EmbeddedCoreError(_, f, _) => f, SourceError::EmbeddedParserError(_, f, _) => f, + SourceError::Files(_) => unreachable!(), + SourceError::UnknownSourceFormat(_, f) => f, + SourceError::InvalidEdn(_, f) => f, } } diff --git a/src/import/mod.rs b/src/import/mod.rs index b295c2f..812728a 100644 --- a/src/import/mod.rs +++ b/src/import/mod.rs @@ -1,6 +1,38 @@ +use codespan_reporting::files::SimpleFiles; + +use crate::{common::sourcemap::SourceMap, core::expr::RcExpr}; +use codespan_reporting::files::Files; + +use self::error::SourceError; + pub mod csv; +pub mod edn; pub mod error; pub mod text; pub mod toml; pub mod xml; pub mod yaml; + +/// Read a supported source format into core representation +pub fn read_to_core<'smap>( + format: &str, + files: &'smap mut SimpleFiles, + source_map: &'smap mut SourceMap, + file_id: usize, +) -> Result { + match format { + "yaml" | "json" => { + let text = files.source(file_id)?.to_string(); + yaml::read_yaml(files, source_map, file_id, &text) + } + "toml" => toml::read_toml(source_map, file_id, files.source(file_id)?), + "text" => text::read_text(source_map, file_id, files.source(file_id)?), + "csv" => csv::read_csv(source_map, file_id, files.source(file_id)?), + "xml" => xml::read_xml(source_map, file_id, files.source(file_id)?), + "edn" => edn::read_edn(source_map, file_id, files.source(file_id)?), + _ => Err(SourceError::UnknownSourceFormat( + format.to_string(), + file_id, + )), + } +} diff --git a/src/import/yaml.rs b/src/import/yaml.rs index 0a706c1..a5b22ed 100644 --- a/src/import/yaml.rs +++ b/src/import/yaml.rs @@ -413,7 +413,7 @@ z: !!int 1234232353 ), ), (free("x"), acore::str("y")), - (free("z"), acore::num(12342323535 as u64)), + (free("z"), acore::num(12342323535_u64)), ]); assert_term_eq!(parsed, expected); diff --git a/src/syntax/input.rs b/src/syntax/input.rs index d15bd3c..8ac770d 100644 --- a/src/syntax/input.rs +++ b/src/syntax/input.rs @@ -108,6 +108,7 @@ impl Locator { "json" => Some(String::from("json")), "txt" => Some(String::from("text")), "toml" => Some(String::from("toml")), + "edn" => Some(String::from("edn")), "yaml" => Some(String::from("yaml")), "yml" => Some(String::from("yaml")), "csv" => Some(String::from("csv")), diff --git a/tests/harness_test.rs b/tests/harness_test.rs index fc2ea69..411ed82 100644 --- a/tests/harness_test.rs +++ b/tests/harness_test.rs @@ -230,3 +230,18 @@ pub fn test_harness_046() { pub fn test_harness_047() { run_test(&opts("047_xml_import.eu")); } + +#[test] +pub fn test_harness_048() { + run_test(&opts("048_parse_embed_core.eu")); +} + +#[test] +pub fn test_harness_049() { + run_test(&opts("049_tester.eu")); +} + +#[test] +pub fn test_harness_050() { + run_test(&opts("050_edn.edn")); +}