mirror of
https://github.com/zaphar/ucg.git
synced 2025-07-21 18:10:42 -04:00
FEATURE: Allow map and filter on tuples.
This commit is contained in:
parent
ef01f166b2
commit
a8c4ce1157
@ -223,6 +223,86 @@ should be placed. Any primitive value can be used as an argument.
|
||||
"https://@:@/" % (host, port)
|
||||
```
|
||||
|
||||
Functional processing expressions
|
||||
---------------------------------
|
||||
|
||||
UCG has a few functional processing expressions called `map` and `filter`. Both 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.
|
||||
|
||||
### Map expressions
|
||||
|
||||
Map macros should produce in the result field a value or [field, value] that
|
||||
will replace the element or field it is curently processing.
|
||||
|
||||
**For Lists**
|
||||
|
||||
When mapping a macro across a list the result field can be any valid value. The
|
||||
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];
|
||||
```
|
||||
|
||||
**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.
|
||||
|
||||
```
|
||||
let test_tpl = {
|
||||
foo = "bar",
|
||||
quux = "baz",
|
||||
};
|
||||
let tpl_mapper = macro(name, val) => {
|
||||
result = select name, [name, val], {
|
||||
"foo" = ["foo", "barbar"],
|
||||
quux = ["cute", "pygmy"],
|
||||
},
|
||||
};
|
||||
map tpl_mapper.result test_tpl == {foo = "barbar", cute = "pygmy"};
|
||||
```
|
||||
|
||||
### Filter expressions
|
||||
|
||||
Filter expressions should return a result 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.
|
||||
|
||||
**Lists**
|
||||
|
||||
```
|
||||
let list2 = ["foo", "bar", "foo", "bar"];
|
||||
let filtrator = macro(item) => {
|
||||
result = select item, NULL, {
|
||||
foo = item,
|
||||
},
|
||||
};
|
||||
|
||||
filter filtrator.result list2 == ["foo", "foo"];
|
||||
```
|
||||
|
||||
**Tuples**
|
||||
|
||||
```
|
||||
let test_tpl = {
|
||||
foo = "bar",
|
||||
quux = "baz",
|
||||
};
|
||||
let tpl_filter = macro(name, val) => {
|
||||
result = name != "foo",
|
||||
};
|
||||
filter tpl_filter.result test_tpl == { quux = "baz" };
|
||||
```
|
||||
|
||||
Include expressions
|
||||
-------------------
|
||||
|
||||
|
@ -13,7 +13,7 @@ let boolfiltrator = macro(item) => {
|
||||
};
|
||||
|
||||
assert |
|
||||
map mapper.result list1 == [2, 3, 4, 5];
|
||||
map mapper.result list1 == [2, 3, 4, 5];
|
||||
|;
|
||||
assert |
|
||||
(map mapper.result [1, 2, 3, 4]) == [2, 3, 4, 5];
|
||||
@ -33,4 +33,46 @@ assert |
|
||||
|;
|
||||
assert |
|
||||
filter boolfiltrator.result [1, 2, 3, 4, 5, 6, 7] == [1, 2, 3, 4];
|
||||
|;
|
||||
|
||||
// Tuple processing
|
||||
let test_tpl = {
|
||||
foo = "bar",
|
||||
quux = "baz",
|
||||
};
|
||||
|
||||
let identity_tpl_mapper = macro(name, val) => {
|
||||
result = [name, val],
|
||||
};
|
||||
|
||||
assert |
|
||||
map identity_tpl_mapper.result test_tpl == test_tpl;
|
||||
|;
|
||||
|
||||
let tpl_mapper = macro(name, val) => {
|
||||
result = select name, [name, val], {
|
||||
"foo" = ["foo", "barbar"],
|
||||
quux = ["cute", "pygmy"],
|
||||
},
|
||||
};
|
||||
|
||||
assert |
|
||||
map tpl_mapper.result test_tpl == {foo = "barbar", cute = "pygmy"};
|
||||
|;
|
||||
|
||||
let identity_tpl_filter = macro(name, val) => {
|
||||
result = true,
|
||||
};
|
||||
|
||||
// strip out foo field
|
||||
let tpl_filter = macro(name, val) => {
|
||||
result = name != "foo",
|
||||
};
|
||||
|
||||
assert |
|
||||
filter identity_tpl_filter.result test_tpl == test_tpl;
|
||||
|;
|
||||
|
||||
assert |
|
||||
filter tpl_filter.result test_tpl == { quux = "baz" };
|
||||
|;
|
@ -1113,7 +1113,7 @@ impl<'a> FileBuilder<'a> {
|
||||
}
|
||||
|
||||
fn eval_functional_list_processing(
|
||||
&mut self,
|
||||
&self,
|
||||
elems: &Vec<Rc<Val>>,
|
||||
def: &MacroDef,
|
||||
outfield: &PositionedItem<String>,
|
||||
@ -1144,6 +1144,77 @@ impl<'a> FileBuilder<'a> {
|
||||
return Ok(Rc::new(Val::List(out)));
|
||||
}
|
||||
|
||||
fn eval_functional_tuple_processing(
|
||||
&self,
|
||||
fs: &Vec<(PositionedItem<String>, Rc<Val>)>,
|
||||
def: &MacroDef,
|
||||
outfield: &PositionedItem<String>,
|
||||
typ: &ListOpType,
|
||||
) -> 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 {
|
||||
ListOpType::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(),
|
||||
));
|
||||
} 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()
|
||||
),
|
||||
error::ErrorType::TypeFail,
|
||||
def.pos.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()
|
||||
),
|
||||
error::ErrorType::TypeFail,
|
||||
def.pos.clone(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
ListOpType::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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Rc::new(Val::Tuple(out)))
|
||||
}
|
||||
|
||||
fn eval_functional_processing(
|
||||
&mut self,
|
||||
def: &ListOpDef,
|
||||
@ -1165,6 +1236,9 @@ impl<'a> FileBuilder<'a> {
|
||||
&Val::List(ref elems) => {
|
||||
self.eval_functional_list_processing(elems, macdef, &def.field, &def.typ)
|
||||
}
|
||||
&Val::Tuple(ref fs) => {
|
||||
self.eval_functional_tuple_processing(fs, macdef, &def.field, &def.typ)
|
||||
}
|
||||
other => Err(Box::new(error::BuildError::new(
|
||||
format!("Expected List as target but got {:?}", other.type_name()),
|
||||
error::ErrorType::TypeFail,
|
||||
|
Loading…
x
Reference in New Issue
Block a user