From d9b2c0cb0021630882299af21ea3535475ace3c7 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Thu, 21 Feb 2019 19:39:30 -0600 Subject: [PATCH] FEATURE: out expression for modules. issue #35 --- integration_tests/modules_test.ucg | 42 +++++++++++ src/ast/mod.rs | 6 ++ src/build/mod.rs | 112 ++++++++++++++++------------- src/parse/mod.rs | 17 ++++- 4 files changed, 125 insertions(+), 52 deletions(-) diff --git a/integration_tests/modules_test.ucg b/integration_tests/modules_test.ucg index ac6742e..4d40024 100644 --- a/integration_tests/modules_test.ucg +++ b/integration_tests/modules_test.ucg @@ -1,3 +1,5 @@ +let t = import "std/testing.ucg".asserts{}; + let test_empty_mod = module { } => { }; @@ -67,4 +69,44 @@ assert { assert { ok = embedded_mod{dep_value="Some"}.embedded_def{}.value == "None", desc = "embedded_mod{dep_value=\"Some\"}.embedded_def{}.value == \"None\"", +}; + +let export_module = module { + foo = "", +} => (result) { + let foo = mod.foo; + + let result = { + out_foo = foo, + }; +}; + +assert t.ok{ + test = out_foo in export_module{foo="bar"}, + desc = "out_foo is in our output tuple", +}; + +assert t.equal{ + left = export_module{foo="bar"}, + right = {out_foo = "bar"}, +}; + +let export_module_tuple = module { + foo = "", +} => ({quux = result.out_foo}) { + let foo = mod.foo; + + let result = { + out_foo = foo, + }; +}; + +assert t.ok{ + test = quux in export_module_tuple{foo="bar"}, + desc = "quux is in our output tuple", +}; + +assert t.equal{ + left = export_module_tuple{foo="bar"}, + right = {quux = "bar"}, }; \ No newline at end of file diff --git a/src/ast/mod.rs b/src/ast/mod.rs index a85c19c..7c099f3 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -535,6 +535,7 @@ pub struct ModuleDef { pub scope: Option, pub pos: Position, pub arg_set: FieldList, + pub out_expr: Option>, pub arg_tuple: Option>, pub statements: Vec, } @@ -545,11 +546,16 @@ impl ModuleDef { scope: None, pos: pos.into(), arg_set: arg_set, + out_expr: None, arg_tuple: None, statements: stmts, } } + pub fn set_out_expr(&mut self, expr: Expression) { + self.out_expr = Some(Box::new(expr)); + } + pub fn imports_to_absolute(&mut self, base: PathBuf) { let rewrite_import = |e: &mut Expression| { if let Expression::Include(ref mut def) = e { diff --git a/src/build/mod.rs b/src/build/mod.rs index 549007e..de68af6 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -1134,6 +1134,68 @@ impl<'a> FileBuilder<'a> { ))); } + fn eval_module_copy( + &self, + def: &CopyDef, + mod_def: &ModuleDef, + scope: &Scope, + ) -> Result, Box> { + let maybe_tpl = mod_def.clone().arg_tuple.unwrap().clone(); + if let &Val::Tuple(ref src_fields) = maybe_tpl.as_ref() { + // 1. First we create a builder. + // TODO(jwall): This file should optionally come from the module def itself. + let mut b = self.clone_builder(); + b.is_module = true; + // 2. We construct an argument tuple by copying from the defs + // argset. + // Push our base tuple on the stack so the copy can use + // self to reference it. + let child_scope = scope.spawn_child().set_curr_val(maybe_tpl.clone()); + let mod_args = self.copy_from_base(src_fields, &def.fields, &child_scope)?; + // put our copied parameters tuple in our builder under the mod key. + let mod_key = PositionedItem::new_with_pos(String::from("mod"), Position::new(0, 0, 0)); + match b.scope.build_output.entry(mod_key) { + Entry::Occupied(e) => { + return Err(error::BuildError::with_pos( + format!( + "Binding \ + for {:?} already \ + exists in module", + e.key(), + ), + error::ErrorType::DuplicateBinding, + mod_def.pos.clone(), + ) + .to_boxed()); + } + Entry::Vacant(e) => { + e.insert(mod_args.clone()); + } + } + // 4. Evaluate all the statements using the builder. + b.eval_stmts(&mod_def.statements)?; + if let Some(ref expr) = mod_def.out_expr { + // 5. Produce the out expression in the context of the statements + // we evaluated previously. + return b.eval_expr(expr, &b.scope); + } else { + // 5. Take all of the bindings in the module and construct a new + // tuple using them. + return Ok(b.get_outputs_as_val()); + } + } else { + return Err(error::BuildError::with_pos( + format!( + "Weird value stored in our module parameters slot {:?}", + mod_def.arg_tuple + ), + error::ErrorType::TypeFail, + def.selector.pos().clone(), + ) + .to_boxed()); + } + } + fn eval_copy(&self, def: &CopyDef, scope: &Scope) -> Result, Box> { let v = self.eval_value(&def.selector, scope)?; if let &Val::Tuple(ref src_fields) = v.as_ref() { @@ -1141,55 +1203,7 @@ impl<'a> FileBuilder<'a> { return self.copy_from_base(&src_fields, &def.fields, &child_scope); } if let &Val::Module(ref mod_def) = v.as_ref() { - let maybe_tpl = mod_def.clone().arg_tuple.unwrap().clone(); - if let &Val::Tuple(ref src_fields) = maybe_tpl.as_ref() { - // 1. First we create a builder. - // TODO(jwall): This file should optionally come from the module def itself. - let mut b = self.clone_builder(); - b.is_module = true; - // 2. We construct an argument tuple by copying from the defs - // argset. - // Push our base tuple on the stack so the copy can use - // self to reference it. - let child_scope = scope.spawn_child().set_curr_val(maybe_tpl.clone()); - let mod_args = self.copy_from_base(src_fields, &def.fields, &child_scope)?; - // put our copied parameters tuple in our builder under the mod key. - let mod_key = - PositionedItem::new_with_pos(String::from("mod"), Position::new(0, 0, 0)); - match b.scope.build_output.entry(mod_key) { - Entry::Occupied(e) => { - return Err(error::BuildError::with_pos( - format!( - "Binding \ - for {:?} already \ - exists in module", - e.key(), - ), - error::ErrorType::DuplicateBinding, - mod_def.pos.clone(), - ) - .to_boxed()); - } - Entry::Vacant(e) => { - e.insert(mod_args.clone()); - } - } - // 4. Evaluate all the statements using the builder. - b.eval_stmts(&mod_def.statements)?; - // 5. Take all of the bindings in the module and construct a new - // tuple using them. - return Ok(b.get_outputs_as_val()); - } else { - return Err(error::BuildError::with_pos( - format!( - "Weird value stored in our module parameters slot {:?}", - mod_def.arg_tuple - ), - error::ErrorType::TypeFail, - def.selector.pos().clone(), - ) - .to_boxed()); - } + return self.eval_module_copy(def, mod_def, scope); } Err(error::BuildError::with_pos( format!("Expected Tuple or Module but got ({})", v), diff --git a/src/parse/mod.rs b/src/parse/mod.rs index a6c13f9..e85b2b4 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -379,17 +379,28 @@ fn module_expression(input: SliceIter) -> Result, Expres _ => optional!(punct!(",")), _ => must!(punct!("}")), _ => must!(punct!("=>")), + out_expr => optional!( + do_each!( + _ => punct!("("), + expr => must!(expression), + _ => must!(punct!(")")), + (expr) + ) + ), _ => must!(punct!("{")), stmt_list => trace_parse!(repeat!(statement)), _ => must!(punct!("}")), - (pos, arglist, stmt_list) + (pos, arglist, out_expr, stmt_list) ); match parsed { Result::Abort(e) => Result::Abort(e), Result::Fail(e) => Result::Fail(e), Result::Incomplete(offset) => Result::Incomplete(offset), - Result::Complete(rest, (pos, arglist, stmt_list)) => { - let def = ModuleDef::new(arglist.unwrap_or_else(|| Vec::new()), stmt_list, pos); + Result::Complete(rest, (pos, arglist, out_expr, stmt_list)) => { + let mut def = ModuleDef::new(arglist.unwrap_or_else(|| Vec::new()), stmt_list, pos); + if let Some(expr) = out_expr { + def.set_out_expr(expr); + } //eprintln!( // "module def at: {:?} arg_typle len {} stmts len {}", // def.pos,