FEATURE: Add a reduce operator for lists and tuples.

fixes #19
fixes #21
This commit is contained in:
Jeremy Wall 2019-01-06 20:56:08 -06:00
parent 292a4a810e
commit e46496c666
6 changed files with 246 additions and 105 deletions

View File

@ -1,3 +1,5 @@
// List processing
let list1 = [1, 2, 3, 4]; let list1 = [1, 2, 3, 4];
let list2 = ["foo", "bar", "foo", "bar"]; let list2 = ["foo", "bar", "foo", "bar"];
@ -12,6 +14,22 @@ let boolfiltrator = macro(item) => {
result = item < 5, result = item < 5,
}; };
let identity_list_reducer = macro(acc, item) => {
result = acc + [item],
};
assert |
reduce identity_list_reducer.result [], list1 == list1;
|;
let list_reducer = macro(acc, item) => {
result = acc + item,
};
assert |
reduce list_reducer.result 0, list1 == 0 + 1 + 2 + 3 + 4;
|;
assert | assert |
map mapper.result list1 == [2, 3, 4, 5]; map mapper.result list1 == [2, 3, 4, 5];
|; |;
@ -75,4 +93,15 @@ assert |
assert | assert |
filter tpl_filter.result test_tpl == { quux = "baz" }; filter tpl_filter.result test_tpl == { quux = "baz" };
|;
let tpl_reducer = macro(acc, name, val) => {
result = acc{
keys = self.keys + [name],
vals = self.vals + [val],
},
};
assert |
reduce tpl_reducer.result {keys = [], vals = []}, test_tpl == {keys = ["foo", "quux"], vals = ["bar", "baz"]};
|; |;

View File

