diff --git a/docsite/site/content/reference/expressions.md b/docsite/site/content/reference/expressions.md index 23af70e..08791cb 100644 --- a/docsite/site/content/reference/expressions.md +++ b/docsite/site/content/reference/expressions.md @@ -604,29 +604,23 @@ let embedded_default_params = top_mod_out_expr{}; embedded_default_params.value == "None"; ``` -### Recursive Modules +### Module builtin bindings One consequence of a module being able to import the same file they are located in is that modules can be called recursively. They are the only expression that is capable of recursion in UCG. Recursion can be done by importing the module's file inside the module's definition and using it as normal. -There is a convenience function `mod.pkg` in the mod binding for a module. That imports the -package/file that the module was declared in. This binding is only present if the module -was declared in a file. Modules created as part of an eval will not have it. +Modules have ia recursive reference to the current module `mod.this` that can +be used for recursive modules. ``` let recursive = module { counter=1, stop=10, } => (result) { - // import our enclosing file again. Careful since calling this function - // means that if you instantiate this module in the same file it is - // declared in you will trigger an import cycle error. - let pkg = mod.pkg(); - let result = select mod.counter != mod.stop, { - true = [mod.start] + pkg.recursive{counter=mod.counter+1}, + true = [mod.start] + mod.this{counter=mod.counter+1}, false = [mod.start], }; }; @@ -634,6 +628,24 @@ let recursive = module { recursive{} == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; ``` +There is also a convenience function `mod.pkg` in the mod binding for a module. +That imports the package/file the module was declared in. This binding is only +present if the module was declared in a file. Modules created as part of an +eval will not have it. + +``` +let self_importer = module { + item=NULL, +} => () { + let pkg = mod.pkg(); + + let result = pkg.recursive{}; +}; + +self_importer{} == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; +``` + + Fail Expression --------------- diff --git a/integration_tests/modules_test.ucg b/integration_tests/modules_test.ucg index b267d82..1cb399d 100644 --- a/integration_tests/modules_test.ucg +++ b/integration_tests/modules_test.ucg @@ -109,4 +109,16 @@ assert t.ok{ assert t.equal{ left = export_module_tuple{foo="bar"}, right = {quux = "bar"}, +}; + +let recursive_module = module {start=1, end=10} => (result) { + let this = mod.this; + let result = select mod.start != mod.end, mod.start, { + true = this{start=mod.start + 1, end=mod.end}, + }; +}; + +assert t.equal{ + left = recursive_module{}, + right = 10, }; \ No newline at end of file diff --git a/src/build/mod.rs b/src/build/mod.rs index 96df6d7..1dbeb37 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -1159,7 +1159,7 @@ impl<'a> FileBuilder<'a> { mod_def: &ModuleDef, scope: &Scope, ) -> Result, Box> { - let maybe_tpl = mod_def.clone().arg_tuple.unwrap().clone(); + let maybe_tpl = mod_def.arg_tuple.as_ref().unwrap().clone(); if let &Val::Tuple(ref src_fields) = maybe_tpl.as_ref() { // 1. First we create a builder. let mut b = self.clone_builder(); @@ -1188,6 +1188,10 @@ impl<'a> FileBuilder<'a> { }), )); } + overrides.push(( + Token::new("this", TokenType::BAREWORD, def.pos.clone()), + Expression::Module(mod_def.clone()), + )); overrides.extend(def.fields.iter().cloned()); let mod_args = self.copy_fields_from_base(src_fields, &overrides, &child_scope)?; // put our copied parameters tuple in our builder under the mod key. @@ -1327,7 +1331,12 @@ impl<'a> FileBuilder<'a> { // First we rewrite the imports to be absolute paths. def.imports_to_absolute(root); // Then we create our tuple default. - def.arg_tuple = Some(self.eval_tuple(&def.arg_set, scope)?); + if def.arg_tuple.is_none() { + // NOTE: This is an ugly hack to enable recursive references to work + // in eval_module_copy. We should really fix out DataModel to properly + // handle this but for now this should work. + def.arg_tuple = Some(self.eval_tuple(&def.arg_set, scope)?); + } // Then we construct a new Val::Module Ok(Rc::new(Val::Module(def))) }