Read EDN keywords and symbols as rich types. Fixes #154. r=nalexander
This commit is contained in:
parent
c4735119c4
commit
a152e60040
8 changed files with 423 additions and 294 deletions
|
@ -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)* __ ")" {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
extern crate ordered_float;
|
||||
extern crate num;
|
||||
|
||||
pub mod keyword;
|
||||
pub mod symbols;
|
||||
pub mod types;
|
||||
|
||||
pub mod parse {
|
||||
|
|
164
edn/src/symbols.rs
Normal file
164
edn/src/symbols.rs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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<f64>),
|
||||
Text(String),
|
||||
Symbol(String),
|
||||
Keyword(String),
|
||||
PlainSymbol(symbols::PlainSymbol),
|
||||
NamespacedSymbol(symbols::NamespacedSymbol),
|
||||
Keyword(symbols::Keyword),
|
||||
NamespacedKeyword(symbols::NamespacedKeyword),
|
||||
Vector(Vec<Value>),
|
||||
List(LinkedList<Value>),
|
||||
// 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));
|
||||
}
|
||||
|
|
|
@ -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,23 +346,18 @@ 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),
|
||||
]))),
|
||||
]))),
|
||||
(Symbol("c".to_string()), Vector(vec![
|
||||
Integer(1),
|
||||
Integer(2),
|
||||
])),
|
||||
(Symbol("d".to_string()), List(LinkedList::from_iter(vec![
|
||||
Integer(3),
|
||||
Integer(4),
|
||||
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),]))),
|
||||
]))),
|
||||
(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);
|
||||
|
||||
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
Loading…
Reference in a new issue