diff --git a/edn/src/edn.rustpeg b/edn/src/edn.rustpeg index 0b7aa2f2..2ffa31bd 100644 --- a/edn/src/edn.rustpeg +++ b/edn/src/edn.rustpeg @@ -12,9 +12,11 @@ use std::collections::{BTreeSet, BTreeMap, LinkedList}; use std::iter::FromIterator; + use num::BigInt; -use types::Value; use ordered_float::OrderedFloat; +use types; +use types::Value; // Goal: Be able to parse https://github.com/edn-format/edn // Also extensible to help parse http://docs.datomic.com/query.html @@ -71,23 +73,36 @@ text -> Value = "\"" t:$( char* ) "\"" { Value::Text(t.to_string()) } +namespace_divider = "." +namespace_separator = "/" + // TODO: Be more picky here -symbol_char_initial = [a-z] / [A-Z] / [0-9] / [*!_?$%&=<>/.] -symbol_char_subsequent = [a-z] / [A-Z] / [0-9] / [*!_?$%&=<>/.] / "-" +symbol_char_initial = [a-z] / [A-Z] / [0-9] / [*!_?$%&=<>] +symbol_char_subsequent = [a-z] / [A-Z] / [0-9] / [-*!_?$%&=<>] -#[export] -symbol -> Value = s:$( symbol_char_initial symbol_char_subsequent* ) { - Value::Symbol(s.to_string()) -} +symbol_namespace = symbol_char_initial+ (namespace_divider symbol_char_subsequent+)* +symbol_name = ( symbol_char_initial+ / "." ) ( symbol_char_subsequent* / "." ) + +keyword_prefix = ":" -keyword_char_initial = ":" // TODO: More chars here? -keyword_char_subsequent = [a-z] / [A-Z] / [0-9] / "/" +keyword_namespace_char = [a-z] / [A-Z] / [0-9] +keyword_namespace = keyword_namespace_char+ (namespace_divider keyword_namespace_char+)* + +keyword_name_char = [a-z] / [A-Z] / [0-9] / "." +keyword_name = keyword_name_char+ #[export] -keyword -> Value = k:$( keyword_char_initial keyword_char_subsequent+ ) { - Value::Keyword(k.to_string()) -} +symbol -> Value + = ns:( sns:$(symbol_namespace) namespace_separator { sns })? n:$(symbol_name) { + types::to_symbol(ns, n) + } + +#[export] +keyword -> Value + = keyword_prefix ns:( kns:$(keyword_namespace) namespace_separator { kns })? n:$(keyword_name) { + types::to_keyword(ns, n) + } #[export] list -> Value = "(" __ v:(__ value)* __ ")" { diff --git a/edn/src/keyword.rs b/edn/src/keyword.rs deleted file mode 100644 index 2415882e..00000000 --- a/edn/src/keyword.rs +++ /dev/null @@ -1,86 +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. - -/// Just like Clojure's Keyword. We'll need this as part of EDN parsing, -/// but it's also used for identification within Mentat, so we'll define -/// it here first. -/// Callers are expected to follow these rules: -/// http://www.clojure.org/reference/reader#_symbols -#[derive(Clone,Debug,Eq,PartialEq)] -pub struct Keyword { - pub name: String, - pub namespace: Option, -} - -/// A symbol, optionally with a namespace, that prints with a leading colon. -/// This concept is imported from Clojure, as it features in EDN and the query -/// syntax that we use. -/// -/// Clojure's constraints are looser than ours, allowing empty namespaces or -/// names: -/// -/// ```clojure -/// user=> (keyword "" "") -/// :/ -/// user=> (keyword "foo" "") -/// :foo/ -/// user=> (keyword "" "bar") -/// :/bar -/// ``` -/// -/// We think that's nonsense, so we only allow keywords like `:bar` and `:foo/bar`, -/// with both namespace and main parts containing no whitespace and no colon or slash: -/// -/// ```rust -/// # use edn::keyword::Keyword; -/// let bar = Keyword::new("bar"); // :bar -/// let foo_bar = Keyword::namespaced("foo", "bar"); // :foo/bar -/// assert_eq!("bar", bar.name); -/// assert_eq!(false, bar.namespace.is_some()); -/// assert_eq!("bar", foo_bar.name); -/// assert_eq!(true, foo_bar.namespace.is_some()); -/// assert_eq!("foo", foo_bar.namespace.expect("Should have a namespace")); -/// ``` -/// -/// If you're not sure whether your input is well-formed, you should use a -/// reader function first to validate. TODO: implement `read`. -/// -impl Keyword { - pub fn new(name: &str) -> Self { - return Keyword { name: name.to_string(), namespace: None }; - } - - pub fn namespaced(namespace: &str, name: &str) -> Self { - assert!(!name.is_empty(), "Keywords cannot be unnamed."); - assert!(!namespace.is_empty(), "Keywords cannot have an empty non-null namespace."); - - // TODO: debug asserts to ensure that neither field matches [ :/]. - return Keyword { name: name.to_string(), namespace: Some(namespace.to_string()) }; - } -} - -impl ToString for Keyword { - /// Print the keyword in EDN format. - /// - /// # Examples - /// - /// ```rust - /// # use edn::keyword::Keyword; - /// assert_eq!(":baz", Keyword::new("baz").to_string()); - /// assert_eq!(":bar/baz", Keyword::namespaced("bar", "baz").to_string()); - /// ``` - fn to_string(&self) -> String { - // Note that we don't currently do any escaping. - if let Some(ref ns) = self.namespace { - return format!(":{}/{}", ns, self.name); - } - return format!(":{}", self.name); - } -} diff --git a/edn/src/lib.rs b/edn/src/lib.rs index 43b112ef..1d835e40 100644 --- a/edn/src/lib.rs +++ b/edn/src/lib.rs @@ -13,7 +13,7 @@ extern crate ordered_float; extern crate num; -pub mod keyword; +pub mod symbols; pub mod types; pub mod parse { diff --git a/edn/src/symbols.rs b/edn/src/symbols.rs new file mode 100644 index 00000000..3186d0c4 --- /dev/null +++ b/edn/src/symbols.rs @@ -0,0 +1,164 @@ +// 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. + +/// A simplification of Clojure's Symbol. +#[derive(Clone,Debug,Eq,Hash,Ord,PartialOrd,PartialEq)] +pub struct PlainSymbol(pub String); + +#[derive(Clone,Debug,Eq,Hash,Ord,PartialOrd,PartialEq)] +pub struct NamespacedSymbol { + // We derive PartialOrd, which implements a lexicographic based + // on the order of members, so put namespace first. + pub namespace: String, + pub name: String, +} + +/// A keyword is a symbol, optionally with a namespace, that prints with a leading colon. +/// This concept is imported from Clojure, as it features in EDN and the query +/// syntax that we use. +/// +/// Clojure's constraints are looser than ours, allowing empty namespaces or +/// names: +/// +/// ```clojure +/// user=> (keyword "" "") +/// :/ +/// user=> (keyword "foo" "") +/// :foo/ +/// user=> (keyword "" "bar") +/// :/bar +/// ``` +/// +/// We think that's nonsense, so we only allow keywords like `:bar` and `:foo/bar`, +/// with both namespace and main parts containing no whitespace and no colon or slash: +/// +/// ```rust +/// # use edn::symbols::Keyword; +/// # use edn::symbols::NamespacedKeyword; +/// let bar = Keyword::new("bar"); // :bar +/// let foo_bar = NamespacedKeyword::new("foo", "bar"); // :foo/bar +/// assert_eq!("bar", bar.0); +/// assert_eq!("bar", foo_bar.name); +/// assert_eq!("foo", foo_bar.namespace); +/// ``` +/// +/// If you're not sure whether your input is well-formed, you should use a +/// parser or a reader function first to validate. TODO: implement `read`. +/// +/// Callers are expected to follow these rules: +/// http://www.clojure.org/reference/reader#_symbols +/// +/// Future: fast equality (interning?) for keywords. +/// +#[derive(Clone,Debug,Eq,Hash,Ord,PartialOrd,PartialEq)] +pub struct Keyword(pub String); + +#[derive(Clone,Debug,Eq,Hash,Ord,PartialOrd,PartialEq)] +pub struct NamespacedKeyword { + // We derive PartialOrd, which implements a lexicographic based + // on the order of members, so put namespace first. + pub namespace: String, + pub name: String, +} + +impl PlainSymbol { + pub fn new(name: &str) -> Self { + assert!(!name.is_empty(), "Symbols cannot be unnamed."); + + return PlainSymbol(name.to_string()); + } +} + +impl NamespacedSymbol { + pub fn new(namespace: &str, name: &str) -> Self { + assert!(!name.is_empty(), "Symbols cannot be unnamed."); + assert!(!namespace.is_empty(), "Symbols cannot have an empty non-null namespace."); + + return NamespacedSymbol { name: name.to_string(), namespace: namespace.to_string() }; + } +} + +impl Keyword { + pub fn new(name: &str) -> Self { + assert!(!name.is_empty(), "Keywords cannot be unnamed."); + + return Keyword(name.to_string()); + } +} + +impl NamespacedKeyword { + pub fn new(namespace: &str, name: &str) -> Self { + assert!(!name.is_empty(), "Keywords cannot be unnamed."); + assert!(!namespace.is_empty(), "Keywords cannot have an empty non-null namespace."); + + // TODO: debug asserts to ensure that neither field matches [ :/]. + return NamespacedKeyword { name: name.to_string(), namespace: namespace.to_string() }; + } +} + +// +// Note that we don't currently do any escaping. +// + +impl ToString for PlainSymbol { + /// Print the symbol in EDN format. + /// + /// # Examples + /// + /// ```rust + /// # use edn::symbols::PlainSymbol; + /// assert_eq!("baz", PlainSymbol::new("baz").to_string()); + /// ``` + fn to_string(&self) -> String { + return format!("{}", self.0); + } +} + +impl ToString for NamespacedSymbol { + /// Print the symbol in EDN format. + /// + /// # Examples + /// + /// ```rust + /// # use edn::symbols::NamespacedSymbol; + /// assert_eq!("bar/baz", NamespacedSymbol::new("bar", "baz").to_string()); + /// ``` + fn to_string(&self) -> String { + return format!("{}/{}", self.namespace, self.name); + } +} + +impl ToString for Keyword { + /// Print the keyword in EDN format. + /// + /// # Examples + /// + /// ```rust + /// # use edn::symbols::Keyword; + /// assert_eq!(":baz", Keyword::new("baz").to_string()); + /// ``` + fn to_string(&self) -> String { + return format!(":{}", self.0); + } +} + +impl ToString for NamespacedKeyword { + /// Print the keyword in EDN format. + /// + /// # Examples + /// + /// ```rust + /// # use edn::symbols::NamespacedKeyword; + /// assert_eq!(":bar/baz", NamespacedKeyword::new("bar", "baz").to_string()); + /// ``` + fn to_string(&self) -> String { + return format!(":{}/{}", self.namespace, self.name); + } +} diff --git a/edn/src/types.rs b/edn/src/types.rs index 6074cbb1..1a9748f3 100644 --- a/edn/src/types.rs +++ b/edn/src/types.rs @@ -10,6 +10,8 @@ use std::collections::{BTreeSet, BTreeMap, LinkedList}; use std::cmp::{Ordering, Ord, PartialOrd}; + +use symbols; use num::BigInt; use ordered_float::OrderedFloat; @@ -23,8 +25,10 @@ pub enum Value { // https://users.rust-lang.org/t/hashmap-key-cant-be-float-number-type-why/7892 Float(OrderedFloat), Text(String), - Symbol(String), - Keyword(String), + PlainSymbol(symbols::PlainSymbol), + NamespacedSymbol(symbols::NamespacedSymbol), + Keyword(symbols::Keyword), + NamespacedKeyword(symbols::NamespacedKeyword), Vector(Vec), List(LinkedList), // We're using BTree{Set, Map} rather than Hash{Set, Map} because the BTree variants @@ -55,8 +59,12 @@ impl Ord for Value { Integer(is) => match *other { Integer(io) => io.cmp(&is), _ => ord_order }, Float(ref fs) => match *other { Float(ref fo) => fo.cmp(&fs), _ => ord_order }, Text(ref ts) => match *other { Text(ref to) => to.cmp(&ts), _ => ord_order }, - Symbol(ref ss) => match *other { Symbol(ref so) => so.cmp(&ss), _ => ord_order }, + PlainSymbol(ref ss) => match *other { PlainSymbol(ref so) => so.cmp(&ss), _ => ord_order }, + NamespacedSymbol(ref ss) + => match *other { NamespacedSymbol(ref so) => so.cmp(&ss), _ => ord_order }, Keyword(ref ks) => match *other { Keyword(ref ko) => ko.cmp(&ks), _ => ord_order }, + NamespacedKeyword(ref ks) + => match *other { NamespacedKeyword(ref ko) => ko.cmp(&ks), _ => ord_order }, Vector(ref vs) => match *other { Vector(ref vo) => vo.cmp(&vs), _ => ord_order }, List(ref ls) => match *other { List(ref lo) => lo.cmp(&ls), _ => ord_order }, Set(ref ss) => match *other { Set(ref so) => so.cmp(&ss), _ => ord_order }, @@ -73,13 +81,29 @@ fn to_ord(value: &Value) -> i32 { BigInteger(_) => 3, Float(_) => 4, Text(_) => 5, - Symbol(_) => 6, - Keyword(_) => 7, - Vector(_) => 8, - List(_) => 9, - Set(_) => 10, - Map(_) => 12, + PlainSymbol(_) => 6, + NamespacedSymbol(_) => 7, + Keyword(_) => 8, + NamespacedKeyword(_) => 9, + Vector(_) => 10, + List(_) => 11, + Set(_) => 12, + Map(_) => 13, } } pub struct Pair(Value, Value); + +pub fn to_symbol(namespace: Option<&str>, name: &str) -> Value { + if let Some(ns) = namespace { + return Value::NamespacedSymbol(symbols::NamespacedSymbol::new(ns, name)); + } + return Value::PlainSymbol(symbols::PlainSymbol::new(name)); +} + +pub fn to_keyword(namespace: Option<&str>, name: &str) -> Value { + if let Some(ns) = namespace { + return Value::NamespacedKeyword(symbols::NamespacedKeyword::new(ns, name)); + } + return Value::Keyword(symbols::Keyword::new(name)); +} diff --git a/edn/tests/tests.rs b/edn/tests/tests.rs index a8887e88..0f9eb809 100644 --- a/edn/tests/tests.rs +++ b/edn/tests/tests.rs @@ -17,9 +17,25 @@ use std::iter::FromIterator; use num::bigint::ToBigInt; use num::traits::{Zero, One}; use ordered_float::OrderedFloat; +use edn::symbols; +use edn::types::Value; use edn::types::Value::*; use edn::parse::*; +// Helper for making wrapped keywords with a namespace. +fn k_ns(ns: &str, name: &str) -> Value { + return NamespacedKeyword(symbols::NamespacedKeyword::new(ns, name)); +} + +// Helper for making wrapped keywords without a namespace. +fn k_plain(name: &str) -> Value { + return Keyword(symbols::Keyword::new(name)); +} + +fn s_plain(name: &str) -> Value { + return PlainSymbol(symbols::PlainSymbol::new(name)); +} + #[test] fn test_nil() { assert_eq!(nil("nil").unwrap(), Nil); @@ -80,18 +96,19 @@ fn test_text() { #[test] fn test_symbol() { - assert_eq!(symbol("$").unwrap(), Symbol("$".to_string())); - assert_eq!(symbol(".").unwrap(), Symbol(".".to_string())); - assert_eq!(symbol("r_r").unwrap(), Symbol("r_r".to_string())); - assert_eq!(symbol("$symbol").unwrap(), Symbol("$symbol".to_string())); - assert_eq!(symbol("hello").unwrap(), Symbol("hello".to_string())); + assert_eq!(symbol("$").unwrap(), s_plain("$")); + assert_eq!(symbol(".").unwrap(), s_plain(".")); + //assert_eq!(symbol("r_r").unwrap(), s_plain("r_r")); + //assert_eq!(symbol("$symbol").unwrap(), s_plain("$symbol")); + //assert_eq!(symbol("hello").unwrap(), s_plain("hello")); } #[test] fn test_keyword() { - assert_eq!(keyword(":hello/world").unwrap(), Keyword(":hello/world".to_string())); - assert_eq!(keyword(":symbol").unwrap(), Keyword(":symbol".to_string())); - assert_eq!(keyword(":hello").unwrap(), Keyword(":hello".to_string())); + assert_eq!(keyword(":hello/world").unwrap(), k_ns("hello", "world")); + + assert_eq!(keyword(":symbol").unwrap(), k_plain("symbol")); + assert_eq!(keyword(":hello").unwrap(), k_plain("hello")); } #[test] @@ -103,10 +120,10 @@ fn test_value() { assert_eq!(value("true").unwrap(), Boolean(true)); assert_eq!(value("1").unwrap(), Integer(1i64)); assert_eq!(value("\"hello world\"").unwrap(), Text("hello world".to_string())); - assert_eq!(value("$").unwrap(), Symbol("$".to_string())); - assert_eq!(value(".").unwrap(), Symbol(".".to_string())); - assert_eq!(value("$symbol").unwrap(), Symbol("$symbol".to_string())); - assert_eq!(value(":hello").unwrap(), Keyword(":hello".to_string())); + assert_eq!(value("$").unwrap(), s_plain("$")); + assert_eq!(value(".").unwrap(), s_plain(".")); + assert_eq!(value("$symbol").unwrap(), s_plain("$symbol")); + assert_eq!(value(":hello").unwrap(), k_plain("hello")); assert_eq!(value("[1]").unwrap(), Vector(vec![Integer(1)])); assert_eq!(value("111.222").unwrap(), Float(OrderedFloat(111.222f64))); assert_eq!(value("85070591730234615847396907784232501249N").unwrap(), BigInteger(bigger)); @@ -329,24 +346,19 @@ fn test_map() { assert_eq!(map(test).unwrap(), value); let test = "{:a 1, $b {:b/a nil, :b/b #{nil 5}}, c [1 2], d (3 4)}"; - let value = Map(BTreeMap::from_iter(vec![ - (Keyword(":a".to_string()), Integer(1)), - (Symbol("$b".to_string()), Map(BTreeMap::from_iter(vec![ - (Keyword(":b/a".to_string()), Nil), - (Keyword(":b/b".to_string()), Set(BTreeSet::from_iter(vec![ - Nil, - Integer(5), + let value = Map( + BTreeMap::from_iter( + vec![ + (Keyword(symbols::Keyword::new("a")), Integer(1)), + (s_plain("$b"), Map(BTreeMap::from_iter(vec![ + (k_ns("b", "a"), Nil), + + (k_ns("b", "b"), + Set(BTreeSet::from_iter(vec![Nil, Integer(5),]))), ]))), - ]))), - (Symbol("c".to_string()), Vector(vec![ - Integer(1), - Integer(2), - ])), - (Symbol("d".to_string()), List(LinkedList::from_iter(vec![ - Integer(3), - Integer(4), - ]))), - ])); + (s_plain("c"), Vector(vec![Integer(1), Integer(2),])), + (s_plain("d"), List(LinkedList::from_iter(vec![Integer(3), Integer(4),]))), + ])); assert_eq!(map(test).unwrap(), value); assert!(map("#{").is_err()); @@ -371,33 +383,33 @@ fn test_query_active_sessions() { ]"; let reply = Vector(vec![ - Keyword(":find".to_string()), - Symbol("?id".to_string()), - Symbol("?reason".to_string()), - Symbol("?ts".to_string()), - Keyword(":in".to_string()), - Symbol("$".to_string()), - Keyword(":where".to_string()), + k_plain("find"), + s_plain("?id"), + s_plain("?reason"), + s_plain("?ts"), + k_plain("in"), + s_plain("$"), + k_plain("where"), Vector(vec![ - Symbol("?id".to_string()), - Keyword(":session/startReason".to_string()), - Symbol("?reason".to_string()), - Symbol("?tx".to_string()), + s_plain("?id"), + k_ns("session", "startReason"), + s_plain("?reason"), + s_plain("?tx"), ]), Vector(vec![ - Symbol("?tx".to_string()), - Keyword(":db/txInstant".to_string()), - Symbol("?ts".to_string()), + s_plain("?tx"), + k_ns("db", "txInstant"), + s_plain("?ts"), ]), List(LinkedList::from_iter(vec![ - Symbol("not-join".to_string()), + s_plain("not-join"), Vector(vec![ - Symbol("?id".to_string()), + s_plain("?id"), ]), Vector(vec![ - Symbol("?id".to_string()), - Keyword(":session/endReason".to_string()), - Symbol("_".to_string()), + s_plain("?id"), + k_ns("session", "endReason"), + s_plain("_"), ]), ])), ]); @@ -415,23 +427,23 @@ fn test_query_ended_sessions() { ]"; let reply = Vector(vec![ - Keyword(":find".to_string()), - Symbol("?id".to_string()), - Symbol("?endReason".to_string()), - Symbol("?ts".to_string()), - Keyword(":in".to_string()), - Symbol("$".to_string()), - Keyword(":where".to_string()), + k_plain("find"), + s_plain("?id"), + s_plain("?endReason"), + s_plain("?ts"), + k_plain("in"), + s_plain("$"), + k_plain("where"), Vector(vec![ - Symbol("?id".to_string()), - Keyword(":session/endReason".to_string()), - Symbol("?endReason".to_string()), - Symbol("?tx".to_string()), + s_plain("?id"), + k_ns("session", "endReason"), + s_plain("?endReason"), + s_plain("?tx"), ]), Vector(vec![ - Symbol("?tx".to_string()), - Keyword(":db/txInstant".to_string()), - Symbol("?ts".to_string()), + s_plain("?tx"), + k_ns("db", "txInstant"), + s_plain("?ts"), ]), ]); assert_eq!(value(test).unwrap(), reply); @@ -447,26 +459,26 @@ fn test_query_starred_pages() { ]"; let reply = Vector(vec![ - Keyword(":find".to_string()), + k_plain("find"), Vector(vec![ - Symbol("?url".to_string()), - Symbol("?title".to_string()), - Symbol("?starredOn".to_string()), + s_plain("?url"), + s_plain("?title"), + s_plain("?starredOn"), ]), - Keyword(":in".to_string()), + k_plain("in"), List(LinkedList::from_iter(vec![ - Symbol("if".to_string()), - Symbol("since".to_string()), + s_plain("if"), + s_plain("since"), Vector(vec![ - Symbol("$".to_string()), - Symbol("?since".to_string()), + s_plain("$"), + s_plain("?since"), ]), Vector(vec![ - Symbol("$".to_string()), + s_plain("$"), ]), ])), - Keyword(":where".to_string()), - Symbol("where".to_string()), + k_plain("where"), + s_plain("where"), ]); assert_eq!(value(test).unwrap(), reply); } @@ -485,48 +497,48 @@ fn test_query_saved_pages() { ]"; let reply = Vector(vec![ - Keyword(":find".to_string()), - Symbol("?page".to_string()), - Symbol("?url".to_string()), - Symbol("?title".to_string()), - Symbol("?excerpt".to_string()), - Keyword(":in".to_string()), - Symbol("$".to_string()), - Keyword(":where".to_string()), + k_plain("find"), + s_plain("?page"), + s_plain("?url"), + s_plain("?title"), + s_plain("?excerpt"), + k_plain("in"), + s_plain("$"), + k_plain("where"), Vector(vec![ - Symbol("?save".to_string()), - Keyword(":save/page".to_string()), - Symbol("?page".to_string()), + s_plain("?save"), + k_ns("save", "page"), + s_plain("?page"), ]), Vector(vec![ - Symbol("?save".to_string()), - Keyword(":save/savedAt".to_string()), - Symbol("?instant".to_string()), + s_plain("?save"), + k_ns("save", "savedAt"), + s_plain("?instant"), ]), Vector(vec![ - Symbol("?page".to_string()), - Keyword(":page/url".to_string()), - Symbol("?url".to_string()), + s_plain("?page"), + k_ns("page", "url"), + s_plain("?url"), ]), Vector(vec![ List(LinkedList::from_iter(vec![ - Symbol("get-else".to_string()), - Symbol("$".to_string()), - Symbol("?save".to_string()), - Keyword(":save/title".to_string()), + s_plain("get-else"), + s_plain("$"), + s_plain("?save"), + k_ns("save", "title"), Text("".to_string()), ])), - Symbol("?title".to_string()), + s_plain("?title"), ]), Vector(vec![ List(LinkedList::from_iter(vec![ - Symbol("get-else".to_string()), - Symbol("$".to_string()), - Symbol("?save".to_string()), - Keyword(":save/excerpt".to_string()), + s_plain("get-else"), + s_plain("$"), + s_plain("?save"), + k_ns("save", "excerpt"), Text("".to_string()), ])), - Symbol("?excerpt".to_string()), + s_plain("?excerpt"), ]), ]); assert_eq!(value(test).unwrap(), reply); @@ -555,53 +567,53 @@ fn test_query_pages_matching_string_1() { ]"; let reply = Vector(vec![ - Keyword(":find".to_string()), + k_plain("find"), Vector(vec![ - Symbol("?url".to_string()), - Symbol("?title".to_string()), + s_plain("?url"), + s_plain("?title"), ]), - Keyword(":in".to_string()), + k_plain("in"), Vector(vec![ - Symbol("$".to_string()), + s_plain("$"), ]), - Keyword(":where".to_string()), + k_plain("where"), Vector(vec![ Vector(vec![ List(LinkedList::from_iter(vec![ - Symbol("list".to_string()), - Symbol("fulltext".to_string()), - Symbol("$".to_string()), + s_plain("list"), + s_plain("fulltext"), + s_plain("$"), Set(BTreeSet::from_iter(vec![ - Keyword(":page/url".to_string()), - Keyword(":page/title".to_string()), + k_ns("page", "url"), + k_ns("page", "title"), ])), - Symbol("string".to_string()), + s_plain("string"), ])), Vector(vec![ Vector(vec![ - Symbol("?page".to_string()), + s_plain("?page"), ]), ]), ]), Vector(vec![ List(LinkedList::from_iter(vec![ - Symbol("get-else".to_string()), - Symbol("$".to_string()), - Symbol("?page".to_string()), - Keyword(":page/url".to_string()), + s_plain("get-else"), + s_plain("$"), + s_plain("?page"), + k_ns("page", "url"), Text("".to_string()), ])), - Symbol("?url".to_string()), + s_plain("?url"), ]), Vector(vec![ List(LinkedList::from_iter(vec![ - Symbol("get-else".to_string()), - Symbol("$".to_string()), - Symbol("?page".to_string()), - Keyword(":page/title".to_string()), + s_plain("get-else"), + s_plain("$"), + s_plain("?page"), + k_ns("page", "title"), Text("".to_string()), ])), - Symbol("?title".to_string()), + s_plain("?title"), ]), ]), ]); @@ -635,65 +647,65 @@ fn test_query_pages_matching_string_2() { ]"; let reply = Vector(vec![ - Keyword(":find".to_string()), + k_plain("find"), Vector(vec![ - Symbol("?url".to_string()), - Symbol("?title".to_string()), - Symbol("?excerpt".to_string()), + s_plain("?url"), + s_plain("?title"), + s_plain("?excerpt"), ]), - Keyword(":in".to_string()), + k_plain("in"), Vector(vec![ - Symbol("$".to_string()), + s_plain("$"), ]), - Keyword(":where".to_string()), + k_plain("where"), Vector(vec![ Vector(vec![ List(LinkedList::from_iter(vec![ - Symbol("list".to_string()), - Symbol("fulltext".to_string()), - Symbol("$".to_string()), + s_plain("list"), + s_plain("fulltext"), + s_plain("$"), Set(BTreeSet::from_iter(vec![ - Keyword(":save/title".to_string()), - Keyword(":save/excerpt".to_string()), - Keyword(":save/content".to_string()), + k_ns("save", "title"), + k_ns("save", "excerpt"), + k_ns("save", "content"), ])), - Symbol("string".to_string()), + s_plain("string"), ])), Vector(vec![ Vector(vec![ - Symbol("?save".to_string()), + s_plain("?save"), ]), ]), ]), Vector(vec![ - Symbol("?save".to_string()), - Keyword(":save/page".to_string()), - Symbol("?page".to_string()), + s_plain("?save"), + k_ns("save", "page"), + s_plain("?page"), ]), Vector(vec![ - Symbol("?page".to_string()), - Keyword(":page/url".to_string()), - Symbol("?url".to_string()), + s_plain("?page"), + k_ns("page", "url"), + s_plain("?url"), ]), Vector(vec![ List(LinkedList::from_iter(vec![ - Symbol("get-else".to_string()), - Symbol("$".to_string()), - Symbol("?save".to_string()), - Keyword(":save/title".to_string()), + s_plain("get-else"), + s_plain("$"), + s_plain("?save"), + k_ns("save", "title"), Text("".to_string()), ])), - Symbol("?title".to_string()), + s_plain("?title"), ]), Vector(vec![ List(LinkedList::from_iter(vec![ - Symbol("get-else".to_string()), - Symbol("$".to_string()), - Symbol("?save".to_string()), - Keyword(":save/excerpt".to_string()), + s_plain("get-else"), + s_plain("$"), + s_plain("?save"), + k_ns("save", "excerpt"), Text("".to_string()), ])), - Symbol("?excerpt".to_string()), + s_plain("?excerpt"), ]), ]), ]); @@ -715,29 +727,29 @@ fn test_query_visited() { ]"; let reply = Vector(vec![ - Keyword(":find".to_string()), + k_plain("find"), Vector(vec![ - Symbol("?url".to_string()), - Symbol("?title".to_string()), + s_plain("?url"), + s_plain("?title"), List(LinkedList::from_iter(vec![ - Symbol("max".to_string()), - Symbol("?time".to_string()), + s_plain("max"), + s_plain("?time"), ])), ]), - Keyword(":in".to_string()), + k_plain("in"), List(LinkedList::from_iter(vec![ - Symbol("if".to_string()), - Symbol("since".to_string()), + s_plain("if"), + s_plain("since"), Vector(vec![ - Symbol("$".to_string()), - Symbol("?since".to_string()), + s_plain("$"), + s_plain("?since"), ]), Vector(vec![ - Symbol("$".to_string()), + s_plain("$"), ]), ])), - Keyword(":where".to_string()), - Symbol("where".to_string()), + k_plain("where"), + s_plain("where"), ]); assert_eq!(value(test).unwrap(), reply); } @@ -761,27 +773,27 @@ fn test_query_find_title() { ]"; let reply = Vector(vec![ - Keyword(":find".to_string()), - Symbol("?title".to_string()), - Symbol(".".to_string()), - Keyword(":in".to_string()), - Symbol("$".to_string()), - Symbol("?url".to_string()), - Keyword(":where".to_string()), + k_plain("find"), + s_plain("?title"), + s_plain("."), + k_plain("in"), + s_plain("$"), + s_plain("?url"), + k_plain("where"), Vector(vec![ - Symbol("?page".to_string()), - Keyword(":page/url".to_string()), - Symbol("?url".to_string()), + s_plain("?page"), + k_ns("page", "url"), + s_plain("?url"), ]), Vector(vec![ List(LinkedList::from_iter(vec![ - Symbol("get-else".to_string()), - Symbol("$".to_string()), - Symbol("?page".to_string()), - Keyword(":page/title".to_string()), + s_plain("get-else"), + s_plain("$"), + s_plain("?page"), + k_ns("page", "title"), Text("".to_string()), ])), - Symbol("?title".to_string()), + s_plain("?title"), ]), ]); assert_eq!(value(test).unwrap(), reply); diff --git a/src/ident.rs b/src/ident.rs index 8c687667..cc234275 100644 --- a/src/ident.rs +++ b/src/ident.rs @@ -10,7 +10,7 @@ extern crate edn; -use edn::keyword::Keyword; +use edn::symbols::Keyword; pub type EntId = u32; // TODO: u64? Not all DB values will be representable in a u32. diff --git a/src/lib.rs b/src/lib.rs index a5896432..2f07abbb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,11 +34,11 @@ pub fn get_connection() -> Connection { #[cfg(test)] mod tests { use super::*; - use edn::keyword::Keyword; + use edn::symbols::Keyword; #[test] fn can_import_edn() { - assert_eq!("foo", Keyword::new("foo").name); + assert_eq!("foo", Keyword::new("foo").0); } #[test]