// 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. #![allow(dead_code)] extern crate edn; extern crate combine; extern crate mentat_tx; use combine::{any, eof, many, optional, parser, satisfy_map, token, Parser, ParseResult, Stream}; use combine::combinator::{Expected, FnParser}; use edn::symbols::NamespacedKeyword; use edn::types::Value; use mentat_tx::entities::*; pub struct Tx(::std::marker::PhantomData I>); type TxParser = Expected ParseResult>>; fn fn_parser(f: fn(I) -> ParseResult, err: &'static str) -> TxParser where I: Stream { parser(f).expected(err) } impl Tx where I: Stream { fn integer() -> TxParser { fn_parser(Tx::::integer_, "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 keyword() -> TxParser { fn_parser(Tx::::keyword_, "keyword") } fn keyword_(input: I) -> ParseResult { return satisfy_map(|x: Value| if let Value::NamespacedKeyword(y) = x { Some(y) } else { None }) .parse_stream(input); } fn entid() -> TxParser { fn_parser(Tx::::entid_, "entid") } 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 { Ok((r, _)) => Some(r), _ => None, } } else { None }) .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(), // TODO: entid or special keyword :db/tx? optional(Tx::<&[Value]>::entid()), eof()) .map(|(_, e, a, v, tx, _)| { Entity::Add { e: e, a: a, v: ValueOrLookupRef::Value(v), tx: tx, } }); // 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 add() -> TxParser { fn_parser(Tx::::add_, "[:db/add e a v tx?]") } 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::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 } }) .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 ...]*]") } pub fn parse(input: I) -> Result, combine::ParseError> { (Tx::::entities(), eof()) .map(|(es, _)| es) .parse(input) .map(|x| x.0) } } #[cfg(test)] mod tests { use super::*; 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)) } #[test] fn test_add() { let input = [Value::Vector(vec![kw("db", "add"), kw("test", "entid"), kw("test", "a"), Value::Text("v".into())])]; let mut parser = Tx::entity(); let result = parser.parse(&input[..]); assert_eq!(result, Ok((Entity::Add { e: EntidOrLookupRef::Entid(Entid::Ident(NamespacedKeyword::new("test", "entid"))), a: Entid::Ident(NamespacedKeyword::new("test", "a")), v: ValueOrLookupRef::Value(Value::Text("v".into())), tx: None, }, &[][..]))); } #[test] fn test_retract() { let input = [Value::Vector(vec![kw("db", "retract"), Value::Integer(101), kw("test", "a"), Value::Text("v".into())])]; let mut parser = Tx::entity(); let result = parser.parse(&input[..]); assert_eq!(result, Ok((Entity::Retract { e: EntidOrLookupRef::Entid(Entid::Entid(101)), a: Entid::Ident(NamespacedKeyword::new("test", "a")), v: ValueOrLookupRef::Value(Value::Text("v".into())), }, &[][..]))); } #[test] fn test_lookup_ref() { let input = [Value::Vector(vec![kw("db", "add"), Value::Vector(vec![kw("test", "a1"), Value::Text("v1".into())]), kw("test", "a"), Value::Text("v".into())])]; let mut parser = Tx::entity(); let result = parser.parse(&input[..]); assert_eq!(result, Ok((Entity::Add { e: EntidOrLookupRef::LookupRef(LookupRef { a: Entid::Ident(NamespacedKeyword::new("test", "a1")), v: Value::Text("v1".into()), }), a: Entid::Ident(NamespacedKeyword::new("test", "a")), v: ValueOrLookupRef::Value(Value::Text("v".into())), tx: None, }, &[][..]))); } }