From b9cbf92205fa0d0ceda5bd7e9dcd9dc2ad7380ca Mon Sep 17 00:00:00 2001 From: Nick Alexander Date: Mon, 3 Apr 2017 16:46:11 -0700 Subject: [PATCH] Part 1: Parse functions in where clauses. --- query-parser/src/parse.rs | 151 ++++++++++++++++++++++++++++++++++++++ query/src/lib.rs | 58 ++++++++++++++- 2 files changed, 207 insertions(+), 2 deletions(-) diff --git a/query-parser/src/parse.rs b/query-parser/src/parse.rs index 46f12751..3776429c 100644 --- a/query-parser/src/parse.rs +++ b/query-parser/src/parse.rs @@ -38,6 +38,7 @@ use self::mentat_parser_utils::value_and_span::{ }; use self::mentat_query::{ + Binding, Direction, Element, FindQuery, @@ -57,7 +58,9 @@ use self::mentat_query::{ SrcVar, UnifyVars, Variable, + VariableOrPlaceholder, WhereClause, + WhereFn, }; error_chain! { @@ -279,6 +282,25 @@ def_parser!(Where, pred, WhereClause, { }))) }); +/// A vector containing a parenthesized function expression and a binding. +def_parser!(Where, where_fn, WhereClause, { + // Accept either a nested list or a nested vector here: + // `[(foo ?x ?y) binding]` or `[[foo ?x ?y] binding]` + vector() + .of_exactly( + (seq().of_exactly( + (Query::predicate_fn(), Query::arguments())), + Bind::binding()) + .map(|((f, args), binding)| { + WhereClause::WhereFn( + WhereFn { + operator: f.0, + args: args, + binding: binding, + }) + })) +}); + def_parser!(Where, pattern, WhereClause, { vector() .of_exactly( @@ -331,6 +353,7 @@ def_parser!(Where, clause, WhereClause, { try(Where::not_clause()), try(Where::pred()), + try(Where::where_fn()), ]) }); @@ -345,6 +368,8 @@ def_matches_plain_symbol!(Find, period, "."); def_matches_plain_symbol!(Find, ellipsis, "..."); +def_matches_plain_symbol!(Find, placeholder, "_"); + def_parser!(Find, find_scalar, FindSpec, { Query::variable() .skip(Find::period()) @@ -448,6 +473,48 @@ def_parser!(Find, query, FindQuery, { }) }); +pub struct Bind<'a>(std::marker::PhantomData<&'a ()>); + +def_parser!(Bind, bind_scalar, Binding, { + Query::variable() + .skip(eof()) + .map(|var| Binding::BindScalar(var)) +}); + +def_parser!(Bind, variable_or_placeholder, VariableOrPlaceholder, { + Query::variable().map(VariableOrPlaceholder::Variable) + .or(Find::placeholder().map(|_| VariableOrPlaceholder::Placeholder)) +}); + +def_parser!(Bind, bind_coll, Binding, { + vector() + .of_exactly(Query::variable() + .skip(Find::ellipsis())) + .map(Binding::BindColl) +}); + +def_parser!(Bind, bind_rel, Binding, { + vector().of_exactly( + vector().of_exactly( + many1::, _>(Bind::variable_or_placeholder()) + .map(Binding::BindRel))) +}); + +def_parser!(Bind, bind_tuple, Binding, { + vector().of_exactly( + many1::, _>(Bind::variable_or_placeholder()) + .map(Binding::BindTuple)) +}); + +def_parser!(Bind, binding, Binding, { + // Any one of the four binding types might apply, so we combine them with `choice`. Our parsers + // consume input, so we need to wrap them in `try` so that they operate independently. + choice([try(Bind::bind_scalar()), + try(Bind::bind_coll()), + try(Bind::bind_tuple()), + try(Bind::bind_rel())]) +}); + pub fn parse_find_string(string: &str) -> Result { let expr = edn::parse::value(string)?; Find::query() @@ -467,6 +534,7 @@ mod test { use self::combine::Parser; use self::edn::OrderedFloat; use self::mentat_query::{ + Binding, Element, FindSpec, NonIntegerConstant, @@ -475,6 +543,7 @@ mod test { PatternValuePlace, SrcVar, Variable, + VariableOrPlaceholder, }; use super::*; @@ -793,4 +862,86 @@ mod test { FnArg::Variable(variable(vy)), ])); } + + #[test] + fn test_bind_scalar() { + let vx = edn::PlainSymbol::new("?x"); + assert_edn_parses_to!(|| list().of_exactly(Bind::binding()), + "(?x)", + Binding::BindScalar(variable(vx))); + } + + #[test] + fn test_bind_coll() { + let vx = edn::PlainSymbol::new("?x"); + assert_edn_parses_to!(|| list().of_exactly(Bind::binding()), + "([?x ...])", + Binding::BindColl(variable(vx))); + } + + #[test] + fn test_bind_rel() { + let vx = edn::PlainSymbol::new("?x"); + let vy = edn::PlainSymbol::new("?y"); + let vw = edn::PlainSymbol::new("?w"); + assert_edn_parses_to!(|| list().of_exactly(Bind::binding()), + "([[?x ?y _ ?w]])", + Binding::BindRel(vec![VariableOrPlaceholder::Variable(variable(vx)), + VariableOrPlaceholder::Variable(variable(vy)), + VariableOrPlaceholder::Placeholder, + VariableOrPlaceholder::Variable(variable(vw)), + ])); + } + + #[test] + fn test_bind_tuple() { + let vx = edn::PlainSymbol::new("?x"); + let vy = edn::PlainSymbol::new("?y"); + let vw = edn::PlainSymbol::new("?w"); + assert_edn_parses_to!(|| list().of_exactly(Bind::binding()), + "([?x ?y _ ?w])", + Binding::BindTuple(vec![VariableOrPlaceholder::Variable(variable(vx)), + VariableOrPlaceholder::Variable(variable(vy)), + VariableOrPlaceholder::Placeholder, + VariableOrPlaceholder::Variable(variable(vw)), + ])); + } + + #[test] + fn test_where_fn() { + assert_edn_parses_to!(Where::where_fn, + "[(f ?x 1) ?y]", + WhereClause::WhereFn(WhereFn { + operator: edn::PlainSymbol::new("f"), + args: vec![FnArg::Variable(Variable::from_valid_name("?x")), + FnArg::EntidOrInteger(1)], + binding: Binding::BindScalar(Variable::from_valid_name("?y")), + })); + + assert_edn_parses_to!(Where::where_fn, + "[(f ?x) [?y ...]]", + WhereClause::WhereFn(WhereFn { + operator: edn::PlainSymbol::new("f"), + args: vec![FnArg::Variable(Variable::from_valid_name("?x"))], + binding: Binding::BindColl(Variable::from_valid_name("?y")), + })); + + assert_edn_parses_to!(Where::where_fn, + "[(f) [?y _]]", + WhereClause::WhereFn(WhereFn { + operator: edn::PlainSymbol::new("f"), + args: vec![], + binding: Binding::BindTuple(vec![VariableOrPlaceholder::Variable(Variable::from_valid_name("?y")), + VariableOrPlaceholder::Placeholder]), + })); + + assert_edn_parses_to!(Where::where_fn, + "[(f) [[_ ?y]]]", + WhereClause::WhereFn(WhereFn { + operator: edn::PlainSymbol::new("f"), + args: vec![], + binding: Binding::BindRel(vec![VariableOrPlaceholder::Placeholder, + VariableOrPlaceholder::Variable(Variable::from_valid_name("?y"))]), + })); + } } diff --git a/query/src/lib.rs b/query/src/lib.rs index fd4f74f8..abd35868 100644 --- a/query/src/lib.rs +++ b/query/src/lib.rs @@ -534,6 +534,25 @@ impl FindSpec { } } +// Datomic accepts variable or placeholder. DataScript accepts recursive bindings. Mentat sticks +// to the non-recursive form Datomic accepts, which is much simpler to process. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum VariableOrPlaceholder { + Placeholder, + Variable(Variable), +} + +#[derive(Clone,Debug,Eq,PartialEq)] +pub enum Binding { + BindRel(Vec), + + BindColl(Variable), + + BindTuple(Vec), + + BindScalar(Variable), +} + // Note that the "implicit blank" rule applies. // A pattern with a reversed attribute — :foo/_bar — is reversed // at the point of parsing. These `Pattern` instances only represent @@ -589,6 +608,13 @@ pub struct Predicate { pub args: Vec, } +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct WhereFn { + pub operator: PlainSymbol, + pub args: Vec, + pub binding: Binding, +} + #[derive(Clone, Debug, Eq, PartialEq)] pub enum UnifyVars { /// `Implicit` means the variables in an `or` or `not` are derived from the enclosed pattern. @@ -664,7 +690,7 @@ pub enum WhereClause { NotJoin(NotJoin), OrJoin(OrJoin), Pred(Predicate), - WhereFn, + WhereFn(WhereFn), RuleExpr, Pattern(Pattern), } @@ -730,7 +756,7 @@ impl ContainsVariables for WhereClause { &Pred(ref p) => p.accumulate_mentioned_variables(acc), &Pattern(ref p) => p.accumulate_mentioned_variables(acc), &NotJoin(ref n) => n.accumulate_mentioned_variables(acc), - &WhereFn => (), + &WhereFn(ref f) => f.accumulate_mentioned_variables(acc), &RuleExpr => (), } } @@ -794,6 +820,34 @@ impl ContainsVariables for Predicate { } } +impl ContainsVariables for Binding { + fn accumulate_mentioned_variables(&self, acc: &mut BTreeSet) { + match self { + &Binding::BindScalar(ref v) | &Binding::BindColl(ref v) => { + acc_ref(acc, v) + }, + &Binding::BindRel(ref vs) | &Binding::BindTuple(ref vs) => { + for v in vs { + if let &VariableOrPlaceholder::Variable(ref v) = v { + acc_ref(acc, v); + } + } + }, + } + } +} + +impl ContainsVariables for WhereFn { + fn accumulate_mentioned_variables(&self, acc: &mut BTreeSet) { + for arg in &self.args { + if let &FnArg::Variable(ref v) = arg { + acc_ref(acc, v) + } + } + self.binding.accumulate_mentioned_variables(acc); + } +} + fn acc_ref(acc: &mut BTreeSet, v: &T) { // Roll on, reference entries! if !acc.contains(v) {