@ -397,7 +397,7 @@ impl MacroDef {
) -> HashSet<String> { ) -> HashSet<String> {
let mut bad_symbols = HashSet::new(); let mut bad_symbols = HashSet::new();
if let &Value::Symbol(ref name) = val { if let &Value::Symbol(ref name) = val {
if !self.symbol_is_in_args(&name.val) { if name.val != "self" && !self.symbol_is_in_args(&name.val) {
bad_symbols.insert(name.val.clone()); bad_symbols.insert(name.val.clone());
} }
} else if let &Value::Tuple(ref tuple_node) = val { } else if let &Value::Tuple(ref tuple_node) = val {
@ -442,12 +442,6 @@ impl MacroDef {
stack.push(expr); stack.push(expr);
} }
} }
&Expression::Copy(ref def) => {
let fields = &def.fields;
for &(_, ref expr) in fields.iter() {
stack.push(expr);
}
}
&Expression::Call(ref def) => { &Expression::Call(ref def) => {
for expr in def.arglist.iter() { for expr in def.arglist.iter() {
stack.push(expr); stack.push(expr);
@ -458,8 +452,9 @@ impl MacroDef {
bad_symbols.extend(syms_set.drain()); bad_symbols.extend(syms_set.drain());
} }
&Expression::Macro(_) &Expression::Macro(_)
| &Expression::Copy(_)
| &Expression::Module(_) | &Expression::Module(_)
| &Expression::ListOp(_) | &Expression::FuncOp(_)
| &Expression::Include(_) => { | &Expression::Include(_) => {
// noop // noop
continue; continue;
@ -561,23 +556,41 @@ pub struct ListDef {
pub pos: Position, pub pos: Position,
} }
/// ListOpType represents the type of list operation for a ListOpDef.
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum ListOpType { pub enum FuncOpDef {
Map, Reduce(ReduceOpDef),
Filter, Map(MapFilterOpDef),
Filter(MapFilterOpDef),
} }
/// ListOpDef implements the list operations in the UCG AST.
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct ListOpDef { pub struct ReduceOpDef {
pub typ: ListOpType, pub mac: PositionedItem<String>,
pub field: PositionedItem<String>,
pub acc: Box<Expression>,
pub target: Box<Expression>,
pub pos: Position,
}
/// MapFilterOpDef implements the list operations in the UCG AST.
#[derive(Debug, PartialEq, Clone)]
pub struct MapFilterOpDef {
pub mac: PositionedItem<String>, pub mac: PositionedItem<String>,
pub field: PositionedItem<String>, pub field: PositionedItem<String>,
pub target: Box<Expression>, pub target: Box<Expression>,
pub pos: Position, pub pos: Position,
} }
impl FuncOpDef {
pub fn pos(&self) -> &Position {
match self {
FuncOpDef::Map(def) => &def.pos,
FuncOpDef::Filter(def) => &def.pos,
FuncOpDef::Reduce(def) => &def.pos,
}
}
}
// TODO(jwall): this should probably be moved to a Val::Module IR type. // TODO(jwall): this should probably be moved to a Val::Module IR type.
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct ModuleDef { pub struct ModuleDef {
@ -632,7 +645,7 @@ pub enum Expression {
Call(CallDef), Call(CallDef),
Macro(MacroDef), Macro(MacroDef),
Select(SelectDef), Select(SelectDef),
ListOp(ListOpDef), FuncOp(FuncOpDef),
Module(ModuleDef), Module(ModuleDef),
} }
@ -649,7 +662,7 @@ impl Expression {
&Expression::Macro(ref def) => &def.pos, &Expression::Macro(ref def) => &def.pos,
&Expression::Module(ref def) => &def.pos, &Expression::Module(ref def) => &def.pos,
&Expression::Select(ref def) => &def.pos, &Expression::Select(ref def) => &def.pos,
&Expression::ListOp(ref def) => &def.pos, &Expression::FuncOp(ref def) => def.pos(),
&Expression::Include(ref def) => &def.pos, &Expression::Include(ref def) => &def.pos,
} }
} }
@ -664,7 +677,7 @@ impl fmt::Display for Expression {
&Expression::Binary(_) => { &Expression::Binary(_) => {
write!(w, "<Expr>")?; write!(w, "<Expr>")?;
} }
&Expression::ListOp(_) => { &Expression::FuncOp(_) => {
write!(w, "<Expr>")?; write!(w, "<Expr>")?;
} }
&Expression::Copy(_) => { &Expression::Copy(_) => {

View File

@ -41,6 +41,11 @@ pub mod scope;
pub use self::ir::Val; pub use self::ir::Val;
enum ProcessingOpType {
Map,
Filter,
}
impl MacroDef { impl MacroDef {
/// Expands a ucg Macro using the given arguments into a new Tuple. /// Expands a ucg Macro using the given arguments into a new Tuple.
pub fn eval( pub fn eval(
@ -206,7 +211,7 @@ impl<'a> FileBuilder<'a> {
} }
fn eval_tuple( fn eval_tuple(
&mut self, &self,
fields: &Vec<(Token, Expression)>, fields: &Vec<(Token, Expression)>,
scope: &Scope, scope: &Scope,
) -> Result<Rc<Val>, Box<dyn Error>> { ) -> Result<Rc<Val>, Box<dyn Error>> {
@ -218,7 +223,7 @@ impl<'a> FileBuilder<'a> {
Ok(Rc::new(Val::Tuple(new_fields))) Ok(Rc::new(Val::Tuple(new_fields)))
} }
fn eval_list(&mut self, def: &ListDef, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> { fn eval_list(&self, def: &ListDef, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> {
let mut vals = Vec::new(); let mut vals = Vec::new();
for expr in def.elems.iter() { for expr in def.elems.iter() {
vals.push(self.eval_expr(expr, scope)?); vals.push(self.eval_expr(expr, scope)?);
@ -226,7 +231,7 @@ impl<'a> FileBuilder<'a> {
Ok(Rc::new(Val::List(vals))) Ok(Rc::new(Val::List(vals)))
} }
fn eval_value(&mut self, v: &Value, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> { fn eval_value(&self, v: &Value, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> {
match v { match v {
&Value::Empty(_) => Ok(Rc::new(Val::Empty)), &Value::Empty(_) => Ok(Rc::new(Val::Empty)),
&Value::Boolean(ref b) => Ok(Rc::new(Val::Boolean(b.val))), &Value::Boolean(ref b) => Ok(Rc::new(Val::Boolean(b.val))),
@ -736,11 +741,7 @@ impl<'a> FileBuilder<'a> {
))) )))
} }
fn do_dot_lookup( fn do_dot_lookup(&self, right: &Expression, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> {
&mut self,
right: &Expression,
scope: &Scope,
) -> Result<Rc<Val>, Box<dyn Error>> {
let pos = right.pos().clone(); let pos = right.pos().clone();
match right { match right {
Expression::Copy(_) => return self.eval_expr(right, scope), Expression::Copy(_) => return self.eval_expr(right, scope),
@ -771,7 +772,7 @@ impl<'a> FileBuilder<'a> {
} }
fn do_element_check( fn do_element_check(
&mut self, &self,
left: &Expression, left: &Expression,
right: &Expression, right: &Expression,
scope: &Scope, scope: &Scope,
@ -817,7 +818,7 @@ impl<'a> FileBuilder<'a> {
} }
} }
fn eval_binary(&mut self, def: &BinaryOpDef, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> { fn eval_binary(&self, def: &BinaryOpDef, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> {
let kind = &def.kind; let kind = &def.kind;
if let &BinaryExprType::IN = kind { if let &BinaryExprType::IN = kind {
return self.do_element_check(&def.left, &def.right, scope); return self.do_element_check(&def.left, &def.right, scope);
@ -858,7 +859,7 @@ impl<'a> FileBuilder<'a> {
} }
fn copy_from_base( fn copy_from_base(
&mut self, &self,
src_fields: &Vec<(PositionedItem<String>, Rc<Val>)>, src_fields: &Vec<(PositionedItem<String>, Rc<Val>)>,
overrides: &Vec<(Token, Expression)>, overrides: &Vec<(Token, Expression)>,
scope: &Scope, scope: &Scope,
@ -935,7 +936,7 @@ impl<'a> FileBuilder<'a> {
))); )));
} }
fn eval_copy(&mut self, def: &CopyDef, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> { fn eval_copy(&self, def: &CopyDef, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> {
let v = self.eval_value(&def.selector, scope)?; let v = self.eval_value(&def.selector, scope)?;
if let &Val::Tuple(ref src_fields) = v.as_ref() { if let &Val::Tuple(ref src_fields) = v.as_ref() {
let mut child_scope = scope.spawn_child(); let mut child_scope = scope.spawn_child();
@ -998,7 +999,7 @@ impl<'a> FileBuilder<'a> {
))) )))
} }
fn eval_format(&mut self, def: &FormatDef, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> { fn eval_format(&self, def: &FormatDef, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> {
let tmpl = &def.template; let tmpl = &def.template;
let args = &def.args; let args = &def.args;
let mut vals = Vec::new(); let mut vals = Vec::new();
@ -1010,7 +1011,7 @@ impl<'a> FileBuilder<'a> {
Ok(Rc::new(Val::Str(formatter.render(&def.pos)?))) Ok(Rc::new(Val::Str(formatter.render(&def.pos)?)))
} }
fn eval_call(&mut self, def: &CallDef, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> { fn eval_call(&self, def: &CallDef, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> {
let args = &def.arglist; let args = &def.arglist;
let v = self.eval_value(&def.macroref, scope)?; let v = self.eval_value(&def.macroref, scope)?;
if let &Val::Macro(ref m) = v.deref() { if let &Val::Macro(ref m) = v.deref() {
@ -1055,11 +1056,7 @@ impl<'a> FileBuilder<'a> {
}; };
} }
fn eval_module_def( fn eval_module_def(&self, def: &ModuleDef, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> {
&mut self,
def: &ModuleDef,
scope: &Scope,
) -> Result<Rc<Val>, Box<dyn Error>> {
let root = self.file_dir(); let root = self.file_dir();
// Always work on a copy. The original should not be modified. // Always work on a copy. The original should not be modified.
let mut def = def.clone(); let mut def = def.clone();
@ -1071,7 +1068,7 @@ impl<'a> FileBuilder<'a> {
Ok(Rc::new(Val::Module(def))) Ok(Rc::new(Val::Module(def)))
} }
fn eval_select(&mut self, def: &SelectDef, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> { fn eval_select(&self, def: &SelectDef, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> {
let target = &def.val; let target = &def.val;
let def_expr = &def.default; let def_expr = &def.default;
let fields = &def.tuple; let fields = &def.tuple;
@ -1117,7 +1114,7 @@ impl<'a> FileBuilder<'a> {
elems: &Vec<Rc<Val>>, elems: &Vec<Rc<Val>>,
def: &MacroDef, def: &MacroDef,
outfield: &PositionedItem<String>, outfield: &PositionedItem<String>,
typ: &ListOpType, 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() {
@ -1125,10 +1122,10 @@ impl<'a> FileBuilder<'a> {
let fields = def.eval(self.file.clone(), self, argvals)?; let fields = def.eval(self.file.clone(), self, argvals)?;
if let Some(v) = find_in_fieldlist(&outfield.val, &fields) { if let Some(v) = find_in_fieldlist(&outfield.val, &fields) {
match typ { match typ {
ListOpType::Map => { ProcessingOpType::Map => {
out.push(v.clone()); out.push(v.clone());
} }
ListOpType::Filter => { ProcessingOpType::Filter => {
if let &Val::Empty = v.as_ref() { if let &Val::Empty = v.as_ref() {
// noop // noop
continue; continue;
@ -1149,7 +1146,7 @@ impl<'a> FileBuilder<'a> {
fs: &Vec<(PositionedItem<String>, Rc<Val>)>, fs: &Vec<(PositionedItem<String>, Rc<Val>)>,
def: &MacroDef, def: &MacroDef,
outfield: &PositionedItem<String>, outfield: &PositionedItem<String>,
typ: &ListOpType, 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 {
@ -1157,7 +1154,7 @@ impl<'a> FileBuilder<'a> {
let fields = def.eval(self.file.clone(), self, argvals)?; let fields = def.eval(self.file.clone(), self, argvals)?;
if let Some(v) = find_in_fieldlist(&outfield.val, &fields) { if let Some(v) = find_in_fieldlist(&outfield.val, &fields) {
match typ { match typ {
ListOpType::Map => { ProcessingOpType::Map => {
if let &Val::List(ref fs) = v.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.
@ -1199,7 +1196,7 @@ impl<'a> FileBuilder<'a> {
))); )));
} }
} }
ListOpType::Filter => { ProcessingOpType::Filter => {
if let &Val::Empty = v.as_ref() { if let &Val::Empty = v.as_ref() {
// noop // noop
continue; continue;
@ -1210,17 +1207,23 @@ impl<'a> FileBuilder<'a> {
out.push((name.clone(), val.clone())); out.push((name.clone(), val.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(),
)));
} }
} }
Ok(Rc::new(Val::Tuple(out))) Ok(Rc::new(Val::Tuple(out)))
} }
fn eval_functional_processing( fn eval_reduce_op(&self, def: &ReduceOpDef, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> {
&mut self, let maybe_target = self.eval_expr(&def.target, scope)?;
def: &ListOpDef, let mut acc = self.eval_expr(&def.acc, scope)?;
scope: &Scope,
) -> Result<Rc<Val>, Box<dyn Error>> {
let maybe_list = self.eval_expr(&def.target, scope)?;
let maybe_mac = self.eval_value(&Value::Symbol(def.mac.clone()), &self.scope.clone())?; let maybe_mac = self.eval_value(&Value::Symbol(def.mac.clone()), &self.scope.clone())?;
let macdef = match maybe_mac.as_ref() { let macdef = match maybe_mac.as_ref() {
&Val::Macro(ref macdef) => macdef, &Val::Macro(ref macdef) => macdef,
@ -1232,15 +1235,85 @@ impl<'a> FileBuilder<'a> {
))); )));
} }
}; };
return match maybe_list.as_ref() { match maybe_target.as_ref() {
&Val::List(ref elems) => { &Val::List(ref elems) => {
self.eval_functional_list_processing(elems, macdef, &def.field, &def.typ) 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(),
)));
}
}
} }
&Val::Tuple(ref fs) => { &Val::Tuple(ref fs) => {
self.eval_functional_tuple_processing(fs, macdef, &def.field, &def.typ) for &(ref name, ref val) in fs.iter() {
let argvals = vec![
acc.clone(),
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(),
)));
}
}
}
other => {
return Err(Box::new(error::BuildError::new(
format!(
"Expected List or Tuple as target but got {:?}",
other.type_name()
),
error::ErrorType::TypeFail,
def.target.pos().clone(),
)));
}
}
Ok(acc)
}
fn eval_functional_processing(
&self,
def: &MapFilterOpDef,
typ: ProcessingOpType,
scope: &Scope,
) -> Result<Rc<Val>, Box<dyn Error>> {
let maybe_target = self.eval_expr(&def.target, scope)?;
let maybe_mac = self.eval_value(&Value::Symbol(def.mac.clone()), &self.scope.clone())?;
let macdef = match maybe_mac.as_ref() {
&Val::Macro(ref macdef) => macdef,
_ => {
return Err(Box::new(error::BuildError::new(
format!("Expected macro but got {:?}", def.mac),
error::ErrorType::TypeFail,
def.pos.clone(),
)));
}
};
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)
} }
other => Err(Box::new(error::BuildError::new( other => Err(Box::new(error::BuildError::new(
format!("Expected List as target but got {:?}", other.type_name()), format!(
"Expected List or Tuple as target but got {:?}",
other.type_name()
),
error::ErrorType::TypeFail, error::ErrorType::TypeFail,
def.target.pos().clone(), def.target.pos().clone(),
))), ))),
@ -1328,7 +1401,7 @@ impl<'a> FileBuilder<'a> {
Ok(contents) Ok(contents)
} }
pub fn eval_include(&mut self, def: &IncludeDef) -> Result<Rc<Val>, Box<dyn Error>> { pub fn eval_include(&self, def: &IncludeDef) -> Result<Rc<Val>, Box<dyn Error>> {
return if def.typ.fragment == "str" { return if def.typ.fragment == "str" {
Ok(Rc::new(Val::Str( Ok(Rc::new(Val::Str(
self.get_file_as_string(&def.path.pos, &def.path.fragment)?, self.get_file_as_string(&def.path.pos, &def.path.fragment)?,
@ -1351,13 +1424,21 @@ impl<'a> FileBuilder<'a> {
}; };
} }
fn eval_func_op(&self, def: &FuncOpDef, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> {
match def {
FuncOpDef::Filter(ref def) => {
self.eval_functional_processing(def, ProcessingOpType::Filter, scope)
}
FuncOpDef::Map(ref def) => {
self.eval_functional_processing(def, ProcessingOpType::Map, scope)
}
FuncOpDef::Reduce(ref def) => self.eval_reduce_op(def, scope),
}
}
// Evals a single Expression in the context of a running Builder. // Evals a single Expression in the context of a running Builder.
// It does not mutate the builders collected state at all. // It does not mutate the builders collected state at all.
pub fn eval_expr( pub fn eval_expr(&self, expr: &Expression, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> {
&mut self,
expr: &Expression,
scope: &Scope,
) -> Result<Rc<Val>, Box<dyn Error>> {
match expr { match expr {
&Expression::Simple(ref val) => self.eval_value(val, scope), &Expression::Simple(ref val) => self.eval_value(val, scope),
&Expression::Binary(ref def) => self.eval_binary(def, scope), &Expression::Binary(ref def) => self.eval_binary(def, scope),
@ -1368,7 +1449,7 @@ impl<'a> FileBuilder<'a> {
&Expression::Macro(ref def) => self.eval_macro_def(def), &Expression::Macro(ref def) => self.eval_macro_def(def),
&Expression::Module(ref def) => self.eval_module_def(def, scope), &Expression::Module(ref def) => self.eval_module_def(def, scope),
&Expression::Select(ref def) => self.eval_select(def, scope), &Expression::Select(ref def) => self.eval_select(def, scope),
&Expression::ListOp(ref def) => self.eval_functional_processing(def, scope), &Expression::FuncOp(ref def) => self.eval_func_op(def, scope),
&Expression::Include(ref def) => self.eval_include(def), &Expression::Include(ref def) => self.eval_include(def),
} }
} }

View File

@ -19,7 +19,7 @@ use std;
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
fn test_expr_to_val(mut cases: Vec<(Expression, Val)>, mut b: FileBuilder) { fn test_expr_to_val(mut cases: Vec<(Expression, Val)>, b: FileBuilder) {
for tpl in cases.drain(0..) { for tpl in cases.drain(0..) {
assert_eq!( assert_eq!(
b.eval_expr(&tpl.0, &b.scope.spawn_child()).unwrap(), b.eval_expr(&tpl.0, &b.scope.spawn_child()).unwrap(),

View File

@ -567,57 +567,70 @@ fn call_expression(input: SliceIter<Token>) -> Result<SliceIter<Token>, Expressi
} }
} }
fn tuple_to_list_op<'a>( make_fn!(
input: &'a SliceIter<Token>, reduce_expression<SliceIter<Token>, Expression>,
kind: ListOpType, do_each!(
macroname: Value, pos => pos,
outfield: Value, _ => word!("reduce"),
list: Expression, macroname => match_type!(BAREWORD),
) -> ConvertResult<'a, Expression> { _ => punct!("."),
let macroname = match macroname { outfield => match_type!(BAREWORD),
Value::Symbol(sym) => sym, acc => trace_parse!(non_op_expression),
_ => { _ => punct!(","),
return Err(Error::new( tgt => trace_parse!(non_op_expression),
format!("Expected a macro name but got {}", macroname.type_name()), (Expression::FuncOp(FuncOpDef::Reduce(ReduceOpDef{
Box::new(input.clone()), mac: (&macroname).into(),
)) field: (&outfield).into(),
} acc: Box::new(acc),
}; target: Box::new(tgt),
let outfield = match outfield { pos: pos,
Value::Symbol(sym) => sym, })))
_ => { )
return Err(Error::new( );
format!("Expected a field name but got {}", outfield.type_name()),
Box::new(input.clone()),
))
}
};
return Ok(Expression::ListOp(ListOpDef {
typ: kind,
mac: macroname,
field: outfield,
target: Box::new(list),
pos: input.into(),
}));
}
make_fn!( make_fn!(
list_op_expression<SliceIter<Token>, Expression>, map_expression<SliceIter<Token>, Expression>,
do_each!( do_each!(
input => input!(), pos => pos,
optype => either!( _ => word!("map"),
do_each!(_ => word!("map"), (ListOpType::Map)),
do_each!(_ => word!("filter"), (ListOpType::Filter))
),
// TODO This should become just a bareword symbol now // TODO This should become just a bareword symbol now
macroname => trace_parse!(symbol), macroname => match_type!(BAREWORD),
_ => punct!("."), _ => punct!("."),
outfield => trace_parse!(symbol), outfield => match_type!(BAREWORD),
list => trace_parse!(non_op_expression), list => trace_parse!(non_op_expression),
(tuple_to_list_op(&input, optype, macroname, outfield, list).unwrap()) (Expression::FuncOp(FuncOpDef::Map(MapFilterOpDef{
mac: (&macroname).into(),
field: (&outfield).into(),
target: Box::new(list),
pos: pos,
})))
) )
); );
make_fn!(
filter_expression<SliceIter<Token>, Expression>,
do_each!(
pos => pos,
_ => 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: (&macroname).into(),
field: (&outfield).into(),
target: Box::new(list),
pos: pos,
})))
)
);
make_fn!(
func_op_expression<SliceIter<Token>, Expression>,
either!(reduce_expression, map_expression, filter_expression)
);
fn unprefixed_expression(input: SliceIter<Token>) -> ParseResult<Expression> { fn unprefixed_expression(input: SliceIter<Token>) -> ParseResult<Expression> {
let _input = input.clone(); let _input = input.clone();
either!( either!(
@ -632,7 +645,7 @@ fn unprefixed_expression(input: SliceIter<Token>) -> ParseResult<Expression> {
make_fn!( make_fn!(
non_op_expression<SliceIter<Token>, Expression>, non_op_expression<SliceIter<Token>, Expression>,
either!( either!(
trace_parse!(list_op_expression), trace_parse!(func_op_expression),
trace_parse!(macro_expression), trace_parse!(macro_expression),
trace_parse!(module_expression), trace_parse!(module_expression),
trace_parse!(select_expression), trace_parse!(select_expression),

View File

@ -308,6 +308,10 @@ make_fn!(filtertok<OffsetStrIter, Token>,
do_text_token_tok!(TokenType::BAREWORD, "filter", WS) do_text_token_tok!(TokenType::BAREWORD, "filter", WS)
); );
make_fn!(reducetok<OffsetStrIter, Token>,
do_text_token_tok!(TokenType::BAREWORD, "reduce", WS)
);
fn comment(input: OffsetStrIter) -> Result<OffsetStrIter, Token> { fn comment(input: OffsetStrIter) -> Result<OffsetStrIter, Token> {
match text_token!(input, "//") { match text_token!(input, "//") {
Result::Complete(rest, _) => { Result::Complete(rest, _) => {
@ -405,6 +409,7 @@ fn token<'a>(input: OffsetStrIter<'a>) -> Result<OffsetStrIter<'a>, Token> {
astok, astok,
maptok, maptok,
filtertok, filtertok,
reducetok,
barewordtok, barewordtok,
whitespace, whitespace,
end_of_input end_of_input