Read EDN keywords and symbols as rich types. Fixes #154. r=nalexander

This commit is contained in:
Richard Newman 2017-01-11 13:51:34 -08:00
parent c4735119c4
commit a152e60040
8 changed files with 423 additions and 294 deletions

View file

@ -12,9 +12,11 @@
use std::collections::{BTreeSet, BTreeMap, LinkedList}; use std::collections::{BTreeSet, BTreeMap, LinkedList};
use std::iter::FromIterator; use std::iter::FromIterator;
use num::BigInt; use num::BigInt;
use types::Value;
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use types;
use types::Value;
// Goal: Be able to parse https://github.com/edn-format/edn // Goal: Be able to parse https://github.com/edn-format/edn
// Also extensible to help parse http://docs.datomic.com/query.html // Also extensible to help parse http://docs.datomic.com/query.html
@ -71,22 +73,35 @@ text -> Value = "\"" t:$( char* ) "\"" {
Value::Text(t.to_string()) Value::Text(t.to_string())
} }
namespace_divider = "."
namespace_separator = "/"
// TODO: Be more picky here // TODO: Be more picky here
symbol_char_initial = [a-z] / [A-Z] / [0-9] / [*!_?$%&=<>/.] symbol_char_initial = [a-z] / [A-Z] / [0-9] / [*!_?$%&=<>]
symbol_char_subsequent = [a-z] / [A-Z] / [0-9] / [*!_?$%&=<>/.] / "-" symbol_char_subsequent = [a-z] / [A-Z] / [0-9] / [-*!_?$%&=<>]
symbol_namespace = symbol_char_initial+ (namespace_divider symbol_char_subsequent+)*
symbol_name = ( symbol_char_initial+ / "." ) ( symbol_char_subsequent* / "." )
keyword_prefix = ":"
// TODO: More chars here?
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] #[export]
symbol -> Value = s:$( symbol_char_initial symbol_char_subsequent* ) { symbol -> Value
Value::Symbol(s.to_string()) = ns:( sns:$(symbol_namespace) namespace_separator { sns })? n:$(symbol_name) {
types::to_symbol(ns, n)
} }
keyword_char_initial = ":"
// TODO: More chars here?
keyword_char_subsequent = [a-z] / [A-Z] / [0-9] / "/"
#[export] #[export]
keyword -> Value = k:$( keyword_char_initial keyword_char_subsequent+ ) { keyword -> Value
Value::Keyword(k.to_string()) = keyword_prefix ns:( kns:$(keyword_namespace) namespace_separator { kns })? n:$(keyword_name) {
types::to_keyword(ns, n)
} }
#[export] #[export]

View file

@ -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<String>,
}
/// 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);
}
}

View file

@ -13,7 +13,7 @@
extern crate ordered_float; extern crate ordered_float;
extern crate num; extern crate num;
pub mod keyword; pub mod symbols;
pub mod types; pub mod types;
pub mod parse { pub mod parse {

164
edn/src/symbols.rs Normal file
View file

@ -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);
}
}

View file

@ -10,6 +10,8 @@
use std::collections::{BTreeSet, BTreeMap, LinkedList}; use std::collections::{BTreeSet, BTreeMap, LinkedList};
use std::cmp::{Ordering, Ord, PartialOrd}; use std::cmp::{Ordering, Ord, PartialOrd};
use symbols;
use num::BigInt; use num::BigInt;
use ordered_float::OrderedFloat; 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 // https://users.rust-lang.org/t/hashmap-key-cant-be-float-number-type-why/7892
Float(OrderedFloat<f64>), Float(OrderedFloat<f64>),
Text(String), Text(String),
Symbol(String), PlainSymbol(symbols::PlainSymbol),
Keyword(String), NamespacedSymbol(symbols::NamespacedSymbol),
Keyword(symbols::Keyword),
NamespacedKeyword(symbols::NamespacedKeyword),
Vector(Vec<Value>), Vector(Vec<Value>),
List(LinkedList<Value>), List(LinkedList<Value>),
// We're using BTree{Set, Map} rather than Hash{Set, Map} because the BTree variants // 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 }, Integer(is) => match *other { Integer(io) => io.cmp(&is), _ => ord_order },
Float(ref fs) => match *other { Float(ref fo) => fo.cmp(&fs), _ => 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 }, 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 }, 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 }, 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 }, 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 }, 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, BigInteger(_) => 3,
Float(_) => 4, Float(_) => 4,
Text(_) => 5, Text(_) => 5,
Symbol(_) => 6, PlainSymbol(_) => 6,
Keyword(_) => 7, NamespacedSymbol(_) => 7,
Vector(_) => 8, Keyword(_) => 8,
List(_) => 9, NamespacedKeyword(_) => 9,
Set(_) => 10, Vector(_) => 10,
Map(_) => 12, List(_) => 11,
Set(_) => 12,
Map(_) => 13,
} }
} }
pub struct Pair(Value, Value); 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));
}

