diff --git a/docsite/site/content/reference/_index.md b/docsite/site/content/reference/_index.md index 844007a..5a6edcb 100644 --- a/docsite/site/content/reference/_index.md +++ b/docsite/site/content/reference/_index.md @@ -26,6 +26,7 @@ Some words are reserved in ucg and can not be used as a named binding. * as * select * macro +* module * env * map * filter diff --git a/examples/module_example/modules/host_module.ucg b/examples/module_example/modules/host_module.ucg new file mode 100644 index 0000000..6fb52aa --- /dev/null +++ b/examples/module_example/modules/host_module.ucg @@ -0,0 +1,11 @@ +let host_mod = module{ + hostname="", + mem=2048, + cpu=2, +} => { + let config = { + hostname = mod.hostname, + memory_size = mod.mem, + cpu_count = mod.cpu, + }; +}; \ No newline at end of file diff --git a/examples/module_example/modules/site_module.ucg b/examples/module_example/modules/site_module.ucg new file mode 100644 index 0000000..e275c23 --- /dev/null +++ b/examples/module_example/modules/site_module.ucg @@ -0,0 +1,25 @@ +let site_mod = module{ + hostname="", + port=0, + db="localhost", + db_user="admin", + db_pass="password" +} => { // mods do not close over their environment. + + // this binding is not visible outside of the module. should be at the time of definition? + // or path should be rewritten to be absolue before instantiation. + import "../../shared.ucg" as shared; + + // processing should be delayed till module instantiation. + let base_config = shared.mk_site_config(mod.hostname, mod.port); + + let config = base_config{ // processing should also be delayed. + dbs = [ + { + database = mod.db, + user = mod.db_user, + pass = mod.db_pass, + }, + ], + }; +}; \ No newline at end of file diff --git a/examples/module_example/modules/unified.ucg b/examples/module_example/modules/unified.ucg new file mode 100644 index 0000000..25b9209 --- /dev/null +++ b/examples/module_example/modules/unified.ucg @@ -0,0 +1,12 @@ +let composed = module{} => { + import "host_module.ucg" as host_mod; + import "site_module.ucg" as site_mod; + + let site_conf = site_mod.site_mod{hostname="example.com", port=80}; + let host_conf = host_mod.host_mod{hostname="example.com"}; +}; + +let unified = composed{}; + +let site_conf = unified.site_conf; +let host_conf = unified.host_conf; \ No newline at end of file diff --git a/examples/module_example/test_mod_host.ucg b/examples/module_example/test_mod_host.ucg new file mode 100644 index 0000000..e0dde8e --- /dev/null +++ b/examples/module_example/test_mod_host.ucg @@ -0,0 +1,3 @@ +import "modules/unified.ucg" as unified; + +out yaml unified.host_conf; \ No newline at end of file diff --git a/examples/module_example/test_mod_site.ucg b/examples/module_example/test_mod_site.ucg new file mode 100644 index 0000000..3f582de --- /dev/null +++ b/examples/module_example/test_mod_site.ucg @@ -0,0 +1,3 @@ +import "modules/unified.ucg" as unified; + +out json unified.site_conf; \ No newline at end of file diff --git a/examples/shared.ucg b/examples/shared.ucg index c5451f6..339550e 100644 --- a/examples/shared.ucg +++ b/examples/shared.ucg @@ -1 +1,7 @@ -let port = 3306; \ No newline at end of file +let port = 3306; + +let mk_site_config = macro(hostname, port) => { + base_url = "https://@/" % (hostname), + port = port, + dbs = [], +}; \ No newline at end of file diff --git a/examples/test_flags.txt b/examples/test_flags.txt index e52863d..dc41662 100644 --- a/examples/test_flags.txt +++ b/examples/test_flags.txt @@ -1 +1 @@ ---port 8080 --listen '0.0.0.0' --verbose --dir 'some/dir' --dir 'some/other/dir' --log.debug true--log.format 'json' \ No newline at end of file +--port 8080 --listen '0.0.0.0' --verbose --dir 'some/dir' --dir 'some/other/dir' --log.debug true --log.format 'json' \ No newline at end of file diff --git a/integration_tests/modules_test.ucg b/integration_tests/modules_test.ucg new file mode 100644 index 0000000..7bb4fc2 --- /dev/null +++ b/integration_tests/modules_test.ucg @@ -0,0 +1,45 @@ +let test_empty_mod = module { +} => { +}; + +let empty_mod_instance = test_empty_mod{}; + +let test_simple_mod = module { + arg = "value", +} => { + let value = mod.arg; +}; + +let simple_mod_instance = test_simple_mod{}; +assert | + simple_mod_instance.value == "value"; +|; + +let simple_mod_with_args = test_simple_mod{arg = "othervalue"}; +assert | + simple_mod_with_args.value == "othervalue"; +|; + +let embedded_mod = module { + deep_value = "None", +} => { + let embedded_def = module { + deep_value = "None", + } => { + let value = mod.deep_value; + }; + + let embedded = embedded_def{deep_value = mod.deep_value}; +}; + +let embedded_default_params = embedded_mod{}; + +assert | + embedded_default_params.embedded.value == "None"; +|; + +let embedded_with_params = embedded_mod{deep_value = "Some"}; + +assert | + embedded_with_params.embedded.value == "Some"; +|; \ No newline at end of file diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 3b370d2..66f792d 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -24,9 +24,13 @@ use std::convert::Into; use std::fmt; use std::hash::Hash; use std::hash::Hasher; +use std::path::PathBuf; +use std::rc::Rc; use abortable_parser; +use build::Val; + macro_rules! enum_type_equality { ( $slf:ident, $r:expr, $( $l:pat ),* ) => { match $slf { @@ -612,6 +616,10 @@ impl MacroDef { // noop continue; } + &Expression::Module(_) => { + // noop + continue; + } &Expression::ListOp(_) => { // noop continue; @@ -705,6 +713,43 @@ pub struct ListOpDef { pub pos: Position, } +#[derive(Debug, PartialEq, Clone)] +pub struct ModuleDef { + pub pos: Position, + pub arg_set: FieldList, + // FIXME(jwall): this should probably be moved to a Val::Module IR type. + pub arg_tuple: Option>, + pub statements: Vec, +} + +impl ModuleDef { + pub fn new>(arg_set: FieldList, stmts: Vec, pos: P) -> Self { + ModuleDef { + pos: pos.into(), + arg_set: arg_set, + // TODO(jwall): Should this get moved to a Val version of our ModuleDef? + arg_tuple: None, + statements: stmts, + } + } + + pub fn imports_to_absolute(&mut self, base: PathBuf) { + for stmt in self.statements.iter_mut() { + if let &mut Statement::Import(ref mut def) = stmt { + let mut path = PathBuf::from(&def.path.fragment); + if path.is_relative() { + def.path.fragment = base + .join(path) + .canonicalize() + .unwrap() + .to_string_lossy() + .to_string(); + } + } + } + } +} + /// Encodes a ucg expression. Expressions compute a value from. #[derive(Debug, PartialEq, Clone)] pub enum Expression { @@ -724,6 +769,7 @@ pub enum Expression { Macro(MacroDef), Select(SelectDef), ListOp(ListOpDef), + Module(ModuleDef), } impl Expression { @@ -738,6 +784,7 @@ impl Expression { &Expression::Format(ref def) => &def.pos, &Expression::Call(ref def) => &def.pos, &Expression::Macro(ref def) => &def.pos, + &Expression::Module(ref def) => &def.pos, &Expression::Select(ref def) => &def.pos, &Expression::ListOp(ref def) => &def.pos, } @@ -774,6 +821,9 @@ impl fmt::Display for Expression { &Expression::Macro(_) => { try!(write!(w, "")); } + &Expression::Module(_) => { + try!(write!(w, "")); + } &Expression::Select(_) => { try!(write!(w, "