FEATURE: Macros are just an expression now.

This is a breaking change for the map, filter, and reduce expressions
but happily a forward compatible change for macros themselves.
This commit is contained in:
Jeremy Wall 2019-01-16 19:07:03 -06:00
parent b18d513a9f
commit 890387b4cc
13 changed files with 238 additions and 348 deletions

View File

@ -249,19 +249,46 @@ UCG can generate lists from a range with an optional step.
0:2:10 == [0, 2, 4, 6, 8, 10]; 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 Functional processing expressions
--------------------------------- ---------------------------------
UCG has a few functional processing expressions called `map` and `filter`. Both of UCG has a few functional processing expressions called `map`, `filter`, and
them can process a list or tuple. `reduce`. All of them can process a list or tuple.
Their syntax starts with either map or filter followed by a symbol that Their syntax starts with either `map` `filter`, or `reduce followed by a symbol
references a valid macro and the outfield for the tuple the macro produces. and that references a valid macro and finally an expression that resolves to either
finally an expression that resolves to either a list of tuple. a list or a tuple.
### Map expressions ### 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. will replace the element or field it is curently processing.
**For Lists** **For Lists**
@ -272,34 +299,32 @@ macro is expected to take a single argument.
``` ```
let list1 = [1, 2, 3, 4]; let list1 = [1, 2, 3, 4];
let mapper = macro(item) => { result = item + 1 }; let mapper = macro(item) => item + 1;
map mapper.result list1 == [2, 3, 4, 5]; map mapper list1 == [2, 3, 4, 5];
``` ```
**For Tuples** **For Tuples**
Macros for mapping across a tuple are expected to take two arguments. The first 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 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 field. The result should be a two item list with the first item being the new
new field name and the second item being the new value. field name and the second item being the new value.
``` ```
let test_tpl = { let test_tpl = {
foo = "bar", foo = "bar",
quux = "baz", quux = "baz",
}; };
let tpl_mapper = macro(name, val) => { let tpl_mapper = macro(name, val) => select name, [name, val], {
result = select name, [name, val], { "foo" = ["foo", "barbar"],
"foo" = ["foo", "barbar"], quux = ["cute", "pygmy"],
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
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 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. 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 list2 = ["foo", "bar", "foo", "bar"];
let filtrator = macro(item) => { let filtrator = macro(item) => select item, NULL, {
result = select item, NULL, { foo = item,
foo = item,
},
}; };
filter filtrator.result list2 == ["foo", "foo"]; filter filtrator.result list2 == ["foo", "foo"];
@ -323,15 +346,13 @@ let test_tpl = {
foo = "bar", foo = "bar",
quux = "baz", quux = "baz",
}; };
let tpl_filter = macro(name, val) => { let tpl_filter = macro(name, val) => name != "foo";
result = name != "foo", filter tpl_filter test_tpl == { quux = "baz" };
};
filter tpl_filter.result test_tpl == { quux = "baz" };
``` ```
### Reduce expressions ### 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** **Tuples**
@ -340,25 +361,21 @@ let test_tpl = {
foo = "bar", foo = "bar",
quux = "baz", quux = "baz",
}; };
let tpl_reducer = macro(acc, name, val) => { let tpl_reducer = macro(acc, name, val) => acc{
result = acc{ keys = self.keys + [name],
keys = self.keys + [name], vals = self.vals + [val],
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** **Lists**
``` ```
let list1 = [1, 2, 3, 4]; let list1 = [1, 2, 3, 4];
let list_reducer = macro(acc, item) => { let list_reducer = macro(acc, item) => acc + item;
result = acc + item,
};
list_reducer.result 0, list1 == 0 + 1 + 2 + 3 + 4; list_reducer 0, list1 == 0 + 1 + 2 + 3 + 4;
``` ```
Include expressions Include expressions
@ -403,31 +420,6 @@ let ifresult = select true, NULL, {
}; // result will be "true result" }; // 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 Modules
------- -------

View File

@ -136,8 +136,8 @@ format_expr: str, percent, lparen, [arglist], rparen ;
``` ```
func_op_kind: map_keyword | filter_keyword ; func_op_kind: map_keyword | filter_keyword ;
map_or_filter_expr: func_op_kind, bareword, dot, bareword, expr ; map_or_filter_expr: func_op_kind, bareword, expr ;
reduce_expr: reduce_keyword, bareword, dot, bareword, expr, expr ; reduce_expr: reduce_keyword, bareword, expr, expr ;
processing_expr: map_or_filter_expr | reduce_expr processing_expr: map_or_filter_expr | reduce_expr
``` ```

View File

@ -3,24 +3,18 @@
let list1 = [1, 2, 3, 4]; let list1 = [1, 2, 3, 4];
let list2 = ["foo", "bar", "foo", "bar"]; let list2 = ["foo", "bar", "foo", "bar"];
let mapper = macro(item) => { result = item + 1 }; let mapper = macro(item) => item + 1;
let filtrator = macro(item) => { let filtrator = macro(item) => select item, NULL, {
result = select item, NULL, {
foo = item, foo = item,
},
}; };
let boolfiltrator = macro(item) => { let boolfiltrator = macro(item) => item < 5;
result = item < 5,
};
let identity_list_reducer = macro(acc, item) => { let identity_list_reducer = macro(acc, item) => acc + [item];
result = acc + [item],
};
assert { assert {
ok = reduce identity_list_reducer.result [], list1 == list1, ok = reduce identity_list_reducer [], list1 == list1,
desc = "reduce identity_list_reducer.result [], list1 == list1", desc = "reduce identity_list_reducer [], list1 == list1",
}; };
let nested_list = { let nested_list = {
@ -28,47 +22,46 @@ let nested_list = {
}; };
assert { assert {
ok = reduce identity_list_reducer.result [], (nested_list.list) == list1, ok = reduce identity_list_reducer [], (nested_list.list) == list1,
desc = "reduce identity_list_reducer.result [], (nested_list.list) == list1", desc = "reduce identity_list_reducer [], (nested_list.list) == list1",
}; };
let list_reducer = macro(acc, item) => { let list_reducer = macro(acc, item) => acc + item;
result = 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 { assert {
ok = reduce list_reducer.result 0, list1 == 0 + 1 + 2 + 3 + 4, ok = map mapper list1 == [2, 3, 4, 5],
desc = "reduce list_reducer.result 0, list1 == 0 + 1 + 2 + 3 + 4", 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 { assert {
ok = map mapper.result list1 == [2, 3, 4, 5], ok = filter filtrator list2 == ["foo", "foo"],
desc = "map mapper.result list1 == [2, 3, 4, 5]", desc = "filter filtrator list2 == [\"foo\", \"foo\"]",
};
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]",
}; };
assert { assert {
ok = filter filtrator.result list2 == ["foo", "foo"], ok = (filter filtrator ["foo", "bar", "foo", "bar"]) == ["foo", "foo"],
desc = "filter filtrator.result list2 == [\"foo\", \"foo\"]", desc = "(filter filtrator [\"foo\", \"bar\", \"foo\", \"bar\"]) == [\"foo\", \"foo\"]",
}; };
assert { assert {
ok = (filter filtrator.result ["foo", "bar", "foo", "bar"]) == ["foo", "foo"], ok = filter filtrator ["foo", "bar", "foo", "bar"] == ["foo", "foo"],
desc = "(filter filtrator.result [\"foo\", \"bar\", \"foo\", \"bar\"]) == [\"foo\", \"foo\"]", desc = "filter filtrator [\"foo\", \"bar\", \"foo\", \"bar\"] == [\"foo\", \"foo\"]",
}; };
assert { assert {
ok = filter filtrator.result ["foo", "bar", "foo", "bar"] == ["foo", "foo"], ok = filter boolfiltrator [1, 2, 3, 4, 5, 6, 7] == [1, 2, 3, 4],
desc = "filter filtrator.result [\"foo\", \"bar\", \"foo\", \"bar\"] == [\"foo\", \"foo\"]", desc = "filter boolfiltrator [1, 2, 3, 4, 5, 6, 7] == [1, 2, 3, 4]",
};
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]",
}; };
// Tuple processing // Tuple processing
@ -77,54 +70,44 @@ let test_tpl = {
quux = "baz", quux = "baz",
}; };
let identity_tpl_mapper = macro(name, val) => { let identity_tpl_mapper = macro(name, val) => [name, val];
result = [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 { assert {
ok = map identity_tpl_mapper.result test_tpl == test_tpl, ok = map tpl_mapper test_tpl == {foo = "barbar", cute = "pygmy"},
desc = "map identity_tpl_mapper.result test_tpl == test_tpl", desc = "map tpl_mapper test_tpl == {foo = \"barbar\", cute = \"pygmy\"}",
}; };
let tpl_mapper = macro(name, val) => { let identity_tpl_filter = macro(name, val) => true;
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,
};
// strip out foo field // strip out foo field
let tpl_filter = macro(name, val) => { let tpl_filter = macro(name, val) => name != "foo";
result = name != "foo",
assert {
ok = filter identity_tpl_filter test_tpl == test_tpl,
desc = "filter identity_tpl_filter == test_tpl",
}; };
assert { assert {
ok = filter identity_tpl_filter.result test_tpl == test_tpl, ok = filter tpl_filter test_tpl == { quux = "baz" },
desc = "filter identity_tpl_filter.result test_tpl == test_tpl", desc = "filter tpl_filter test_tpl == { quux = \"baz\" }",
}; };
assert { let tpl_reducer = macro(acc, name, val) => acc{
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{
keys = self.keys + [name], keys = self.keys + [name],
vals = self.vals + [val], vals = self.vals + [val],
},
}; };
assert { assert {
ok = 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.result {keys = [], vals = []}, test_tpl == {keys = [\"foo\", \"quux\"], vals = [\"bar\", \"baz\"]}", desc = "reduce tpl_reducer {keys = [], vals = []}, test_tpl == {keys = [\"foo\", \"quux\"], vals = [\"bar\", \"baz\"]}",
}; };

View File

@ -75,4 +75,11 @@ let closure = macro(arg1) => {
assert { assert {
ok = closure("bar").result == "foobar", ok = closure("bar").result == "foobar",
desc = "we closed over closed_over and got @" % (closure("bar").result), 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",
}; };

View File

@ -379,7 +379,7 @@ impl<'a> From<&'a PositionedItem<String>> for PositionedItem<String> {
pub struct MacroDef { pub struct MacroDef {
pub scope: Option<Scope>, pub scope: Option<Scope>,
pub argdefs: Vec<PositionedItem<String>>, pub argdefs: Vec<PositionedItem<String>>,
pub fields: FieldList, pub fields: Box<Expression>,
pub pos: Position, pub pos: Position,
} }
@ -484,7 +484,6 @@ pub enum FuncOpDef {
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct ReduceOpDef { pub struct ReduceOpDef {
pub mac: PositionedItem<String>, pub mac: PositionedItem<String>,
pub field: PositionedItem<String>,
pub acc: Box<Expression>, pub acc: Box<Expression>,
pub target: Box<Expression>, pub target: Box<Expression>,
pub pos: Position, pub pos: Position,
@ -494,7 +493,6 @@ pub struct ReduceOpDef {
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct MapFilterOpDef { pub struct MapFilterOpDef {
pub mac: PositionedItem<String>, pub mac: PositionedItem<String>,
pub field: PositionedItem<String>,
pub target: Box<Expression>, pub target: Box<Expression>,
pub pos: Position, pub pos: Position,
} }

View File

@ -89,7 +89,7 @@ impl<'a> AstWalker<'a> {
Expression::Grouped(ref mut expr) => { Expression::Grouped(ref mut expr) => {
self.walk_expression(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) => { Expression::Module(ref mut def) => {
self.walk_fieldset(&mut def.arg_set); self.walk_fieldset(&mut def.arg_set);
for stmt in def.statements.iter_mut() { for stmt in def.statements.iter_mut() {

View File

@ -55,7 +55,7 @@ impl MacroDef {
root: PathBuf, root: PathBuf,
parent_builder: &FileBuilder, parent_builder: &FileBuilder,
mut args: Vec<Rc<Val>>, mut args: Vec<Rc<Val>>,
) -> Result<Vec<(PositionedItem<String>, Rc<Val>)>, Box<dyn Error>> { ) -> Result<Rc<Val>, Box<dyn Error>> {
// Error conditions. If the args don't match the length and types of the argdefs then this is // Error conditions. If the args don't match the length and types of the argdefs then this is
// macro call error. // macro call error.
if args.len() > self.argdefs.len() { 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. // We clobber anything that used to be in the scope with the arguments.
b.merge_build_output(build_output, true); b.merge_build_output(build_output, true);
let mut result: Vec<(PositionedItem<String>, Rc<Val>)> = Vec::new(); Ok(b.eval_expr(self.fields.as_ref(), &b.scope.spawn_child())?)
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)
} }
} }
@ -1112,8 +1104,7 @@ impl<'a> FileBuilder<'a> {
for arg in args.iter() { for arg in args.iter() {
argvals.push(self.eval_expr(arg, scope)?); argvals.push(self.eval_expr(arg, scope)?);
} }
let fields = m.eval(self.file.clone(), self, argvals)?; return Ok(m.eval(self.file.clone(), self, argvals)?);
return Ok(Rc::new(Val::Tuple(fields)));
} }
Err(Box::new(error::BuildError::new( Err(Box::new(error::BuildError::new(
// We should pretty print the selectors here. // We should pretty print the selectors here.
@ -1195,28 +1186,25 @@ impl<'a> FileBuilder<'a> {
&self, &self,
elems: &Vec<Rc<Val>>, elems: &Vec<Rc<Val>>,
def: &MacroDef, def: &MacroDef,
outfield: &PositionedItem<String>,
typ: ProcessingOpType, typ: ProcessingOpType,
) -> Result<Rc<Val>, Box<dyn Error>> { ) -> Result<Rc<Val>, Box<dyn Error>> {
let mut out = Vec::new(); let mut out = Vec::new();
for item in elems.iter() { for item in elems.iter() {
let argvals = vec![item.clone()]; let argvals = vec![item.clone()];
let fields = def.eval(self.file.clone(), self, argvals)?; let val = def.eval(self.file.clone(), self, argvals)?;
if let Some(v) = find_in_fieldlist(&outfield.val, &fields) { match typ {
match typ { ProcessingOpType::Map => {
ProcessingOpType::Map => { out.push(val.clone());
out.push(v.clone()); }
} ProcessingOpType::Filter => {
ProcessingOpType::Filter => { if let &Val::Empty = val.as_ref() {
if let &Val::Empty = v.as_ref() { // noop
// noop continue;
continue; } else if let &Val::Boolean(false) = val.as_ref() {
} else if let &Val::Boolean(false) = v.as_ref() { // noop
// noop continue;
continue;
}
out.push(item.clone());
} }
out.push(item.clone());
} }
} }
} }
@ -1227,77 +1215,65 @@ impl<'a> FileBuilder<'a> {
&self, &self,
fs: &Vec<(PositionedItem<String>, Rc<Val>)>, fs: &Vec<(PositionedItem<String>, Rc<Val>)>,
def: &MacroDef, def: &MacroDef,
outfield: &PositionedItem<String>,
typ: ProcessingOpType, typ: ProcessingOpType,
) -> Result<Rc<Val>, Box<dyn Error>> { ) -> Result<Rc<Val>, Box<dyn Error>> {
let mut out = Vec::new(); let mut out = Vec::new();
for &(ref name, ref val) in fs { for &(ref name, ref val) in fs {
let argvals = vec![Rc::new(Val::Str(name.val.clone())), val.clone()]; let argvals = vec![Rc::new(Val::Str(name.val.clone())), val.clone()];
let fields = def.eval(self.file.clone(), self, argvals)?; let result = def.eval(self.file.clone(), self, argvals)?;
if let Some(v) = find_in_fieldlist(&outfield.val, &fields) { match typ {
match typ { ProcessingOpType::Map => {
ProcessingOpType::Map => { if let &Val::List(ref fs) = result.as_ref() {
if let &Val::List(ref fs) = v.as_ref() { if fs.len() == 2 {
if fs.len() == 2 { // index 0 should be a string for the new field name.
// index 0 should be a string for the new field name. // index 1 should be the val.
// index 1 should be the val. let new_name = if let &Val::Str(ref s) = fs[0].as_ref() {
let new_name = if let &Val::Str(ref s) = fs[0].as_ref() { s.clone()
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(),
));
} else { } else {
return Err(Box::new(error::BuildError::new( return Err(Box::new(error::BuildError::new(
format!( format!(
"map on a tuple field expects a list of size 2 as output but got size {}", "map on tuple expects the first item out list to be a string but got size {}",
fs.len() fs[0].type_name()
), ),
error::ErrorType::TypeFail, error::ErrorType::TypeFail,
def.pos.clone(), def.pos.clone(),
))); )));
} };
out.push((
PositionedItem::new(new_name, name.pos.clone()),
fs[1].clone(),
));
} else { } else {
return Err(Box::new(error::BuildError::new( return Err(Box::new(error::BuildError::new(
format!( format!(
"map on a tuple field expects a list as output but got {:?}", "map on a tuple field expects a list of size 2 as output but got size {}",
v.type_name() fs.len()
), ),
error::ErrorType::TypeFail, error::ErrorType::TypeFail,
def.pos.clone(), def.pos.clone(),
))); )));
} }
} } else {
ProcessingOpType::Filter => { return Err(Box::new(error::BuildError::new(
if let &Val::Empty = v.as_ref() { format!(
// noop "map on a tuple field expects a list as output but got {:?}",
continue; result.type_name()
} else if let &Val::Boolean(false) = v.as_ref() { ),
// noop error::ErrorType::TypeFail,
continue; def.pos.clone(),
} )));
out.push((name.clone(), val.clone()));
} }
} }
} else { ProcessingOpType::Filter => {
return Err(Box::new(error::BuildError::new( if let &Val::Empty = result.as_ref() {
format!( // noop
"Result {} field does not exist in macro body!", continue;
outfield.val } else if let &Val::Boolean(false) = result.as_ref() {
), // noop
error::ErrorType::NoSuchSymbol, continue;
def.pos.clone(), }
))); out.push((name.clone(), val.clone()));
}
} }
} }
Ok(Rc::new(Val::Tuple(out))) Ok(Rc::new(Val::Tuple(out)))
@ -1321,16 +1297,8 @@ impl<'a> FileBuilder<'a> {
&Val::List(ref elems) => { &Val::List(ref elems) => {
for item in elems.iter() { for item in elems.iter() {
let argvals = vec![acc.clone(), item.clone()]; let argvals = vec![acc.clone(), item.clone()];
let fields = macdef.eval(self.file.clone(), self, argvals)?; let result = macdef.eval(self.file.clone(), self, argvals)?;
if let Some(v) = find_in_fieldlist(&def.field.val, &fields) { acc = result;
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(),
)));
}
} }
} }
&Val::Tuple(ref fs) => { &Val::Tuple(ref fs) => {
@ -1340,16 +1308,8 @@ impl<'a> FileBuilder<'a> {
Rc::new(Val::Str(name.val.clone())), Rc::new(Val::Str(name.val.clone())),
val.clone(), val.clone(),
]; ];
let fields = macdef.eval(self.file.clone(), self, argvals)?; let result = macdef.eval(self.file.clone(), self, argvals)?;
if let Some(v) = find_in_fieldlist(&def.field.val, &fields) { acc = result;
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(),
)));
}
} }
} }
other => { other => {
@ -1385,12 +1345,8 @@ impl<'a> FileBuilder<'a> {
} }
}; };
return match maybe_target.as_ref() { return match maybe_target.as_ref() {
&Val::List(ref elems) => { &Val::List(ref elems) => self.eval_functional_list_processing(elems, macdef, typ),
self.eval_functional_list_processing(elems, macdef, &def.field, typ) &Val::Tuple(ref fs) => self.eval_functional_tuple_processing(fs, macdef, typ),
}
&Val::Tuple(ref fs) => {
self.eval_functional_tuple_processing(fs, macdef, &def.field, typ)
}
other => Err(Box::new(error::BuildError::new( other => Err(Box::new(error::BuildError::new(
format!( format!(
"Expected List or Tuple as target but got {:?}", "Expected List or Tuple as target but got {:?}",

View File

@ -250,13 +250,10 @@ fn test_macro_hermetic() {
.or_insert(Rc::new(Val::Macro(MacroDef { .or_insert(Rc::new(Val::Macro(MacroDef {
scope: None, scope: None,
argdefs: vec![value_node!("arg2".to_string(), Position::new(1, 0, 0))], argdefs: vec![value_node!("arg2".to_string(), Position::new(1, 0, 0))],
fields: vec![( fields: Box::new(Expression::Simple(Value::Symbol(value_node!(
make_tok!("foo", Position::new(1, 1, 1)), "arg1".to_string(),
Expression::Simple(Value::Symbol(value_node!( Position::new(1, 1, 1)
"arg1".to_string(), )))),
Position::new(1, 1, 1)
))),
)],
pos: Position::new(1, 0, 0), pos: Position::new(1, 0, 0),
}))); })));
test_expr_to_val( test_expr_to_val(

View File

@ -343,10 +343,9 @@ make_fn!(
); );
fn tuple_to_macro<'a>( fn tuple_to_macro<'a>(
input: SliceIter<'a, Token>,
pos: Position, pos: Position,
vals: Option<Vec<Value>>, vals: Option<Vec<Value>>,
val: Value, val: Expression,
) -> ConvertResult<'a, Expression> { ) -> ConvertResult<'a, Expression> {
let mut default_args = match vals { let mut default_args = match vals {
None => Vec::new(), None => Vec::new(),
@ -359,18 +358,12 @@ fn tuple_to_macro<'a>(
val: s.to_string(), val: s.to_string(),
}) })
.collect(); .collect();
match val { Ok(Expression::Macro(MacroDef {
Value::Tuple(v) => Ok(Expression::Macro(MacroDef { scope: None,
scope: None, argdefs: arglist,
argdefs: arglist, fields: Box::new(val),
fields: v.val, pos: pos,
pos: pos, }))
})),
val => Err(Error::new(
format!("Expected Tuple Got {:?}", val),
Box::new(input.clone()),
)),
}
} }
make_fn!( make_fn!(
@ -417,23 +410,21 @@ fn macro_expression(input: SliceIter<Token>) -> Result<SliceIter<Token>, Express
arglist => trace_parse!(optional!(arglist)), arglist => trace_parse!(optional!(arglist)),
_ => must!(punct!(")")), _ => must!(punct!(")")),
_ => must!(punct!("=>")), _ => must!(punct!("=>")),
map => trace_parse!(tuple), map => trace_parse!(expression),
(pos, arglist, map) (pos, arglist, map)
); );
match parsed { match parsed {
Result::Abort(e) => Result::Abort(e), Result::Abort(e) => Result::Abort(e),
Result::Fail(e) => Result::Fail(e), Result::Fail(e) => Result::Fail(e),
Result::Incomplete(offset) => Result::Incomplete(offset), Result::Incomplete(offset) => Result::Incomplete(offset),
Result::Complete(rest, (pos, arglist, map)) => { Result::Complete(rest, (pos, arglist, map)) => match tuple_to_macro(pos, arglist, map) {
match tuple_to_macro(rest.clone(), pos, arglist, map) { Ok(expr) => Result::Complete(rest, expr),
Ok(expr) => Result::Complete(rest, expr), Err(e) => Result::Fail(Error::caused_by(
Err(e) => Result::Fail(Error::caused_by( "Invalid Macro syntax",
"Invalid Macro syntax", Box::new(e),
Box::new(e), Box::new(rest.clone()),
Box::new(rest.clone()), )),
)), },
}
}
} }
} }
@ -574,14 +565,11 @@ make_fn!(
pos => pos, pos => pos,
_ => word!("reduce"), _ => word!("reduce"),
macroname => match_type!(BAREWORD), macroname => match_type!(BAREWORD),
_ => punct!("."),
outfield => match_type!(BAREWORD),
acc => trace_parse!(non_op_expression), acc => trace_parse!(non_op_expression),
_ => punct!(","), _ => punct!(","),
tgt => trace_parse!(non_op_expression), tgt => trace_parse!(non_op_expression),
(Expression::FuncOp(FuncOpDef::Reduce(ReduceOpDef{ (Expression::FuncOp(FuncOpDef::Reduce(ReduceOpDef{
mac: (&macroname).into(), mac: (&macroname).into(),
field: (&outfield).into(),
acc: Box::new(acc), acc: Box::new(acc),
target: Box::new(tgt), target: Box::new(tgt),
pos: pos, pos: pos,
@ -596,12 +584,9 @@ make_fn!(
_ => word!("map"), _ => word!("map"),
// TODO This should become just a bareword symbol now // TODO This should become just a bareword symbol now
macroname => match_type!(BAREWORD), macroname => match_type!(BAREWORD),
_ => punct!("."),
outfield => match_type!(BAREWORD),
list => trace_parse!(non_op_expression), list => trace_parse!(non_op_expression),
(Expression::FuncOp(FuncOpDef::Map(MapFilterOpDef{ (Expression::FuncOp(FuncOpDef::Map(MapFilterOpDef{
mac: (&macroname).into(), mac: (&macroname).into(),
field: (&outfield).into(),
target: Box::new(list), target: Box::new(list),
pos: pos, pos: pos,
}))) })))
@ -615,12 +600,9 @@ make_fn!(
_ => word!("filter"), _ => word!("filter"),
// TODO This should become just a bareword symbol now // TODO This should become just a bareword symbol now
macroname => match_type!(BAREWORD), macroname => match_type!(BAREWORD),
_ => punct!("."),
outfield => match_type!(BAREWORD),
list => trace_parse!(non_op_expression), list => trace_parse!(non_op_expression),
(Expression::FuncOp(FuncOpDef::Filter(MapFilterOpDef{ (Expression::FuncOp(FuncOpDef::Filter(MapFilterOpDef{
mac: (&macroname).into(), mac: (&macroname).into(),
field: (&outfield).into(),
target: Box::new(list), target: Box::new(list),
pos: pos, pos: pos,
}))) })))

View File

@ -1,23 +1,17 @@
let maybe = module{ let maybe = module{
val = NULL, val = NULL,
} => { } => {
let do = macro(op) => { let do = macro(op) => select (mod.val != NULL), NULL, {
result = select (mod.val != NULL), NULL, { true = op(mod.val),
true = op(mod.val).result,
}
}; };
}; };
let if = module{ let if = module{
test = false, test = false,
} => { } => {
let do = macro(op, arg) => { let do = macro(op, arg) => select mod.test, arg, {
result = select mod.test, arg, { true = op(arg),
true = op(arg).result,
},
}; };
}; };
let identity = macro(arg) => { let identity = macro(arg) => arg;
result = arg,
};

View File

@ -2,38 +2,32 @@ let str_join = module{
sep=" ", sep=" ",
list=NULL, list=NULL,
} => { } => {
let joiner = macro(acc, item) => { let joiner = macro(acc, item) => select (acc.out == ""), NULL, {
result = select (acc.out == ""), NULL, { true = acc{
true = acc{ out="@@" % (acc.out,item),
out="@@" % (acc.out,item), },
}, false = acc{
false = acc{ out="@@@" % (acc.out, acc.sep, item),
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{ let len = module{
list = NULL, list = NULL,
} => { } => {
let counter = macro(acc, item) => { let counter = macro(acc, item) => acc + 1;
result = acc + 1,
};
let result = reduce counter.result 0, (mod.list); let result = reduce counter 0, (mod.list);
}; };
let reverse = module{ let reverse = module{
list = NULL, list = NULL,
} => { } => {
let reducer = macro(acc, item) => { let reducer = macro(acc, item) => [item] + acc;
result = [item] + acc,
};
let result = reduce reducer.result [], (mod.list); let result = reduce reducer [], (mod.list);
}; };
let enumerate = module{ let enumerate = module{
@ -41,13 +35,14 @@ let enumerate = module{
step = 1, step = 1,
list = NULL, list = NULL,
} => { } => {
let reducer = macro(acc, item) => { let reducer = macro(acc, item) => acc{
result = acc{count = acc.count + acc.step, list=acc.list + [[acc.count, item]]}, count = acc.count + acc.step,
list=acc.list + [[acc.count, item]],
}; };
let acc = {count=mod.start, list=[], step=mod.step}; 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; let result = enumerated.list;
}; };
@ -55,23 +50,19 @@ let zip = module{
list1 = NULL, list1 = NULL,
list2 = NULL, list2 = NULL,
} => { } => {
let counter = macro(acc, item) => { let counter = macro(acc, item) => acc + 1;
result = acc + 1,
};
let len1 = reduce counter.result 0, (mod.list1); let len1 = reduce counter 0, (mod.list1);
let len2 = reduce counter.result 0, (mod.list2); let len2 = reduce counter 0, (mod.list2);
let rng = select (len1 >= len2), NULL, { let rng = select (len1 >= len2), NULL, {
true = 0:(len1 - 1), true = 0:(len1 - 1),
false = 0:(len2 - 1), false = 0:(len2 - 1),
}; };
let reducer = macro(acc, item) => { let reducer = macro(acc, item) => acc{
result = acc{ result = acc.result + [[acc.list1.(item), acc.list2.(item)]],
result = acc.result + [[acc.list1.(item), acc.list2.(item)]], idxs = acc.idxs + [item]
idxs = acc.idxs + [item]
},
}; };
let acc = { let acc = {
@ -81,5 +72,5 @@ let zip = module{
idxs = [], idxs = [],
}; };
let result = (reduce reducer.result acc, rng).result; let result = (reduce reducer acc, rng).result;
}; };

View File

@ -1,31 +1,29 @@
let t = (import "std/testing.ucg").asserts{}; let t = (import "std/testing.ucg").asserts{};
let f = (import "std/functional.ucg"); let f = (import "std/functional.ucg");
let op = macro(arg) => { let op = macro(arg) => arg{foo="bar"};
result = arg{foo="bar"},
};
assert t.equal{ assert t.equal{
left = f.maybe{val=NULL}.do(op).result, left = f.maybe{val=NULL}.do(op),
right = NULL, right = NULL,
}; };
assert t.equal{ assert t.equal{
left = f.maybe{val={}}.do(op).result, left = f.maybe{val={}}.do(op),
right = {foo="bar"}, right = {foo="bar"},
}; };
assert t.equal{ assert t.equal{
left = f.if{test=true}.do(op, {}).result, left = f.if{test=true}.do(op, {}),
right = {foo="bar"}, right = {foo="bar"},
}; };
assert t.equal{ assert t.equal{
left = f.if{test=false}.do(op, {}).result, left = f.if{test=false}.do(op, {}),
right = {}, right = {},
}; };
assert t.equal{ assert t.equal{
left = f.identity("foo").result, left = f.identity("foo"),
right = "foo", right = "foo",
}; };

View File

@ -3,40 +3,32 @@
let fields = module{ let fields = module{
tpl = NULL, tpl = NULL,
} => { } => {
let reducer = macro(acc, field, value) => { let reducer = macro(acc, field, value) => acc + [field];
result = acc + [field],
};
let result = reduce reducer.result [], (mod.tpl); let result = reduce reducer [], (mod.tpl);
}; };
// return a list of the values in a tuple. // return a list of the values in a tuple.
let values = module{ let values = module{
tpl = NULL, tpl = NULL,
} => { } => {
let reducer = macro(acc, field, value) => { let reducer = macro(acc, field, value) => acc + [value];
result = acc + [value],
};
let result = reduce reducer.result [], (mod.tpl); let result = reduce reducer [], (mod.tpl);
}; };
let iter = module{ let iter = module{
tpl = NULL, tpl = NULL,
} => { } => {
let reducer = macro(acc, field, value) => { let reducer = macro(acc, field, value) => acc + [[field, value]];
result = acc + [[field, value]],
};
let result = reduce reducer.result [], (mod.tpl); let result = reduce reducer [], (mod.tpl);
}; };
let strip_nulls = module{ let strip_nulls = module{
tpl = NULL, tpl = NULL,
} => { } => {
let filterer = macro(name, value) => { let filterer = macro(name, value) => value != NULL;
result = value != NULL,
};
let result = filter filterer.result (mod.tpl); let result = filter filterer (mod.tpl);
}; };