diff --git a/parser-utils/Cargo.toml b/parser-utils/Cargo.toml index 5d2a2f1c..634594d1 100644 --- a/parser-utils/Cargo.toml +++ b/parser-utils/Cargo.toml @@ -5,3 +5,6 @@ authors = ["Victor Porof ", "Richard Newman { + satisfy_map(|x: edn::Value| { + if let edn::Value::PlainSymbol(ref s) = x { + if s.0.as_str() == $name { + return Some(()); + } + } + return None; + }).parse_stream($input) + } +} + +/// Define an `impl` body for the `$parser` type. The body will contain a parser +/// function called `$name`, consuming a stream of `$item_type`s. The parser's +/// result type will be `$result_type`. +/// +/// The provided `$body` will be evaluated with `$input` bound to the input stream. +/// +/// `$body`, when run, should return a `ParseResult` of the appropriate result type. +#[macro_export] +macro_rules! def_parser_fn { + ( $parser: ident, $name: ident, $item_type: ty, $result_type: ty, $input: ident, $body: block ) => { + impl $parser where I: Stream { + fn $name() -> ResultParser<$result_type, I> { + fn inner>($input: I) -> ParseResult<$result_type, I> { + $body + } + parser(inner as fn(I) -> ParseResult<$result_type, I>).expected("$name") + } + } + } +} + +/// `def_value_parser_fn` is a short-cut to `def_parser_fn` with the input type +/// being `edn::Value`. +#[macro_export] +macro_rules! def_value_parser_fn { + ( $parser: ident, $name: ident, $result_type: ty, $input: ident, $body: block ) => { + def_parser_fn!($parser, $name, edn::Value, $result_type, $input, $body); + } +} + +/// `def_value_satisfy_parser_fn` is a short-cut to `def_parser_fn` with the input type +/// being `edn::Value` and the body being a call to `satisfy_map` with the given transformer. +/// +/// In practice this allows you to simply pass a function that accepts an `&edn::Value` and +/// returns an `Option<$result_type>`: if a suitable value is at the front of the stream, +/// it will be converted and returned by the parser; otherwise, the parse will fail. +#[macro_export] +macro_rules! def_value_satisfy_parser_fn { + ( $parser: ident, $name: ident, $result_type: ty, $transformer: path ) => { + def_value_parser_fn!($parser, $name, $result_type, input, { + satisfy_map(|x: edn::Value| $transformer(&x)).parse_stream(input) + }); + } +} diff --git a/query-parser/src/lib.rs b/query-parser/src/lib.rs index c33ca63f..fcf24ba3 100644 --- a/query-parser/src/lib.rs +++ b/query-parser/src/lib.rs @@ -14,8 +14,6 @@ extern crate matches; #[macro_use] extern crate mentat_parser_utils; -#[macro_use] -mod parser_util; mod util; mod parse; pub mod find; diff --git a/query-parser/src/parser_util.rs b/query-parser/src/parser_util.rs deleted file mode 100644 index 6175d958..00000000 --- a/query-parser/src/parser_util.rs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2016 Mozilla -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not use -// this file except in compliance with the License. You may obtain a copy of the -// License at http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software distributed -// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, either express or implied. See the License for the -// specific language governing permissions and limitations under the License. - -extern crate combine; -extern crate edn; - -extern crate mentat_parser_utils; -extern crate mentat_query; - -/// Generate a `satisfy_map` expression that matches a `PlainSymbol` -/// value with the given name. -/// -/// We do this rather than using `combine::token` so that we don't -/// need to allocate a new `String` inside a `PlainSymbol` inside a `Value` -/// just to match input. -macro_rules! matches_plain_symbol { - ($name: expr, $input: ident) => { - satisfy_map(|x: edn::Value| { - if let edn::Value::PlainSymbol(ref s) = x { - if s.0.as_str() == $name { - return Some(()); - } - } - return None; - }).parse_stream($input) - } -} - -/// Define an `impl` body for the `$parser` type. The body will contain a parser -/// function called `$name`, consuming a stream of `$item_type`s. The parser's -/// result type will be `$result_type`. -/// -/// The provided `$body` will be evaluated with `$input` bound to the input stream. -/// -/// `$body`, when run, should return a `ParseResult` of the appropriate result type. -macro_rules! def_parser_fn { - ( $parser: ident, $name: ident, $item_type: ty, $result_type: ty, $input: ident, $body: block ) => { - impl $parser where I: Stream { - fn $name() -> ResultParser<$result_type, I> { - fn inner>($input: I) -> ParseResult<$result_type, I> { - $body - } - parser(inner as fn(I) -> ParseResult<$result_type, I>).expected("$name") - } - } - } -} - -/// `def_value_parser_fn` is a short-cut to `def_parser_fn` with the input type -/// being `edn::Value`. -macro_rules! def_value_parser_fn { - ( $parser: ident, $name: ident, $result_type: ty, $input: ident, $body: block ) => { - def_parser_fn!($parser, $name, edn::Value, $result_type, $input, $body); - } -} - -/// `def_value_satisfy_parser_fn` is a short-cut to `def_parser_fn` with the input type -/// being `edn::Value` and the body being a call to `satisfy_map` with the given transformer. -/// -/// In practice this allows you to simply pass a function that accepts an `&edn::Value` and -/// returns an `Option<$result_type>`: if a suitable value is at the front of the stream, -/// it will be converted and returned by the parser; otherwise, the parse will fail. -macro_rules! def_value_satisfy_parser_fn { - ( $parser: ident, $name: ident, $result_type: ty, $transformer: path ) => { - def_value_parser_fn!($parser, $name, $result_type, input, { - satisfy_map(|x: edn::Value| $transformer(&x)).parse_stream(input) - }); - } -} diff --git a/tx-parser/Cargo.toml b/tx-parser/Cargo.toml index 7601720b..db3537fa 100644 --- a/tx-parser/Cargo.toml +++ b/tx-parser/Cargo.toml @@ -10,3 +10,6 @@ combine = "2.1.1" [dependencies.mentat_tx] path = "../tx" + +[dependencies.mentat_parser_utils] + path = "../parser-utils" diff --git a/tx-parser/src/lib.rs b/tx-parser/src/lib.rs index 0de570a5..598f7eef 100644 --- a/tx-parser/src/lib.rs +++ b/tx-parser/src/lib.rs @@ -14,11 +14,15 @@ extern crate edn; extern crate combine; extern crate mentat_tx; +#[macro_use] +extern crate mentat_parser_utils; + use combine::{any, eof, many, parser, satisfy_map, token, Parser, ParseResult, Stream}; use combine::combinator::{Expected, FnParser}; use edn::symbols::NamespacedKeyword; use edn::types::Value; use mentat_tx::entities::{Entid, EntidOrLookupRef, Entity, LookupRef, OpType, ValueOrLookupRef}; +use mentat_parser_utils::ResultParser; pub struct Tx(::std::marker::PhantomData I>); @@ -30,181 +34,128 @@ fn fn_parser(f: fn(I) -> ParseResult, err: &'static str) -> TxParser parser(f).expected(err) } -impl Tx - where I: Stream -{ - fn integer() -> TxParser { - fn_parser(Tx::::integer_, "integer") - } +def_value_satisfy_parser_fn!(Tx, integer, i64, Value::as_integer); - fn integer_(input: I) -> ParseResult { - return satisfy_map(|x: Value| if let Value::Integer(y) = x { - Some(y) - } else { - None - }) - .parse_stream(input); - } +fn value_to_namespaced_keyword(val: &Value) -> Option { + val.as_namespaced_keyword().map(|x| x.clone()) +} +def_value_satisfy_parser_fn!(Tx, keyword, NamespacedKeyword, value_to_namespaced_keyword); - fn keyword() -> TxParser { - fn_parser(Tx::::keyword_, "keyword") - } +def_parser_fn!(Tx, entid, Value, Entid, input, { + Tx::::integer() + .map(|x| Entid::Entid(x)) + .or(Tx::::keyword().map(|x| Entid::Ident(x))) + .parse_lazy(input) + .into() +}); - fn keyword_(input: I) -> ParseResult { - return satisfy_map(|x: Value| if let Value::NamespacedKeyword(y) = x { - Some(y) - } else { - None - }) - .parse_stream(input); - } +def_parser_fn!(Tx, lookup_ref, Value, LookupRef, input, { + satisfy_map(|x: Value| if let Value::Vector(y) = x { + let mut p = (Tx::<&[Value]>::entid(), any(), eof()) + .map(|(a, v, _)| LookupRef { a: a, v: v }); + let r = p.parse_lazy(&y[..]).into(); + match r { + Ok((r, _)) => Some(r), + _ => None, + } + } else { + None + }) + .parse_stream(input) +}); - fn entid() -> TxParser { - fn_parser(Tx::::entid_, "entid") - } +def_parser_fn!(Tx, entid_or_lookup_ref, Value, EntidOrLookupRef, input, { + Tx::::entid() + .map(|x| EntidOrLookupRef::Entid(x)) + .or(Tx::::lookup_ref().map(|x| EntidOrLookupRef::LookupRef(x))) + .parse_lazy(input) + .into() +}); - fn entid_(input: I) -> ParseResult { - let p = Tx::::integer() - .map(|x| Entid::Entid(x)) - .or(Tx::::keyword().map(|x| Entid::Ident(x))) - .parse_lazy(input) - .into(); - return p; - } - - fn lookup_ref() -> TxParser { - fn_parser(Tx::::lookup_ref_, "lookup-ref") - } - - fn lookup_ref_(input: I) -> ParseResult { - return satisfy_map(|x: Value| if let Value::Vector(y) = x { - let mut p = (Tx::<&[Value]>::entid(), any(), eof()) - .map(|(a, v, _)| LookupRef { a: a, v: v }); - let r = p.parse_lazy(&y[..]).into(); - match r { +// TODO: abstract the "match Vector, parse internal stream" pattern to remove this boilerplate. +def_parser_fn!(Tx, add, Value, Entity, input, { + satisfy_map(|x: Value| -> Option { + if let Value::Vector(y) = x { + let mut p = (token(Value::NamespacedKeyword(NamespacedKeyword::new("db", "add"))), + Tx::<&[Value]>::entid_or_lookup_ref(), + Tx::<&[Value]>::entid(), + // TODO: handle lookup-ref. + any(), + eof()) + .map(|(_, e, a, v, _)| { + Entity::AddOrRetract { + op: OpType::Add, + e: e, + a: a, + v: ValueOrLookupRef::Value(v), + } + }); + // TODO: use ok() with a type annotation rather than explicit match. + match p.parse_lazy(&y[..]).into() { Ok((r, _)) => Some(r), _ => None, } } else { None - }) - .parse_stream(input); - } + } + }) + .parse_stream(input) +}); - fn entid_or_lookup_ref() -> TxParser { - fn_parser(Tx::::entid_or_lookup_ref_, "entid|lookup-ref") - } - - fn entid_or_lookup_ref_(input: I) -> ParseResult { - let p = Tx::::entid() - .map(|x| EntidOrLookupRef::Entid(x)) - .or(Tx::::lookup_ref().map(|x| EntidOrLookupRef::LookupRef(x))) - .parse_lazy(input) - .into(); - return p; - } - - // TODO: abstract the "match Vector, parse internal stream" pattern to remove this boilerplate. - fn add_(input: I) -> ParseResult { - return satisfy_map(|x: Value| -> Option { - if let Value::Vector(y) = x { - let mut p = (token(Value::NamespacedKeyword(NamespacedKeyword::new("db", - "add"))), - Tx::<&[Value]>::entid_or_lookup_ref(), - Tx::<&[Value]>::entid(), - // TODO: handle lookup-ref. - any(), - eof()) - .map(|(_, e, a, v, _)| { - Entity::AddOrRetract { - op: OpType::Add, - e: e, - a: a, - v: ValueOrLookupRef::Value(v), - } - }); - // TODO: use ok() with a type annotation rather than explicit match. - match p.parse_lazy(&y[..]).into() { - Ok((r, _)) => Some(r), - _ => None, - } - } else { - None +def_parser_fn!(Tx, retract, Value, Entity, input, { + satisfy_map(|x: Value| -> Option { + if let Value::Vector(y) = x { + let mut p = (token(Value::NamespacedKeyword(NamespacedKeyword::new("db", "retract"))), + Tx::<&[Value]>::entid_or_lookup_ref(), + Tx::<&[Value]>::entid(), + // TODO: handle lookup-ref. + any(), + eof()) + .map(|(_, e, a, v, _)| { + Entity::AddOrRetract { + op: OpType::Retract, + e: e, + a: a, + v: ValueOrLookupRef::Value(v), + } + }); + // TODO: use ok() with a type annotation rather than explicit match. + match p.parse_lazy(&y[..]).into() { + Ok((r, _)) => Some(r), + _ => None, } - }) - .parse_stream(input); - } + } else { + None + } + }) + .parse_stream(input) +}); - fn add() -> TxParser { - fn_parser(Tx::::add_, "[:db/add e a v]") - } +def_parser_fn!(Tx, entity, Value, Entity, input, { + let mut p = Tx::::add() + .or(Tx::::retract()); + p.parse_stream(input) +}); - fn retract_(input: I) -> ParseResult { - return satisfy_map(|x: Value| -> Option { - if let Value::Vector(y) = x { - let mut p = (token(Value::NamespacedKeyword(NamespacedKeyword::new("db", - "retract"))), - Tx::<&[Value]>::entid_or_lookup_ref(), - Tx::<&[Value]>::entid(), - // TODO: handle lookup-ref. - any(), - eof()) - .map(|(_, e, a, v, _)| { - Entity::AddOrRetract { - op: OpType::Retract, - e: e, - a: a, - v: ValueOrLookupRef::Value(v), - } - }); - // TODO: use ok() with a type annotation rather than explicit match. - match p.parse_lazy(&y[..]).into() { - Ok((r, _)) => Some(r), - _ => None, - } - } else { - None +def_parser_fn!(Tx, entities, Value, Vec, input, { + satisfy_map(|x: Value| -> Option> { + if let Value::Vector(y) = x { + let mut p = (many(Tx::<&[Value]>::entity()), eof()).map(|(es, _)| es); + // TODO: use ok() with a type annotation rather than explicit match. + match p.parse_lazy(&y[..]).into() { + Ok((r, _)) => Some(r), + _ => None, } - }) - .parse_stream(input); - } - - fn retract() -> TxParser { - fn_parser(Tx::::retract_, "[:db/retract e a v]") - } - - fn entity_(input: I) -> ParseResult { - let mut p = Tx::::add() - .or(Tx::::retract()); - p.parse_stream(input) - } - - fn entity() -> TxParser { - fn_parser(Tx::::entity_, - "[:db/add|:db/retract ...]") - } - - fn entities_(input: I) -> ParseResult, I> { - return satisfy_map(|x: Value| -> Option> { - if let Value::Vector(y) = x { - let mut p = (many(Tx::<&[Value]>::entity()), eof()).map(|(es, _)| es); - // TODO: use ok() with a type annotation rather than explicit match. - match p.parse_lazy(&y[..]).into() { - Ok((r, _)) => Some(r), - _ => None, - } - } else { - None - } - }) - .parse_stream(input); - } - - fn entities() -> TxParser, I> { - fn_parser(Tx::::entities_, - "[[:db/add|:db/retract ...]*]") - } + } else { + None + } + }) + .parse_stream(input) +}); +impl Tx + where I: Stream +{ pub fn parse(input: I) -> Result, combine::ParseError> { (Tx::::entities(), eof()) .map(|(es, _)| es) @@ -219,7 +170,6 @@ mod tests { use combine::Parser; use edn::symbols::NamespacedKeyword; use edn::types::Value; - use mentat_tx::entities::*; fn kw(namespace: &str, name: &str) -> Value { Value::NamespacedKeyword(NamespacedKeyword::new(namespace, name))