FEATURE: Give modules a reference to self.

This commit is contained in:
Jeremy Wall 2019-04-24 19:05:48 -05:00
parent f12d264778
commit 439ebf74f3
3 changed files with 45 additions and 12 deletions

View File

@ -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
---------------

View File

@ -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,
};

View File

@ -1159,7 +1159,7 @@ impl<'a> FileBuilder<'a> {
mod_def: &ModuleDef,
scope: &Scope,
) -> Result<Rc<Val>, Box<dyn Error>> {
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)))
}