diff --git a/docsite/site/content/reference/expressions.md b/docsite/site/content/reference/expressions.md index 2239f57..f967ab4 100644 --- a/docsite/site/content/reference/expressions.md +++ b/docsite/site/content/reference/expressions.md @@ -249,19 +249,46 @@ UCG can generate lists from a range with an optional step. 0:2:10 == [0, 2, 4, 6, 8, 10]; ``` +Macros +----- + +Macros look like functions but they are resolved at compile time and +configurations don't execute so they never appear in output. Macros close over +the environment up to the point where they are declared in the file. One +consequence of this is that they can not call themselves so recursive macros +are not possible. This is probably a feature. They are useful for constructing +tuples of a certain shape or otherwise promoting data reuse. You define a macro +with the `macro` keyword followed by the arguments in parentheses, a `=>`, and +then a valid expression. + +``` +let mymacro = macro (arg1, arg2) => { + host = arg1, + port = arg2, + connstr = "couchdb://@:@" % (arg1, arg2), +}; + +let my_dbconf = mymacro("couchdb.example.org", "9090"); + +let my_dbhost = dbconf.host; + +let add = macro(arg1, arg2) => arg1 + arg2; +add(1, 1) == 2; +``` + Functional processing expressions --------------------------------- -UCG has a few functional processing expressions called `map` and `filter`. Both of -them can process a list or tuple. +UCG has a few functional processing expressions called `map`, `filter`, and +`reduce`. All of them can process a list or tuple. -Their syntax starts with either map or filter followed by a symbol that -references a valid macro and the outfield for the tuple the macro produces. and -finally an expression that resolves to either a list of tuple. +Their syntax starts with either `map` `filter`, or `reduce followed by a symbol +that references a valid macro and finally an expression that resolves to either +a list or a tuple. ### Map expressions -Map macros should produce in the result field a value or [field, value] that +Map macros should produce either a valid value or a list of [field, value] that will replace the element or field it is curently processing. **For Lists** @@ -272,34 +299,32 @@ macro is expected to take a single argument. ``` let list1 = [1, 2, 3, 4]; -let mapper = macro(item) => { result = item + 1 }; -map mapper.result list1 == [2, 3, 4, 5]; +let mapper = macro(item) => item + 1; +map mapper list1 == [2, 3, 4, 5]; ``` **For Tuples** Macros for mapping across a tuple are expected to take two arguments. The first argument is the name of the field. The second argument is the value in that -field. The result field should be a two item list with the first item being the -new field name and the second item being the new value. +field. The result should be a two item list with the first item being the new +field name and the second item being the new value. ``` let test_tpl = { foo = "bar", quux = "baz", }; -let tpl_mapper = macro(name, val) => { - result = select name, [name, val], { - "foo" = ["foo", "barbar"], - quux = ["cute", "pygmy"], - }, +let tpl_mapper = macro(name, val) => select name, [name, val], { + "foo" = ["foo", "barbar"], + quux = ["cute", "pygmy"], }; -map tpl_mapper.result test_tpl == {foo = "barbar", cute = "pygmy"}; +map tpl_mapper test_tpl == {foo = "barbar", cute = "pygmy"}; ``` ### Filter expressions -Filter expressions should return a result field with false or NULL for items to +Filter expressions should return a field with false or NULL for items to filter out of the list or tuple. Any other value in the return field results in the item or field staying in the resulting list or tuple. @@ -307,10 +332,8 @@ the item or field staying in the resulting list or tuple. ``` let list2 = ["foo", "bar", "foo", "bar"]; -let filtrator = macro(item) => { - result = select item, NULL, { - foo = item, - }, +let filtrator = macro(item) => select item, NULL, { + foo = item, }; filter filtrator.result list2 == ["foo", "foo"]; @@ -323,15 +346,13 @@ let test_tpl = { foo = "bar", quux = "baz", }; -let tpl_filter = macro(name, val) => { - result = name != "foo", -}; -filter tpl_filter.result test_tpl == { quux = "baz" }; +let tpl_filter = macro(name, val) => name != "foo"; +filter tpl_filter test_tpl == { quux = "baz" }; ``` ### Reduce expressions -Reduce expressions start with the reduce keyword followed by a symbol referencing a macro a dot and the output field, then an expression for the accumulator and finally the tuple or list to process. +Reduce expressions start with the reduce keyword followed by a symbol referencing a macro an expression for the accumulator and finally the tuple or list to process. **Tuples** @@ -340,25 +361,21 @@ let test_tpl = { foo = "bar", quux = "baz", }; -let tpl_reducer = macro(acc, name, val) => { - result = acc{ - keys = self.keys + [name], - vals = self.vals + [val], - }, +let tpl_reducer = macro(acc, name, val) => acc{ + keys = self.keys + [name], + vals = self.vals + [val], }; -reduce tpl_reducer.result {keys = [], vals = []}, test_tpl == {keys = ["foo", "quux"], vals = ["bar", "baz"]}; +reduce tpl_reducer {keys = [], vals = []}, test_tpl == {keys = ["foo", "quux"], vals = ["bar", "baz"]}; ``` **Lists** ``` let list1 = [1, 2, 3, 4]; -let list_reducer = macro(acc, item) => { - result = acc + item, -}; +let list_reducer = macro(acc, item) => acc + item; - list_reducer.result 0, list1 == 0 + 1 + 2 + 3 + 4; + list_reducer 0, list1 == 0 + 1 + 2 + 3 + 4; ``` Include expressions @@ -403,31 +420,6 @@ let ifresult = select true, NULL, { }; // result will be "true result" ``` -Macros ------ - -Macros look like functions but they are resolved at compile time and -configurations don't execute so they never appear in output. Macros close over -their environment in the file they are declared in. They are useful for -constructing tuples of a certain shape or otherwise promoting data reuse. You -define a macro with the `macro` keyword followed by the arguments in -parentheses, a `=>`, and then a tuple literal. - -``` -let mymacro = macro (arg1, arg2) => { - host = arg1, - port = arg2, - connstr = "couchdb://@:@" % (arg1, arg2), -}; - -let my_dbconf = mymacro("couchdb.example.org", "9090"); - -let my_dbhost = dbconf.host; -``` - -Note that while macros can close over their environment they can not reference -other fields in the macro body itself. - Modules ------- diff --git a/docsite/site/content/reference/grammar.md b/docsite/site/content/reference/grammar.md index ee3d9e0..f7fa9e4 100644 --- a/docsite/site/content/reference/grammar.md +++ b/docsite/site/content/reference/grammar.md @@ -136,8 +136,8 @@ format_expr: str, percent, lparen, [arglist], rparen ; ``` func_op_kind: map_keyword | filter_keyword ; -map_or_filter_expr: func_op_kind, bareword, dot, bareword, expr ; -reduce_expr: reduce_keyword, bareword, dot, bareword, expr, expr ; +map_or_filter_expr: func_op_kind, bareword, expr ; +reduce_expr: reduce_keyword, bareword, expr, expr ; processing_expr: map_or_filter_expr | reduce_expr ``` diff --git a/integration_tests/functional_processing_test.ucg b/integration_tests/functional_processing_test.ucg index dd36777..727cc84 100644 --- a/integration_tests/functional_processing_test.ucg +++ b/integration_tests/functional_processing_test.ucg @@ -3,24 +3,18 @@ let list1 = [1, 2, 3, 4]; let list2 = ["foo", "bar", "foo", "bar"]; -let mapper = macro(item) => { result = item + 1 }; -let filtrator = macro(item) => { - result = select item, NULL, { +let mapper = macro(item) => item + 1; +let filtrator = macro(item) => select item, NULL, { foo = item, - }, }; -let boolfiltrator = macro(item) => { - result = item < 5, -}; +let boolfiltrator = macro(item) => item < 5; -let identity_list_reducer = macro(acc, item) => { - result = acc + [item], -}; +let identity_list_reducer = macro(acc, item) => acc + [item]; assert { - ok = reduce identity_list_reducer.result [], list1 == list1, - desc = "reduce identity_list_reducer.result [], list1 == list1", + ok = reduce identity_list_reducer [], list1 == list1, + desc = "reduce identity_list_reducer [], list1 == list1", }; let nested_list = { @@ -28,47 +22,46 @@ let nested_list = { }; assert { - ok = reduce identity_list_reducer.result [], (nested_list.list) == list1, - desc = "reduce identity_list_reducer.result [], (nested_list.list) == list1", + ok = reduce identity_list_reducer [], (nested_list.list) == list1, + desc = "reduce identity_list_reducer [], (nested_list.list) == list1", }; -let list_reducer = macro(acc, item) => { - result = acc + item, +let list_reducer = macro(acc, item) => acc + item; + +assert { + ok = reduce list_reducer 0, list1 == 0 + 1 + 2 + 3 + 4, + desc = "reduce list_reducer 0, list1 == 0 + 1 + 2 + 3 + 4", }; assert { - ok = reduce list_reducer.result 0, list1 == 0 + 1 + 2 + 3 + 4, - desc = "reduce list_reducer.result 0, list1 == 0 + 1 + 2 + 3 + 4", + ok = map mapper list1 == [2, 3, 4, 5], + desc = "map mapper list1 == [2, 3, 4, 5]", +}; +assert { + ok = (map mapper [1, 2, 3, 4]) == [2, 3, 4, 5], + desc = "(map mapper [1, 2, 3, 4]) == [2, 3, 4, 5]", +}; +assert { + ok = map mapper [1, 2, 3, 4] == [2, 3, 4, 5], + desc = "map mapper [1, 2, 3, 4] == [2, 3, 4, 5]", }; assert { - ok = map mapper.result list1 == [2, 3, 4, 5], - desc = "map mapper.result list1 == [2, 3, 4, 5]", -}; -assert { - ok = (map mapper.result [1, 2, 3, 4]) == [2, 3, 4, 5], - desc = "(map mapper.result [1, 2, 3, 4]) == [2, 3, 4, 5]", -}; -assert { - ok = map mapper.result [1, 2, 3, 4] == [2, 3, 4, 5], - desc = "map mapper.result [1, 2, 3, 4] == [2, 3, 4, 5]", + ok = filter filtrator list2 == ["foo", "foo"], + desc = "filter filtrator list2 == [\"foo\", \"foo\"]", }; assert { - ok = filter filtrator.result list2 == ["foo", "foo"], - desc = "filter filtrator.result list2 == [\"foo\", \"foo\"]", + ok = (filter filtrator ["foo", "bar", "foo", "bar"]) == ["foo", "foo"], + desc = "(filter filtrator [\"foo\", \"bar\", \"foo\", \"bar\"]) == [\"foo\", \"foo\"]", }; assert { - ok = (filter filtrator.result ["foo", "bar", "foo", "bar"]) == ["foo", "foo"], - desc = "(filter filtrator.result [\"foo\", \"bar\", \"foo\", \"bar\"]) == [\"foo\", \"foo\"]", + ok = filter filtrator ["foo", "bar", "foo", "bar"] == ["foo", "foo"], + desc = "filter filtrator [\"foo\", \"bar\", \"foo\", \"bar\"] == [\"foo\", \"foo\"]", }; assert { - ok = filter filtrator.result ["foo", "bar", "foo", "bar"] == ["foo", "foo"], - desc = "filter filtrator.result [\"foo\", \"bar\", \"foo\", \"bar\"] == [\"foo\", \"foo\"]", -}; -assert { - ok = filter boolfiltrator.result [1, 2, 3, 4, 5, 6, 7] == [1, 2, 3, 4], - desc = "filter boolfiltrator.result [1, 2, 3, 4, 5, 6, 7] == [1, 2, 3, 4]", + ok = filter boolfiltrator [1, 2, 3, 4, 5, 6, 7] == [1, 2, 3, 4], + desc = "filter boolfiltrator [1, 2, 3, 4, 5, 6, 7] == [1, 2, 3, 4]", }; // Tuple processing @@ -77,54 +70,44 @@ let test_tpl = { quux = "baz", }; -let identity_tpl_mapper = macro(name, val) => { - result = [name, val], +let identity_tpl_mapper = macro(name, val) => [name, val]; + +assert { + ok = map identity_tpl_mapper test_tpl == test_tpl, + desc = "map identity_tpl_mapper test_tpl == test_tpl", +}; + +let tpl_mapper = macro(name, val) => select name, [name, val], { + "foo" = ["foo", "barbar"], + quux = ["cute", "pygmy"], }; assert { - ok = map identity_tpl_mapper.result test_tpl == test_tpl, - desc = "map identity_tpl_mapper.result test_tpl == test_tpl", + ok = map tpl_mapper test_tpl == {foo = "barbar", cute = "pygmy"}, + desc = "map tpl_mapper test_tpl == {foo = \"barbar\", cute = \"pygmy\"}", }; -let tpl_mapper = macro(name, val) => { - result = select name, [name, val], { - "foo" = ["foo", "barbar"], - quux = ["cute", "pygmy"], - }, -}; - -assert { - ok = map tpl_mapper.result test_tpl == {foo = "barbar", cute = "pygmy"}, - desc = "map tpl_mapper.result test_tpl == {foo = \"barbar\", cute = \"pygmy\"}", -}; - -let identity_tpl_filter = macro(name, val) => { - result = true, -}; +let identity_tpl_filter = macro(name, val) => true; // strip out foo field -let tpl_filter = macro(name, val) => { - result = name != "foo", +let tpl_filter = macro(name, val) => name != "foo"; + +assert { + ok = filter identity_tpl_filter test_tpl == test_tpl, + desc = "filter identity_tpl_filter == test_tpl", }; assert { - ok = filter identity_tpl_filter.result test_tpl == test_tpl, - desc = "filter identity_tpl_filter.result test_tpl == test_tpl", + ok = filter tpl_filter test_tpl == { quux = "baz" }, + desc = "filter tpl_filter test_tpl == { quux = \"baz\" }", }; -assert { - ok = filter tpl_filter.result test_tpl == { quux = "baz" }, - desc = "filter tpl_filter.result test_tpl == { quux = \"baz\" }", -}; - -let tpl_reducer = macro(acc, name, val) => { - result = acc{ +let tpl_reducer = macro(acc, name, val) => acc{ keys = self.keys + [name], vals = self.vals + [val], - }, }; assert { - ok = reduce tpl_reducer.result {keys = [], vals = []}, test_tpl == {keys = ["foo", "quux"], vals = ["bar", "baz"]}, - desc = "reduce tpl_reducer.result {keys = [], vals = []}, test_tpl == {keys = [\"foo\", \"quux\"], vals = [\"bar\", \"baz\"]}", + ok = reduce tpl_reducer {keys = [], vals = []}, test_tpl == {keys = ["foo", "quux"], vals = ["bar", "baz"]}, + desc = "reduce tpl_reducer {keys = [], vals = []}, test_tpl == {keys = [\"foo\", \"quux\"], vals = [\"bar\", \"baz\"]}", }; \ No newline at end of file diff --git a/integration_tests/macros_test.ucg b/integration_tests/macros_test.ucg index a5c1a62..c7e0a81 100644 --- a/integration_tests/macros_test.ucg +++ b/integration_tests/macros_test.ucg @@ -75,4 +75,11 @@ let closure = macro(arg1) => { assert { ok = closure("bar").result == "foobar", desc = "we closed over closed_over and got @" % (closure("bar").result), +}; + +let concat = macro(arg1, arg2) => arg1 + arg2; + +assert { + ok = concat("foo", "bar") == "foobar", + desc = "macros that aren't tuples work", }; \ No newline at end of file diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 6dd25e3..558de82 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -379,7 +379,7 @@ impl<'a> From<&'a PositionedItem> for PositionedItem { pub struct MacroDef { pub scope: Option, pub argdefs: Vec>, - pub fields: FieldList, + pub fields: Box, pub pos: Position, } @@ -484,7 +484,6 @@ pub enum FuncOpDef { #[derive(Debug, PartialEq, Clone)] pub struct ReduceOpDef { pub mac: PositionedItem, - pub field: PositionedItem, pub acc: Box, pub target: Box, pub pos: Position, @@ -494,7 +493,6 @@ pub struct ReduceOpDef { #[derive(Debug, PartialEq, Clone)] pub struct MapFilterOpDef { pub mac: PositionedItem, - pub field: PositionedItem, pub target: Box, pub pos: Position, } diff --git a/src/ast/walk.rs b/src/ast/walk.rs index 193a438..6a48e82 100644 --- a/src/ast/walk.rs +++ b/src/ast/walk.rs @@ -89,7 +89,7 @@ impl<'a> AstWalker<'a> { Expression::Grouped(ref mut expr) => { self.walk_expression(expr); } - Expression::Macro(ref mut def) => self.walk_fieldset(&mut def.fields), + Expression::Macro(ref mut def) => self.walk_expression(def.fields.as_mut()), Expression::Module(ref mut def) => { self.walk_fieldset(&mut def.arg_set); for stmt in def.statements.iter_mut() { diff --git a/src/build/mod.rs b/src/build/mod.rs index 03ca4f9..a02c1f1 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -55,7 +55,7 @@ impl MacroDef { root: PathBuf, parent_builder: &FileBuilder, mut args: Vec>, - ) -> Result, Rc)>, Box> { + ) -> Result, Box> { // Error conditions. If the args don't match the length and types of the argdefs then this is // macro call error. if args.len() > self.argdefs.len() { @@ -84,15 +84,7 @@ impl MacroDef { } // We clobber anything that used to be in the scope with the arguments. b.merge_build_output(build_output, true); - let mut result: Vec<(PositionedItem, Rc)> = Vec::new(); - for &(ref key, ref expr) in self.fields.iter() { - // We clone the expressions here because this macro may be consumed - // multiple times in the future. - let scope = b.scope.spawn_child(); - let val = b.eval_expr(expr, &scope)?; - result.push((key.into(), val.clone())); - } - Ok(result) + Ok(b.eval_expr(self.fields.as_ref(), &b.scope.spawn_child())?) } } @@ -1112,8 +1104,7 @@ impl<'a> FileBuilder<'a> { for arg in args.iter() { argvals.push(self.eval_expr(arg, scope)?); } - let fields = m.eval(self.file.clone(), self, argvals)?; - return Ok(Rc::new(Val::Tuple(fields))); + return Ok(m.eval(self.file.clone(), self, argvals)?); } Err(Box::new(error::BuildError::new( // We should pretty print the selectors here. @@ -1195,28 +1186,25 @@ impl<'a> FileBuilder<'a> { &self, elems: &Vec>, def: &MacroDef, - outfield: &PositionedItem, typ: ProcessingOpType, ) -> Result, Box> { let mut out = Vec::new(); for item in elems.iter() { let argvals = vec![item.clone()]; - let fields = def.eval(self.file.clone(), self, argvals)?; - if let Some(v) = find_in_fieldlist(&outfield.val, &fields) { - match typ { - ProcessingOpType::Map => { - out.push(v.clone()); - } - ProcessingOpType::Filter => { - if let &Val::Empty = v.as_ref() { - // noop - continue; - } else if let &Val::Boolean(false) = v.as_ref() { - // noop - continue; - } - out.push(item.clone()); + let val = def.eval(self.file.clone(), self, argvals)?; + match typ { + ProcessingOpType::Map => { + out.push(val.clone()); + } + ProcessingOpType::Filter => { + if let &Val::Empty = val.as_ref() { + // noop + continue; + } else if let &Val::Boolean(false) = val.as_ref() { + // noop + continue; } + out.push(item.clone()); } } } @@ -1227,77 +1215,65 @@ impl<'a> FileBuilder<'a> { &self, fs: &Vec<(PositionedItem, Rc)>, def: &MacroDef, - outfield: &PositionedItem, typ: ProcessingOpType, ) -> Result, Box> { let mut out = Vec::new(); for &(ref name, ref val) in fs { let argvals = vec![Rc::new(Val::Str(name.val.clone())), val.clone()]; - let fields = def.eval(self.file.clone(), self, argvals)?; - if let Some(v) = find_in_fieldlist(&outfield.val, &fields) { - match typ { - ProcessingOpType::Map => { - if let &Val::List(ref fs) = v.as_ref() { - if fs.len() == 2 { - // index 0 should be a string for the new field name. - // index 1 should be the val. - let new_name = if let &Val::Str(ref s) = fs[0].as_ref() { - s.clone() - } else { - return Err(Box::new(error::BuildError::new( - format!( - "map on tuple expects the first item out list to be a string but got size {}", - fs[0].type_name() - ), - error::ErrorType::TypeFail, - def.pos.clone(), - ))); - }; - out.push(( - PositionedItem::new(new_name, name.pos.clone()), - fs[1].clone(), - )); + let result = def.eval(self.file.clone(), self, argvals)?; + match typ { + ProcessingOpType::Map => { + if let &Val::List(ref fs) = result.as_ref() { + if fs.len() == 2 { + // index 0 should be a string for the new field name. + // index 1 should be the val. + let new_name = if let &Val::Str(ref s) = fs[0].as_ref() { + s.clone() } else { return Err(Box::new(error::BuildError::new( format!( - "map on a tuple field expects a list of size 2 as output but got size {}", - fs.len() + "map on tuple expects the first item out list to be a string but got size {}", + fs[0].type_name() ), error::ErrorType::TypeFail, def.pos.clone(), ))); - } + }; + out.push(( + PositionedItem::new(new_name, name.pos.clone()), + fs[1].clone(), + )); } else { return Err(Box::new(error::BuildError::new( format!( - "map on a tuple field expects a list as output but got {:?}", - v.type_name() + "map on a tuple field expects a list of size 2 as output but got size {}", + fs.len() ), error::ErrorType::TypeFail, def.pos.clone(), ))); } - } - ProcessingOpType::Filter => { - if let &Val::Empty = v.as_ref() { - // noop - continue; - } else if let &Val::Boolean(false) = v.as_ref() { - // noop - continue; - } - out.push((name.clone(), val.clone())); + } else { + return Err(Box::new(error::BuildError::new( + format!( + "map on a tuple field expects a list as output but got {:?}", + result.type_name() + ), + error::ErrorType::TypeFail, + def.pos.clone(), + ))); } } - } else { - return Err(Box::new(error::BuildError::new( - format!( - "Result {} field does not exist in macro body!", - outfield.val - ), - error::ErrorType::NoSuchSymbol, - def.pos.clone(), - ))); + ProcessingOpType::Filter => { + if let &Val::Empty = result.as_ref() { + // noop + continue; + } else if let &Val::Boolean(false) = result.as_ref() { + // noop + continue; + } + out.push((name.clone(), val.clone())); + } } } Ok(Rc::new(Val::Tuple(out))) @@ -1321,16 +1297,8 @@ impl<'a> FileBuilder<'a> { &Val::List(ref elems) => { for item in elems.iter() { let argvals = vec![acc.clone(), item.clone()]; - let fields = macdef.eval(self.file.clone(), self, argvals)?; - if let Some(v) = find_in_fieldlist(&def.field.val, &fields) { - acc = v.clone(); - } else { - return Err(Box::new(error::BuildError::new( - format!("Result {} field does not exist in macro body!", def.field), - error::ErrorType::NoSuchSymbol, - def.pos.clone(), - ))); - } + let result = macdef.eval(self.file.clone(), self, argvals)?; + acc = result; } } &Val::Tuple(ref fs) => { @@ -1340,16 +1308,8 @@ impl<'a> FileBuilder<'a> { Rc::new(Val::Str(name.val.clone())), val.clone(), ]; - let fields = macdef.eval(self.file.clone(), self, argvals)?; - if let Some(v) = find_in_fieldlist(&def.field.val, &fields) { - acc = v.clone(); - } else { - return Err(Box::new(error::BuildError::new( - format!("Result field {}does not exist in macro body!", def.field), - error::ErrorType::NoSuchSymbol, - def.pos.clone(), - ))); - } + let result = macdef.eval(self.file.clone(), self, argvals)?; + acc = result; } } other => { @@ -1385,12 +1345,8 @@ impl<'a> FileBuilder<'a> { } }; return match maybe_target.as_ref() { - &Val::List(ref elems) => { - self.eval_functional_list_processing(elems, macdef, &def.field, typ) - } - &Val::Tuple(ref fs) => { - self.eval_functional_tuple_processing(fs, macdef, &def.field, typ) - } + &Val::List(ref elems) => self.eval_functional_list_processing(elems, macdef, typ), + &Val::Tuple(ref fs) => self.eval_functional_tuple_processing(fs, macdef, typ), other => Err(Box::new(error::BuildError::new( format!( "Expected List or Tuple as target but got {:?}", diff --git a/src/build/test.rs b/src/build/test.rs index d6eb9d7..36f8ba9 100644 --- a/src/build/test.rs +++ b/src/build/test.rs @@ -250,13 +250,10 @@ fn test_macro_hermetic() { .or_insert(Rc::new(Val::Macro(MacroDef { scope: None, argdefs: vec![value_node!("arg2".to_string(), Position::new(1, 0, 0))], - fields: vec![( - make_tok!("foo", Position::new(1, 1, 1)), - Expression::Simple(Value::Symbol(value_node!( - "arg1".to_string(), - Position::new(1, 1, 1) - ))), - )], + fields: Box::new(Expression::Simple(Value::Symbol(value_node!( + "arg1".to_string(), + Position::new(1, 1, 1) + )))), pos: Position::new(1, 0, 0), }))); test_expr_to_val( diff --git a/src/parse/mod.rs b/src/parse/mod.rs index be573e5..1e03432 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -343,10 +343,9 @@ make_fn!( ); fn tuple_to_macro<'a>( - input: SliceIter<'a, Token>, pos: Position, vals: Option>, - val: Value, + val: Expression, ) -> ConvertResult<'a, Expression> { let mut default_args = match vals { None => Vec::new(), @@ -359,18 +358,12 @@ fn tuple_to_macro<'a>( val: s.to_string(), }) .collect(); - match val { - Value::Tuple(v) => Ok(Expression::Macro(MacroDef { - scope: None, - argdefs: arglist, - fields: v.val, - pos: pos, - })), - val => Err(Error::new( - format!("Expected Tuple Got {:?}", val), - Box::new(input.clone()), - )), - } + Ok(Expression::Macro(MacroDef { + scope: None, + argdefs: arglist, + fields: Box::new(val), + pos: pos, + })) } make_fn!( @@ -417,23 +410,21 @@ fn macro_expression(input: SliceIter) -> Result, Express arglist => trace_parse!(optional!(arglist)), _ => must!(punct!(")")), _ => must!(punct!("=>")), - map => trace_parse!(tuple), + map => trace_parse!(expression), (pos, arglist, map) ); 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, map)) => { - match tuple_to_macro(rest.clone(), pos, arglist, map) { - Ok(expr) => Result::Complete(rest, expr), - Err(e) => Result::Fail(Error::caused_by( - "Invalid Macro syntax", - Box::new(e), - Box::new(rest.clone()), - )), - } - } + Result::Complete(rest, (pos, arglist, map)) => match tuple_to_macro(pos, arglist, map) { + Ok(expr) => Result::Complete(rest, expr), + Err(e) => Result::Fail(Error::caused_by( + "Invalid Macro syntax", + Box::new(e), + Box::new(rest.clone()), + )), + }, } } @@ -574,14 +565,11 @@ make_fn!( pos => pos, _ => word!("reduce"), macroname => match_type!(BAREWORD), - _ => punct!("."), - outfield => match_type!(BAREWORD), acc => trace_parse!(non_op_expression), _ => punct!(","), tgt => trace_parse!(non_op_expression), (Expression::FuncOp(FuncOpDef::Reduce(ReduceOpDef{ mac: (¯oname).into(), - field: (&outfield).into(), acc: Box::new(acc), target: Box::new(tgt), pos: pos, @@ -596,12 +584,9 @@ make_fn!( _ => word!("map"), // TODO This should become just a bareword symbol now macroname => match_type!(BAREWORD), - _ => punct!("."), - outfield => match_type!(BAREWORD), list => trace_parse!(non_op_expression), (Expression::FuncOp(FuncOpDef::Map(MapFilterOpDef{ mac: (¯oname).into(), - field: (&outfield).into(), target: Box::new(list), pos: pos, }))) @@ -615,12 +600,9 @@ make_fn!( _ => word!("filter"), // TODO This should become just a bareword symbol now macroname => match_type!(BAREWORD), - _ => punct!("."), - outfield => match_type!(BAREWORD), list => trace_parse!(non_op_expression), (Expression::FuncOp(FuncOpDef::Filter(MapFilterOpDef{ mac: (¯oname).into(), - field: (&outfield).into(), target: Box::new(list), pos: pos, }))) diff --git a/std/functional.ucg b/std/functional.ucg index f884bd6..426f8f6 100644 --- a/std/functional.ucg +++ b/std/functional.ucg @@ -1,23 +1,17 @@ let maybe = module{ val = NULL, } => { - let do = macro(op) => { - result = select (mod.val != NULL), NULL, { - true = op(mod.val).result, - } + let do = macro(op) => select (mod.val != NULL), NULL, { + true = op(mod.val), }; }; let if = module{ test = false, } => { - let do = macro(op, arg) => { - result = select mod.test, arg, { - true = op(arg).result, - }, + let do = macro(op, arg) => select mod.test, arg, { + true = op(arg), }; }; -let identity = macro(arg) => { - result = arg, -}; \ No newline at end of file +let identity = macro(arg) => arg; \ No newline at end of file diff --git a/std/lists.ucg b/std/lists.ucg index ea1fb91..85fd910 100644 --- a/std/lists.ucg +++ b/std/lists.ucg @@ -2,38 +2,32 @@ let str_join = module{ sep=" ", list=NULL, } => { - let joiner = macro(acc, item) => { - result = select (acc.out == ""), NULL, { - true = acc{ - out="@@" % (acc.out,item), - }, - false = acc{ - out="@@@" % (acc.out, acc.sep, item), - }, + let joiner = macro(acc, item) => select (acc.out == ""), NULL, { + true = acc{ + out="@@" % (acc.out,item), + }, + false = acc{ + out="@@@" % (acc.out, acc.sep, item), }, }; - let result = (reduce joiner.result {sep=mod.sep, out=""}, (mod.list)).out; + let result = (reduce joiner {sep=mod.sep, out=""}, (mod.list)).out; }; let len = module{ list = NULL, } => { - let counter = macro(acc, item) => { - result = acc + 1, - }; + let counter = macro(acc, item) => acc + 1; - let result = reduce counter.result 0, (mod.list); + let result = reduce counter 0, (mod.list); }; let reverse = module{ list = NULL, } => { - let reducer = macro(acc, item) => { - result = [item] + acc, - }; + let reducer = macro(acc, item) => [item] + acc; - let result = reduce reducer.result [], (mod.list); + let result = reduce reducer [], (mod.list); }; let enumerate = module{ @@ -41,13 +35,14 @@ let enumerate = module{ step = 1, list = NULL, } => { - let reducer = macro(acc, item) => { - result = acc{count = acc.count + acc.step, list=acc.list + [[acc.count, item]]}, + let reducer = macro(acc, item) => acc{ + count = acc.count + acc.step, + list=acc.list + [[acc.count, item]], }; let acc = {count=mod.start, list=[], step=mod.step}; - let enumerated = reduce reducer.result acc, (mod.list); + let enumerated = reduce reducer acc, (mod.list); let result = enumerated.list; }; @@ -55,23 +50,19 @@ let zip = module{ list1 = NULL, list2 = NULL, } => { - let counter = macro(acc, item) => { - result = acc + 1, - }; + let counter = macro(acc, item) => acc + 1; - let len1 = reduce counter.result 0, (mod.list1); - let len2 = reduce counter.result 0, (mod.list2); + let len1 = reduce counter 0, (mod.list1); + let len2 = reduce counter 0, (mod.list2); let rng = select (len1 >= len2), NULL, { true = 0:(len1 - 1), false = 0:(len2 - 1), }; - let reducer = macro(acc, item) => { - result = acc{ - result = acc.result + [[acc.list1.(item), acc.list2.(item)]], - idxs = acc.idxs + [item] - }, + let reducer = macro(acc, item) => acc{ + result = acc.result + [[acc.list1.(item), acc.list2.(item)]], + idxs = acc.idxs + [item] }; let acc = { @@ -81,5 +72,5 @@ let zip = module{ idxs = [], }; - let result = (reduce reducer.result acc, rng).result; + let result = (reduce reducer acc, rng).result; }; \ No newline at end of file diff --git a/std/tests/functional_test.ucg b/std/tests/functional_test.ucg index 82d0cac..b6b7486 100644 --- a/std/tests/functional_test.ucg +++ b/std/tests/functional_test.ucg @@ -1,31 +1,29 @@ let t = (import "std/testing.ucg").asserts{}; let f = (import "std/functional.ucg"); -let op = macro(arg) => { - result = arg{foo="bar"}, -}; +let op = macro(arg) => arg{foo="bar"}; assert t.equal{ - left = f.maybe{val=NULL}.do(op).result, + left = f.maybe{val=NULL}.do(op), right = NULL, }; assert t.equal{ - left = f.maybe{val={}}.do(op).result, + left = f.maybe{val={}}.do(op), right = {foo="bar"}, }; assert t.equal{ - left = f.if{test=true}.do(op, {}).result, + left = f.if{test=true}.do(op, {}), right = {foo="bar"}, }; assert t.equal{ - left = f.if{test=false}.do(op, {}).result, + left = f.if{test=false}.do(op, {}), right = {}, }; assert t.equal{ - left = f.identity("foo").result, + left = f.identity("foo"), right = "foo", }; \ No newline at end of file diff --git a/std/tuples.ucg b/std/tuples.ucg index 54157a0..056bc1d 100644 --- a/std/tuples.ucg +++ b/std/tuples.ucg @@ -3,40 +3,32 @@ let fields = module{ tpl = NULL, } => { - let reducer = macro(acc, field, value) => { - result = acc + [field], - }; + let reducer = macro(acc, field, value) => acc + [field]; - let result = reduce reducer.result [], (mod.tpl); + let result = reduce reducer [], (mod.tpl); }; // return a list of the values in a tuple. let values = module{ tpl = NULL, } => { - let reducer = macro(acc, field, value) => { - result = acc + [value], - }; + let reducer = macro(acc, field, value) => acc + [value]; - let result = reduce reducer.result [], (mod.tpl); + let result = reduce reducer [], (mod.tpl); }; let iter = module{ tpl = NULL, } => { - let reducer = macro(acc, field, value) => { - result = acc + [[field, value]], - }; + let reducer = macro(acc, field, value) => acc + [[field, value]]; - let result = reduce reducer.result [], (mod.tpl); + let result = reduce reducer [], (mod.tpl); }; let strip_nulls = module{ tpl = NULL, } => { - let filterer = macro(name, value) => { - result = value != NULL, - }; + let filterer = macro(name, value) => value != NULL; - let result = filter filterer.result (mod.tpl); + let result = filter filterer (mod.tpl); }; \ No newline at end of file