mirror of
https://github.com/zaphar/ucg.git
synced 2025-07-22 18:19:54 -04:00
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:
parent
b18d513a9f
commit
890387b4cc
@ -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
|
||||
-------
|
||||
|
||||
|
@ -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
|
||||
```
|
||||
|
||||
|
@ -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\"]}",
|
||||
};
|
@ -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",
|
||||
};
|
@ -379,7 +379,7 @@ impl<'a> From<&'a PositionedItem<String>> for PositionedItem<String> {
|
||||
pub struct MacroDef {
|
||||
pub scope: Option<Scope>,
|
||||
pub argdefs: Vec<PositionedItem<String>>,
|
||||
pub fields: FieldList,
|
||||
pub fields: Box<Expression>,
|
||||
pub pos: Position,
|
||||
}
|
||||
|
||||
@ -484,7 +484,6 @@ pub enum FuncOpDef {
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct ReduceOpDef {
|
||||
pub mac: PositionedItem<String>,
|
||||
pub field: PositionedItem<String>,
|
||||
pub acc: Box<Expression>,
|
||||
pub target: Box<Expression>,
|
||||
pub pos: Position,
|
||||
@ -494,7 +493,6 @@ pub struct ReduceOpDef {
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct MapFilterOpDef {
|
||||
pub mac: PositionedItem<String>,
|
||||
pub field: PositionedItem<String>,
|
||||
pub target: Box<Expression>,
|
||||
pub pos: Position,
|
||||
}
|
||||
|
@ -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() {
|
||||
|
162
src/build/mod.rs
162
src/build/mod.rs
@ -55,7 +55,7 @@ impl MacroDef {
|
||||
root: PathBuf,
|
||||
parent_builder: &FileBuilder,
|
||||
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
|
||||
// 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<String>, Rc<Val>)> = 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<Rc<Val>>,
|
||||
def: &MacroDef,
|
||||
outfield: &PositionedItem<String>,
|
||||
typ: ProcessingOpType,
|
||||
) -> Result<Rc<Val>, Box<dyn Error>> {
|
||||
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<String>, Rc<Val>)>,
|
||||
def: &MacroDef,
|
||||
outfield: &PositionedItem<String>,
|
||||
typ: ProcessingOpType,
|
||||
) -> Result<Rc<Val>, Box<dyn Error>> {
|
||||
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 {:?}",
|
||||
|
@ -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(
|
||||
|
@ -343,10 +343,9 @@ make_fn!(
|
||||
);
|
||||
|
||||
fn tuple_to_macro<'a>(
|
||||
input: SliceIter<'a, Token>,
|
||||
pos: Position,
|
||||
vals: Option<Vec<Value>>,
|
||||
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<Token>) -> Result<SliceIter<Token>, 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,
|
||||
})))
|
||||
|
@ -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,
|
||||
};
|
||||
let identity = macro(arg) => arg;
|
@ -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;
|
||||
};
|
@ -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",
|
||||
};
|
@ -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);
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user