View file

@ -17,9 +17,25 @@ use std::iter::FromIterator;
use num::bigint::ToBigInt; use num::bigint::ToBigInt;
use num::traits::{Zero, One}; use num::traits::{Zero, One};
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use edn::symbols;
use edn::types::Value;
use edn::types::Value::*; use edn::types::Value::*;
use edn::parse::*; 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] #[test]
fn test_nil() { fn test_nil() {
assert_eq!(nil("nil").unwrap(), Nil); assert_eq!(nil("nil").unwrap(), Nil);
@ -80,18 +96,19 @@ fn test_text() {
#[test] #[test]
fn test_symbol() { fn test_symbol() {
assert_eq!(symbol("$").unwrap(), Symbol("$".to_string())); assert_eq!(symbol("$").unwrap(), s_plain("$"));
assert_eq!(symbol(".").unwrap(), Symbol(".".to_string())); assert_eq!(symbol(".").unwrap(), s_plain("."));
assert_eq!(symbol("r_r").unwrap(), Symbol("r_r".to_string())); //assert_eq!(symbol("r_r").unwrap(), s_plain("r_r"));
assert_eq!(symbol("$symbol").unwrap(), Symbol("$symbol".to_string())); //assert_eq!(symbol("$symbol").unwrap(), s_plain("$symbol"));
assert_eq!(symbol("hello").unwrap(), Symbol("hello".to_string())); //assert_eq!(symbol("hello").unwrap(), s_plain("hello"));
} }
#[test] #[test]
fn test_keyword() { fn test_keyword() {
assert_eq!(keyword(":hello/world").unwrap(), Keyword(":hello/world".to_string())); assert_eq!(keyword(":hello/world").unwrap(), k_ns("hello", "world"));
assert_eq!(keyword(":symbol").unwrap(), Keyword(":symbol".to_string()));
assert_eq!(keyword(":hello").unwrap(), Keyword(":hello".to_string())); assert_eq!(keyword(":symbol").unwrap(), k_plain("symbol"));
assert_eq!(keyword(":hello").unwrap(), k_plain("hello"));
} }
#[test] #[test]
@ -103,10 +120,10 @@ fn test_value() {
assert_eq!(value("true").unwrap(), Boolean(true)); assert_eq!(value("true").unwrap(), Boolean(true));
assert_eq!(value("1").unwrap(), Integer(1i64)); assert_eq!(value("1").unwrap(), Integer(1i64));
assert_eq!(value("\"hello world\"").unwrap(), Text("hello world".to_string())); assert_eq!(value("\"hello world\"").unwrap(), Text("hello world".to_string()));
assert_eq!(value("$").unwrap(), Symbol("$".to_string())); assert_eq!(value("$").unwrap(), s_plain("$"));
assert_eq!(value(".").unwrap(), Symbol(".".to_string())); assert_eq!(value(".").unwrap(), s_plain("."));
assert_eq!(value("$symbol").unwrap(), Symbol("$symbol".to_string())); assert_eq!(value("$symbol").unwrap(), s_plain("$symbol"));
assert_eq!(value(":hello").unwrap(), Keyword(":hello".to_string())); assert_eq!(value(":hello").unwrap(), k_plain("hello"));
assert_eq!(value("[1]").unwrap(), Vector(vec![Integer(1)])); assert_eq!(value("[1]").unwrap(), Vector(vec![Integer(1)]));
assert_eq!(value("111.222").unwrap(), Float(OrderedFloat(111.222f64))); assert_eq!(value("111.222").unwrap(), Float(OrderedFloat(111.222f64)));
assert_eq!(value("85070591730234615847396907784232501249N").unwrap(), BigInteger(bigger)); assert_eq!(value("85070591730234615847396907784232501249N").unwrap(), BigInteger(bigger));
@ -329,23 +346,18 @@ fn test_map() {
assert_eq!(map(test).unwrap(), value); 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 test = "{:a 1, $b {:b/a nil, :b/b #{nil 5}}, c [1 2], d (3 4)}";
let value = Map(BTreeMap::from_iter(vec![ let value = Map(
(Keyword(":a".to_string()), Integer(1)), BTreeMap::from_iter(
(Symbol("$b".to_string()), Map(BTreeMap::from_iter(vec![ vec![
(Keyword(":b/a".to_string()), Nil), (Keyword(symbols::Keyword::new("a")), Integer(1)),
(Keyword(":b/b".to_string()), Set(BTreeSet::from_iter(vec![ (s_plain("$b"), Map(BTreeMap::from_iter(vec![
Nil, (k_ns("b", "a"), Nil),
Integer(5),
]))), (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_eq!(map(test).unwrap(), value);
@ -371,33 +383,33 @@ fn test_query_active_sessions() {
]"; ]";
let reply = Vector(vec![ let reply = Vector(vec![
Keyword(":find".to_string()), k_plain("find"),
Symbol("?id".to_string()), s_plain("?id"),
Symbol("?reason".to_string()), s_plain("?reason"),
Symbol("?ts".to_string()), s_plain("?ts"),
Keyword(":in".to_string()), k_plain("in"),
Symbol("$".to_string()), s_plain("$"),
Keyword(":where".to_string()), k_plain("where"),
Vector(vec![ Vector(vec![
Symbol("?id".to_string()), s_plain("?id"),
Keyword(":session/startReason".to_string()), k_ns("session", "startReason"),
Symbol("?reason".to_string()), s_plain("?reason"),
Symbol("?tx".to_string()), s_plain("?tx"),
]), ]),
Vector(vec![ Vector(vec![
Symbol("?tx".to_string()), s_plain("?tx"),
Keyword(":db/txInstant".to_string()), k_ns("db", "txInstant"),
Symbol("?ts".to_string()), s_plain("?ts"),
]), ]),
List(LinkedList::from_iter(vec![ List(LinkedList::from_iter(vec![
Symbol("not-join".to_string()), s_plain("not-join"),
Vector(vec![ Vector(vec![
Symbol("?id".to_string()), s_plain("?id"),
]), ]),
Vector(vec![ Vector(vec![
Symbol("?id".to_string()), s_plain("?id"),
Keyword(":session/endReason".to_string()), k_ns("session", "endReason"),
Symbol("_".to_string()), s_plain("_"),
]), ]),
])), ])),
]); ]);
@ -415,23 +427,23 @@ fn test_query_ended_sessions() {
]"; ]";
let reply = Vector(vec![ let reply = Vector(vec![
Keyword(":find".to_string()), k_plain("find"),
Symbol("?id".to_string()), s_plain("?id"),
Symbol("?endReason".to_string()), s_plain("?endReason"),
Symbol("?ts".to_string()), s_plain("?ts"),
Keyword(":in".to_string()), k_plain("in"),
Symbol("$".to_string()), s_plain("$"),
Keyword(":where".to_string()), k_plain("where"),
Vector(vec![ Vector(vec![
Symbol("?id".to_string()), s_plain("?id"),
Keyword(":session/endReason".to_string()), k_ns("session", "endReason"),
Symbol("?endReason".to_string()), s_plain("?endReason"),
Symbol("?tx".to_string()), s_plain("?tx"),
]), ]),
Vector(vec![ Vector(vec![
Symbol("?tx".to_string()), s_plain("?tx"),
Keyword(":db/txInstant".to_string()), k_ns("db", "txInstant"),
Symbol("?ts".to_string()), s_plain("?ts"),
]), ]),
]); ]);
assert_eq!(value(test).unwrap(), reply); assert_eq!(value(test).unwrap(), reply);
@ -447,26 +459,26 @@ fn test_query_starred_pages() {
]"; ]";
let reply = Vector(vec![ let reply = Vector(vec![
Keyword(":find".to_string()), k_plain("find"),
Vector(vec![ Vector(vec![
Symbol("?url".to_string()), s_plain("?url"),
Symbol("?title".to_string()), s_plain("?title"),
Symbol("?starredOn".to_string()), s_plain("?starredOn"),
]), ]),
Keyword(":in".to_string()), k_plain("in"),
List(LinkedList::from_iter(vec![ List(LinkedList::from_iter(vec![
Symbol("if".to_string()), s_plain("if"),
Symbol("since".to_string()), s_plain("since"),
Vector(vec![ Vector(vec![
Symbol("$".to_string()), s_plain("$"),
Symbol("?since".to_string()), s_plain("?since"),
]), ]),
Vector(vec![ Vector(vec![
Symbol("$".to_string()), s_plain("$"),
]), ]),
])), ])),
Keyword(":where".to_string()), k_plain("where"),
Symbol("where".to_string()), s_plain("where"),
]); ]);
assert_eq!(value(test).unwrap(), reply); assert_eq!(value(test).unwrap(), reply);
} }
@ -485,48 +497,48 @@ fn test_query_saved_pages() {
]"; ]";
let reply = Vector(vec![ let reply = Vector(vec![
Keyword(":find".to_string()), k_plain("find"),
Symbol("?page".to_string()), s_plain("?page"),
Symbol("?url".to_string()), s_plain("?url"),
Symbol("?title".to_string()), s_plain("?title"),
Symbol("?excerpt".to_string()), s_plain("?excerpt"),
Keyword(":in".to_string()), k_plain("in"),
Symbol("$".to_string()), s_plain("$"),
Keyword(":where".to_string()), k_plain("where"),
Vector(vec![ Vector(vec![
Symbol("?save".to_string()), s_plain("?save"),
Keyword(":save/page".to_string()), k_ns("save", "page"),
Symbol("?page".to_string()), s_plain("?page"),
]), ]),
Vector(vec![ Vector(vec![
Symbol("?save".to_string()), s_plain("?save"),
Keyword(":save/savedAt".to_string()), k_ns("save", "savedAt"),
Symbol("?instant".to_string()), s_plain("?instant"),
]), ]),
Vector(vec![ Vector(vec![
Symbol("?page".to_string()), s_plain("?page"),
Keyword(":page/url".to_string()), k_ns("page", "url"),
Symbol("?url".to_string()), s_plain("?url"),
]), ]),
Vector(vec![ Vector(vec![
List(LinkedList::from_iter(vec![ List(LinkedList::from_iter(vec![
Symbol("get-else".to_string()), s_plain("get-else"),
Symbol("$".to_string()), s_plain("$"),
Symbol("?save".to_string()), s_plain("?save"),
Keyword(":save/title".to_string()), k_ns("save", "title"),
Text("".to_string()), Text("".to_string()),
])), ])),
Symbol("?title".to_string()), s_plain("?title"),
]), ]),
Vector(vec![ Vector(vec![
List(LinkedList::from_iter(vec![ List(LinkedList::from_iter(vec![
Symbol("get-else".to_string()), s_plain("get-else"),
Symbol("$".to_string()), s_plain("$"),
Symbol("?save".to_string()), s_plain("?save"),
Keyword(":save/excerpt".to_string()), k_ns("save", "excerpt"),
Text("".to_string()), Text("".to_string()),
])), ])),
Symbol("?excerpt".to_string()), s_plain("?excerpt"),
]), ]),
]); ]);
assert_eq!(value(test).unwrap(), reply); assert_eq!(value(test).unwrap(), reply);
@ -555,53 +567,53 @@ fn test_query_pages_matching_string_1() {
]"; ]";
let reply = Vector(vec![ let reply = Vector(vec![
Keyword(":find".to_string()), k_plain("find"),
Vector(vec![ Vector(vec![
Symbol("?url".to_string()), s_plain("?url"),
Symbol("?title".to_string()), s_plain("?title"),
]), ]),
Keyword(":in".to_string()), k_plain("in"),
Vector(vec![ Vector(vec![
Symbol("$".to_string()), s_plain("$"),
]), ]),
Keyword(":where".to_string()), k_plain("where"),
Vector(vec![ Vector(vec![
Vector(vec![ Vector(vec![
List(LinkedList::from_iter(vec![ List(LinkedList::from_iter(vec![
Symbol("list".to_string()), s_plain("list"),
Symbol("fulltext".to_string()), s_plain("fulltext"),
Symbol("$".to_string()), s_plain("$"),
Set(BTreeSet::from_iter(vec![ Set(BTreeSet::from_iter(vec![
Keyword(":page/url".to_string()), k_ns("page", "url"),
Keyword(":page/title".to_string()), k_ns("page", "title"),
])), ])),
Symbol("string".to_string()), s_plain("string"),
])), ])),
Vector(vec![ Vector(vec![
Vector(vec![ Vector(vec![
Symbol("?page".to_string()), s_plain("?page"),
]), ]),
]), ]),
]), ]),
Vector(vec![ Vector(vec![
List(LinkedList::from_iter(vec![ List(LinkedList::from_iter(vec![
Symbol("get-else".to_string()), s_plain("get-else"),
Symbol("$".to_string()), s_plain("$"),
Symbol("?page".to_string()), s_plain("?page"),
Keyword(":page/url".to_string()), k_ns("page", "url"),
Text("".to_string()), Text("".to_string()),
])), ])),
Symbol("?url".to_string()), s_plain("?url"),
]), ]),
Vector(vec![ Vector(vec![
List(LinkedList::from_iter(vec![ List(LinkedList::from_iter(vec![
Symbol("get-else".to_string()), s_plain("get-else"),
Symbol("$".to_string()), s_plain("$"),
Symbol("?page".to_string()), s_plain("?page"),
Keyword(":page/title".to_string()), k_ns("page", "title"),
Text("".to_string()), 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![ let reply = Vector(vec![
Keyword(":find".to_string()), k_plain("find"),
Vector(vec![ Vector(vec![
Symbol("?url".to_string()), s_plain("?url"),
Symbol("?title".to_string()), s_plain("?title"),
Symbol("?excerpt".to_string()), s_plain("?excerpt"),
]), ]),
Keyword(":in".to_string()), k_plain("in"),
Vector(vec![ Vector(vec![
Symbol("$".to_string()), s_plain("$"),
]), ]),
Keyword(":where".to_string()), k_plain("where"),
Vector(vec![ Vector(vec![
Vector(vec![ Vector(vec![
List(LinkedList::from_iter(vec![ List(LinkedList::from_iter(vec![
Symbol("list".to_string()), s_plain("list"),
Symbol("fulltext".to_string()), s_plain("fulltext"),
Symbol("$".to_string()), s_plain("$"),
Set(BTreeSet::from_iter(vec![ Set(BTreeSet::from_iter(vec![
Keyword(":save/title".to_string()), k_ns("save", "title"),
Keyword(":save/excerpt".to_string()), k_ns("save", "excerpt"),
Keyword(":save/content".to_string()), k_ns("save", "content"),
])), ])),
Symbol("string".to_string()), s_plain("string"),
])), ])),
Vector(vec![ Vector(vec![
Vector(vec![ Vector(vec![
Symbol("?save".to_string()), s_plain("?save"),
]), ]),
]), ]),
]), ]),
Vector(vec![ Vector(vec![
Symbol("?save".to_string()), s_plain("?save"),
Keyword(":save/page".to_string()), k_ns("save", "page"),
Symbol("?page".to_string()), s_plain("?page"),
]), ]),
Vector(vec![ Vector(vec![
Symbol("?page".to_string()), s_plain("?page"),
Keyword(":page/url".to_string()), k_ns("page", "url"),
Symbol("?url".to_string()), s_plain("?url"),
]), ]),
Vector(vec![ Vector(vec![
List(LinkedList::from_iter(vec![ List(LinkedList::from_iter(vec![
Symbol("get-else".to_string()), s_plain("get-else"),
Symbol("$".to_string()), s_plain("$"),
Symbol("?save".to_string()), s_plain("?save"),
Keyword(":save/title".to_string()), k_ns("save", "title"),
Text("".to_string()), Text("".to_string()),
])), ])),
Symbol("?title".to_string()), s_plain("?title"),
]), ]),
Vector(vec![ Vector(vec![
List(LinkedList::from_iter(vec![ List(LinkedList::from_iter(vec![
Symbol("get-else".to_string()), s_plain("get-else"),
Symbol("$".to_string()), s_plain("$"),
Symbol("?save".to_string()), s_plain("?save"),
Keyword(":save/excerpt".to_string()), k_ns("save", "excerpt"),
Text("".to_string()), Text("".to_string()),
])), ])),
Symbol("?excerpt".to_string()), s_plain("?excerpt"),
]), ]),
]), ]),
]); ]);
@ -715,29 +727,29 @@ fn test_query_visited() {
]"; ]";
let reply = Vector(vec![ let reply = Vector(vec![
Keyword(":find".to_string()), k_plain("find"),
Vector(vec![ Vector(vec![
Symbol("?url".to_string()), s_plain("?url"),
Symbol("?title".to_string()), s_plain("?title"),
List(LinkedList::from_iter(vec![ List(LinkedList::from_iter(vec![
Symbol("max".to_string()), s_plain("max"),
Symbol("?time".to_string()), s_plain("?time"),
])), ])),
]), ]),
Keyword(":in".to_string()), k_plain("in"),
List(LinkedList::from_iter(vec![ List(LinkedList::from_iter(vec![
Symbol("if".to_string()), s_plain("if"),
Symbol("since".to_string()), s_plain("since"),
Vector(vec![ Vector(vec![
Symbol("$".to_string()), s_plain("$"),
Symbol("?since".to_string()), s_plain("?since"),
]), ]),
Vector(vec![ Vector(vec![
Symbol("$".to_string()), s_plain("$"),
]), ]),
])), ])),
Keyword(":where".to_string()), k_plain("where"),
Symbol("where".to_string()), s_plain("where"),
]); ]);
assert_eq!(value(test).unwrap(), reply); assert_eq!(value(test).unwrap(), reply);
} }
@ -761,27 +773,27 @@ fn test_query_find_title() {
]"; ]";
let reply = Vector(vec![ let reply = Vector(vec![
Keyword(":find".to_string()), k_plain("find"),
Symbol("?title".to_string()), s_plain("?title"),
Symbol(".".to_string()), s_plain("."),
Keyword(":in".to_string()), k_plain("in"),
Symbol("$".to_string()), s_plain("$"),
Symbol("?url".to_string()), s_plain("?url"),
Keyword(":where".to_string()), k_plain("where"),
Vector(vec![ Vector(vec![
Symbol("?page".to_string()), s_plain("?page"),
Keyword(":page/url".to_string()), k_ns("page", "url"),
Symbol("?url".to_string()), s_plain("?url"),
]), ]),
Vector(vec![ Vector(vec![
List(LinkedList::from_iter(vec![ List(LinkedList::from_iter(vec![
Symbol("get-else".to_string()), s_plain("get-else"),
Symbol("$".to_string()), s_plain("$"),
Symbol("?page".to_string()), s_plain("?page"),
Keyword(":page/title".to_string()), k_ns("page", "title"),
Text("".to_string()), Text("".to_string()),
])), ])),
Symbol("?title".to_string()), s_plain("?title"),
]), ]),
]); ]);
assert_eq!(value(test).unwrap(), reply); assert_eq!(value(test).unwrap(), reply);

View file

@ -10,7 +10,7 @@
extern crate edn; 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. pub type EntId = u32; // TODO: u64? Not all DB values will be representable in a u32.

View file

@ -34,11 +34,11 @@ pub fn get_connection() -> Connection {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use edn::keyword::Keyword; use edn::symbols::Keyword;
#[test] #[test]
fn can_import_edn() { fn can_import_edn() {
assert_eq!("foo", Keyword::new("foo").name); assert_eq!("foo", Keyword::new("foo").0);
} }
#[test] #[test]