From 476f04e27b5524a30d35c9f878526ed9eca4a0b7 Mon Sep 17 00:00:00 2001 From: Richard Newman Date: Wed, 21 Dec 2016 08:55:00 -0800 Subject: [PATCH] Implement a rudimentary Keyword struct and the beginnings of ident/entid. --- Cargo.toml | 3 ++ edn/Cargo.toml | 3 ++ edn/src/keyword.rs | 86 +++++++++++++++++++++++++++++++++++++++++ edn/src/lib.rs | 11 ++++++ query-parser/Cargo.toml | 7 ++++ src/ident.rs | 25 ++++++++++++ src/lib.rs | 9 +++++ 7 files changed, 144 insertions(+) create mode 100644 edn/Cargo.toml create mode 100644 edn/src/keyword.rs create mode 100644 edn/src/lib.rs create mode 100644 src/ident.rs diff --git a/Cargo.toml b/Cargo.toml index b897cbcc..2e978756 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,9 @@ authors = ["Richard Newman ", "Nicholas Alexander , +} + +/// 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 new file mode 100644 index 00000000..145c04c4 --- /dev/null +++ b/edn/src/lib.rs @@ -0,0 +1,11 @@ +// Copyright 2016 Mozilla +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use +// this file except in compliance with the License. You may obtain a copy of the +// License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +pub mod keyword; diff --git a/query-parser/Cargo.toml b/query-parser/Cargo.toml index e939fdd9..d2ad101c 100644 --- a/query-parser/Cargo.toml +++ b/query-parser/Cargo.toml @@ -1,3 +1,10 @@ [package] name = "mentat_query_parser" version = "0.0.1" + +[dependencies] +[dependencies.edn] + path = "../edn" + +[dependencies.mentat_query] + path = "../query" diff --git a/src/ident.rs b/src/ident.rs new file mode 100644 index 00000000..8c687667 --- /dev/null +++ b/src/ident.rs @@ -0,0 +1,25 @@ +// Copyright 2016 Mozilla +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use +// this file except in compliance with the License. You may obtain a copy of the +// License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +extern crate edn; + +use edn::keyword::Keyword; + +pub type EntId = u32; // TODO: u64? Not all DB values will be representable in a u32. + +/// The ability to transform entity identifiers (entids) into keyword names (idents). +pub trait ToIdent { + fn ident(&self, entid: EntId) -> Option; +} + +/// The ability to transform idents into the corresponding entid. +pub trait ToEntId { + fn entid(&self, ident: &Keyword) -> Option; +} diff --git a/src/lib.rs b/src/lib.rs index ae434424..52e0cf94 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,11 +8,14 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. +extern crate edn; extern crate mentat_query_parser; extern crate rusqlite; use rusqlite::Connection; +pub mod ident; + pub fn get_name() -> String { return String::from("mentat"); } @@ -30,6 +33,12 @@ pub fn get_connection() -> Connection { #[cfg(test)] mod tests { use super::*; + use edn::keyword::Keyword; + + #[test] + fn can_import_edn() { + assert_eq!("foo", Keyword::new("foo").name); + } #[test] fn can_import_parser() {