From 245e7df147cc2c7b6a7bf6c8f124fe200dccbd5c Mon Sep 17 00:00:00 2001 From: Nick Alexander Date: Wed, 11 Jan 2017 15:21:42 -0800 Subject: [PATCH 1/2] Add tx{-parser} crates; start parsing transactions. This depends on edn and uses the combine parser combinator library. --- .travis.yml | 1 + Cargo.toml | 3 + edn/src/types.rs | 2 +- tx-parser/Cargo.toml | 11 ++ tx-parser/src/lib.rs | 378 +++++++++++++++++++++++++++++++++++++++++++ tx/Cargo.toml | 7 + tx/README.md | 1 + tx/src/entities.rs | 58 +++++++ tx/src/lib.rs | 11 ++ 9 files changed, 471 insertions(+), 1 deletion(-) create mode 100644 tx-parser/Cargo.toml create mode 100644 tx-parser/src/lib.rs create mode 100644 tx/Cargo.toml create mode 100644 tx/README.md create mode 100644 tx/src/entities.rs create mode 100644 tx/src/lib.rs diff --git a/.travis.yml b/.travis.yml index c72423669..e2b872d8a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,3 +4,4 @@ script: - cargo test --verbose - cargo test --verbose -p edn - cargo test --verbose -p mentat_query_parser + - cargo test --verbose -p mentat_tx_parser diff --git a/Cargo.toml b/Cargo.toml index ecf0f84fe..ce71e5982 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,6 @@ clap = "2.19.3" [dependencies.mentat_query_parser] path = "query-parser" + +[dependencies.mentat_tx_parser] + path = "tx-parser" diff --git a/edn/src/types.rs b/edn/src/types.rs index 1a9748f3d..7f969d071 100644 --- a/edn/src/types.rs +++ b/edn/src/types.rs @@ -16,7 +16,7 @@ use num::BigInt; use ordered_float::OrderedFloat; /// Value represents one of the allowed values in an EDN string. -#[derive(PartialEq, Eq, Hash, Debug)] +#[derive(PartialEq, Eq, Hash, Clone, Debug)] pub enum Value { Nil, Boolean(bool), diff --git a/tx-parser/Cargo.toml b/tx-parser/Cargo.toml new file mode 100644 index 000000000..8d1185e30 --- /dev/null +++ b/tx-parser/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "mentat_tx_parser" +version = "0.0.1" + +[dependencies] +combine = "2.1.1" + +[dependencies.edn] + path = "../edn" +[dependencies.mentat_tx] + path = "../tx" diff --git a/tx-parser/src/lib.rs b/tx-parser/src/lib.rs new file mode 100644 index 000000000..023b2b015 --- /dev/null +++ b/tx-parser/src/lib.rs @@ -0,0 +1,378 @@ +// 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}; +// TODO: understand why this is self::edn rather than just edn. +use self::edn::types::Value; +use mentat_tx::entities::*; + +// TODO: implement combine::Positioner on Value. We can't do this +// right now because Value is defined in edn and the trait is defined +// in combine. + +// #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +// pub struct ValuePosition { +// pub position: usize, +// } + +// impl fmt::Display for ValuePosition { +// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +// write!(f, "value position: {}", self.position) +// } +// } + +// impl combine::primitives::Positioner for Value { +// type Position = ValuePosition; +// fn start() -> ValuePosition { +// ValuePosition { position: 1 } +// } +// fn update(&self, position: &mut ValuePosition) { +// position.position += 1; +// } +// } + +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::Keyword(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::Keyword("db/add".into())), + 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::Keyword("db/retract".into())), + 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 retract_attribute_(input: I) -> ParseResult { + return satisfy_map(|x: Value| -> Option { + if let Value::Vector(y) = x { + let mut p = (token(Value::Keyword("db/retractAttribute".into())), + Tx::<&[Value]>::entid_or_lookup_ref(), + Tx::<&[Value]>::entid(), + eof()) + .map(|(_, e, a, _)| Entity::RetractAttribute { e: e, a: a }); + // 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_attribute() -> TxParser { + fn_parser(Tx::::retract_attribute_, "[:db/retractAttribute e a]") + } + + fn retract_entity_(input: I) -> ParseResult { + return satisfy_map(|x: Value| -> Option { + if let Value::Vector(y) = x { + let mut p = (token(Value::Keyword("db/retractEntity".into())), + Tx::<&[Value]>::entid_or_lookup_ref(), + eof()) + .map(|(_, e, _)| Entity::RetractEntity { e: e }); + // 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_entity() -> TxParser { + fn_parser(Tx::::retract_entity_, "[:db/retractEntity e]") + } + + fn entity_(input: I) -> ParseResult { + let mut p = Tx::::add() + .or(Tx::::retract()) + .or(Tx::::retract_attribute()) + .or(Tx::::retract_entity()); + p.parse_stream(input) + } + + fn entity() -> TxParser { + fn_parser(Tx::::entity_, + "[:db/add|:db/retract|:db/retractAttribute|:db/retractEntity ...]") + } + + 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|:db/retractAttribute|:db/retractEntity ...]*]") + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_add() { + let input = [Value::Vector(vec![Value::Keyword("db/add".into()), + Value::Keyword("ident".into()), + Value::Keyword("a".into()), + 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("ident".into())), + a: EntId::Ident("a".into()), + v: ValueOrLookupRef::Value(Value::Text("v".into())), + tx: None, + }, + &[][..]))); + } + + #[test] + fn test_retract() { + let input = [Value::Vector(vec![Value::Keyword("db/retract".into()), + Value::Integer(101), + Value::Keyword("a".into()), + 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("a".into()), + v: ValueOrLookupRef::Value(Value::Text("v".into())), + }, + &[][..]))); + } + + #[test] + fn test_entities() { + let input = [Value::Vector(vec![ + Value::Vector(vec![Value::Keyword("db/add".into()), + Value::Integer(101), + Value::Keyword("a".into()), + Value::Text("v".into())]), + Value::Vector(vec![Value::Keyword("db/retract".into()), + Value::Integer(102), + Value::Keyword("b".into()), + Value::Text("w".into())])])]; + + let mut parser = Tx::entities(); + let result = parser.parse(&input[..]); + assert_eq!(result, + Ok((vec![ + Entity::Add { + e: EntIdOrLookupRef::EntId(EntId::EntId(101)), + a: EntId::Ident("a".into()), + v: ValueOrLookupRef::Value(Value::Text("v".into())), + tx: None, + }, + Entity::Retract { + e: EntIdOrLookupRef::EntId(EntId::EntId(102)), + a: EntId::Ident("b".into()), + v: ValueOrLookupRef::Value(Value::Text("w".into())), + }, + ], + &[][..]))); + } + + #[test] + fn test_lookup_ref() { + let input = [Value::Vector(vec![Value::Keyword("db/add".into()), + Value::Vector(vec![Value::Keyword("a1".into()), + Value::Text("v1".into())]), + Value::Keyword("a".into()), + 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("a1".into()), + v: Value::Text("v1".into()), + }), + a: EntId::Ident("a".into()), + v: ValueOrLookupRef::Value(Value::Text("v".into())), + tx: None, + }, + &[][..]))); + } + + // TODO: test error handling in select cases. It's tricky to do + // this without combine::Positioner; see the TODO at the top of + // the file. +} diff --git a/tx/Cargo.toml b/tx/Cargo.toml new file mode 100644 index 000000000..72010b561 --- /dev/null +++ b/tx/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "mentat_tx" +version = "0.0.1" + +[dependencies] +[dependencies.edn] + path = "../edn" diff --git a/tx/README.md b/tx/README.md new file mode 100644 index 000000000..229497670 --- /dev/null +++ b/tx/README.md @@ -0,0 +1 @@ +This sub-crate implements the core types used by the transaction processor. \ No newline at end of file diff --git a/tx/src/entities.rs b/tx/src/entities.rs new file mode 100644 index 000000000..0ccb439fc --- /dev/null +++ b/tx/src/entities.rs @@ -0,0 +1,58 @@ +// 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. + +///! This module defines core types that support the transaction processor. + +extern crate edn; + +// TODO: understand why this is self::edn rather than just edn. +use self::edn::types::Value; + +#[derive(Clone, Debug, PartialEq)] +pub enum EntId { + EntId(i64), + Ident(String), +} + +#[derive(Clone, Debug, PartialEq)] +pub struct LookupRef { + pub a: EntId, + // TODO: consider boxing to allow recursive lookup refs. + pub v: Value, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum EntIdOrLookupRef { + EntId(EntId), + LookupRef(LookupRef), +} + +#[derive(Clone, Debug, PartialEq)] +pub enum ValueOrLookupRef { + Value(Value), + LookupRef(LookupRef), +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Entity { + Add { + e: EntIdOrLookupRef, + a: EntId, + v: ValueOrLookupRef, + tx: Option, + }, + Retract { + e: EntIdOrLookupRef, + a: EntId, + v: ValueOrLookupRef, + }, + RetractAttribute { e: EntIdOrLookupRef, a: EntId }, + RetractEntity { e: EntIdOrLookupRef }, +} diff --git a/tx/src/lib.rs b/tx/src/lib.rs new file mode 100644 index 000000000..78eac7485 --- /dev/null +++ b/tx/src/lib.rs @@ -0,0 +1,11 @@ +// 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. + +pub mod entities; From 929f3608b86efad12b1514438c9d4f548687e7f2 Mon Sep 17 00:00:00 2001 From: Nick Alexander Date: Thu, 12 Jan 2017 11:07:59 -0800 Subject: [PATCH 2/2] EntId -> Entid; use new NamespacedKeyword; split out integration test. --- tx-parser/Cargo.toml | 1 + tx-parser/src/lib.rs | 155 ++++++++++++++------------------------ tx-parser/tests/parser.rs | 49 ++++++++++++ tx/src/entities.rs | 28 +++---- 4 files changed, 121 insertions(+), 112 deletions(-) create mode 100644 tx-parser/tests/parser.rs diff --git a/tx-parser/Cargo.toml b/tx-parser/Cargo.toml index 8d1185e30..7601720bc 100644 --- a/tx-parser/Cargo.toml +++ b/tx-parser/Cargo.toml @@ -7,5 +7,6 @@ combine = "2.1.1" [dependencies.edn] path = "../edn" + [dependencies.mentat_tx] path = "../tx" diff --git a/tx-parser/src/lib.rs b/tx-parser/src/lib.rs index 023b2b015..8b136ba48 100644 --- a/tx-parser/src/lib.rs +++ b/tx-parser/src/lib.rs @@ -16,36 +16,11 @@ extern crate mentat_tx; use combine::{any, eof, many, optional, parser, satisfy_map, token, Parser, ParseResult, Stream}; use combine::combinator::{Expected, FnParser}; -// TODO: understand why this is self::edn rather than just edn. -use self::edn::types::Value; +use edn::symbols::NamespacedKeyword; +use edn::types::Value; use mentat_tx::entities::*; -// TODO: implement combine::Positioner on Value. We can't do this -// right now because Value is defined in edn and the trait is defined -// in combine. - -// #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] -// pub struct ValuePosition { -// pub position: usize, -// } - -// impl fmt::Display for ValuePosition { -// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { -// write!(f, "value position: {}", self.position) -// } -// } - -// impl combine::primitives::Positioner for Value { -// type Position = ValuePosition; -// fn start() -> ValuePosition { -// ValuePosition { position: 1 } -// } -// fn update(&self, position: &mut ValuePosition) { -// position.position += 1; -// } -// } - -struct Tx(::std::marker::PhantomData I>); +pub struct Tx(::std::marker::PhantomData I>); type TxParser = Expected ParseResult>>; @@ -71,12 +46,12 @@ impl Tx .parse_stream(input); } - fn keyword() -> TxParser { + fn keyword() -> TxParser { fn_parser(Tx::::keyword_, "keyword") } - fn keyword_(input: I) -> ParseResult { - return satisfy_map(|x: Value| if let Value::Keyword(y) = x { + fn keyword_(input: I) -> ParseResult { + return satisfy_map(|x: Value| if let Value::NamespacedKeyword(y) = x { Some(y) } else { None @@ -84,14 +59,14 @@ impl Tx .parse_stream(input); } - fn entid() -> TxParser { + fn entid() -> TxParser { fn_parser(Tx::::entid_, "entid") } - fn entid_(input: I) -> ParseResult { + fn entid_(input: I) -> ParseResult { let p = Tx::::integer() - .map(|x| EntId::EntId(x)) - .or(Tx::::keyword().map(|x| EntId::Ident(x))) + .map(|x| Entid::Entid(x)) + .or(Tx::::keyword().map(|x| Entid::Ident(x))) .parse_lazy(input) .into(); return p; @@ -116,14 +91,14 @@ impl Tx .parse_stream(input); } - fn entid_or_lookup_ref() -> TxParser { + fn entid_or_lookup_ref() -> TxParser { fn_parser(Tx::::entid_or_lookup_ref_, "entid|lookup-ref") } - fn entid_or_lookup_ref_(input: I) -> ParseResult { + 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))) + .map(|x| EntidOrLookupRef::Entid(x)) + .or(Tx::::lookup_ref().map(|x| EntidOrLookupRef::LookupRef(x))) .parse_lazy(input) .into(); return p; @@ -133,7 +108,8 @@ impl Tx fn add_(input: I) -> ParseResult { return satisfy_map(|x: Value| -> Option { if let Value::Vector(y) = x { - let mut p = (token(Value::Keyword("db/add".into())), + let mut p = (token(Value::NamespacedKeyword(NamespacedKeyword::new("db", + "add"))), Tx::<&[Value]>::entid_or_lookup_ref(), Tx::<&[Value]>::entid(), // TODO: handle lookup-ref. @@ -168,7 +144,8 @@ impl Tx fn retract_(input: I) -> ParseResult { return satisfy_map(|x: Value| -> Option { if let Value::Vector(y) = x { - let mut p = (token(Value::Keyword("db/retract".into())), + let mut p = (token(Value::NamespacedKeyword(NamespacedKeyword::new("db", + "retract"))), Tx::<&[Value]>::entid_or_lookup_ref(), Tx::<&[Value]>::entid(), // TODO: handle lookup-ref. @@ -200,7 +177,7 @@ impl Tx fn retract_attribute_(input: I) -> ParseResult { return satisfy_map(|x: Value| -> Option { if let Value::Vector(y) = x { - let mut p = (token(Value::Keyword("db/retractAttribute".into())), + let mut p = (token(Value::NamespacedKeyword(NamespacedKeyword::new("db", "retractAttribute"))), Tx::<&[Value]>::entid_or_lookup_ref(), Tx::<&[Value]>::entid(), eof()) @@ -224,10 +201,12 @@ impl Tx fn retract_entity_(input: I) -> ParseResult { return satisfy_map(|x: Value| -> Option { if let Value::Vector(y) = x { - let mut p = (token(Value::Keyword("db/retractEntity".into())), - Tx::<&[Value]>::entid_or_lookup_ref(), - eof()) - .map(|(_, e, _)| Entity::RetractEntity { e: e }); + let mut p = + (token(Value::NamespacedKeyword(NamespacedKeyword::new("db", + "retractEntity"))), + Tx::<&[Value]>::entid_or_lookup_ref(), + eof()) + .map(|(_, e, _)| Entity::RetractEntity { e: e }); // TODO: use ok() with a type annotation rather than explicit match. match p.parse_lazy(&y[..]).into() { Ok((r, _)) => Some(r), @@ -260,8 +239,7 @@ impl Tx 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); + 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), @@ -278,24 +256,40 @@ impl Tx fn_parser(Tx::::entities_, "[[:db/add|:db/retract|:db/retractAttribute|:db/retractEntity ...]*]") } + + 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![Value::Keyword("db/add".into()), - Value::Keyword("ident".into()), - Value::Keyword("a".into()), + 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("ident".into())), - a: EntId::Ident("a".into()), + 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, }, @@ -304,75 +298,40 @@ mod tests { #[test] fn test_retract() { - let input = [Value::Vector(vec![Value::Keyword("db/retract".into()), + let input = [Value::Vector(vec![kw("db", "retract"), Value::Integer(101), - Value::Keyword("a".into()), + 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("a".into()), + e: EntidOrLookupRef::Entid(Entid::Entid(101)), + a: Entid::Ident(NamespacedKeyword::new("test", "a")), v: ValueOrLookupRef::Value(Value::Text("v".into())), }, &[][..]))); } - #[test] - fn test_entities() { - let input = [Value::Vector(vec![ - Value::Vector(vec![Value::Keyword("db/add".into()), - Value::Integer(101), - Value::Keyword("a".into()), - Value::Text("v".into())]), - Value::Vector(vec![Value::Keyword("db/retract".into()), - Value::Integer(102), - Value::Keyword("b".into()), - Value::Text("w".into())])])]; - - let mut parser = Tx::entities(); - let result = parser.parse(&input[..]); - assert_eq!(result, - Ok((vec![ - Entity::Add { - e: EntIdOrLookupRef::EntId(EntId::EntId(101)), - a: EntId::Ident("a".into()), - v: ValueOrLookupRef::Value(Value::Text("v".into())), - tx: None, - }, - Entity::Retract { - e: EntIdOrLookupRef::EntId(EntId::EntId(102)), - a: EntId::Ident("b".into()), - v: ValueOrLookupRef::Value(Value::Text("w".into())), - }, - ], - &[][..]))); - } - #[test] fn test_lookup_ref() { - let input = [Value::Vector(vec![Value::Keyword("db/add".into()), - Value::Vector(vec![Value::Keyword("a1".into()), + let input = [Value::Vector(vec![kw("db", "add"), + Value::Vector(vec![kw("test", "a1"), Value::Text("v1".into())]), - Value::Keyword("a".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("a1".into()), + e: EntidOrLookupRef::LookupRef(LookupRef { + a: Entid::Ident(NamespacedKeyword::new("test", "a1")), v: Value::Text("v1".into()), }), - a: EntId::Ident("a".into()), + a: Entid::Ident(NamespacedKeyword::new("test", "a")), v: ValueOrLookupRef::Value(Value::Text("v".into())), tx: None, }, &[][..]))); } - - // TODO: test error handling in select cases. It's tricky to do - // this without combine::Positioner; see the TODO at the top of - // the file. } diff --git a/tx-parser/tests/parser.rs b/tx-parser/tests/parser.rs new file mode 100644 index 000000000..774245602 --- /dev/null +++ b/tx-parser/tests/parser.rs @@ -0,0 +1,49 @@ +// 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 edn; +extern crate combine; +extern crate mentat_tx; +extern crate mentat_tx_parser; + +use edn::parse; +use edn::symbols::NamespacedKeyword; +use edn::types::Value; +use mentat_tx::entities::*; +use mentat_tx_parser::Tx; + +#[test] +fn test_entities() { + + // TODO: align with whitespace after the EDN parser ignores more whitespace. + let input = r#"[[:db/add 101 :test/a "v"] +[:db/retract 102 :test/b "w"]]"#; + + let edn = parse::value(input).unwrap(); + let input = [edn]; + + let result = Tx::parse(&input[..]); + assert_eq!(result, + Ok(vec![ + Entity::Add { + e: EntidOrLookupRef::Entid(Entid::Entid(101)), + a: Entid::Ident(NamespacedKeyword::new("test", "a")), + v: ValueOrLookupRef::Value(Value::Text("v".into())), + tx: None, + }, + Entity::Retract { + e: EntidOrLookupRef::Entid(Entid::Entid(102)), + a: Entid::Ident(NamespacedKeyword::new("test", "b")), + v: ValueOrLookupRef::Value(Value::Text("w".into())), + }, + ])); +} + +// TODO: test error handling in select cases. diff --git a/tx/src/entities.rs b/tx/src/entities.rs index 0ccb439fc..3c99b622c 100644 --- a/tx/src/entities.rs +++ b/tx/src/entities.rs @@ -12,25 +12,25 @@ extern crate edn; -// TODO: understand why this is self::edn rather than just edn. use self::edn::types::Value; +use self::edn::symbols::NamespacedKeyword; #[derive(Clone, Debug, PartialEq)] -pub enum EntId { - EntId(i64), - Ident(String), +pub enum Entid { + Entid(i64), + Ident(NamespacedKeyword), } #[derive(Clone, Debug, PartialEq)] pub struct LookupRef { - pub a: EntId, + pub a: Entid, // TODO: consider boxing to allow recursive lookup refs. pub v: Value, } #[derive(Clone, Debug, PartialEq)] -pub enum EntIdOrLookupRef { - EntId(EntId), +pub enum EntidOrLookupRef { + Entid(Entid), LookupRef(LookupRef), } @@ -43,16 +43,16 @@ pub enum ValueOrLookupRef { #[derive(Clone, Debug, PartialEq)] pub enum Entity { Add { - e: EntIdOrLookupRef, - a: EntId, + e: EntidOrLookupRef, + a: Entid, v: ValueOrLookupRef, - tx: Option, + tx: Option, }, Retract { - e: EntIdOrLookupRef, - a: EntId, + e: EntidOrLookupRef, + a: Entid, v: ValueOrLookupRef, }, - RetractAttribute { e: EntIdOrLookupRef, a: EntId }, - RetractEntity { e: EntIdOrLookupRef }, + RetractAttribute { e: EntidOrLookupRef, a: Entid }, + RetractEntity { e: EntidOrLookupRef }, }