Skip to content
This repository was archived by the owner on Sep 12, 2018. It is now read-only.

Commit c473511

Browse files
joewalkerrnewman
authored andcommitted
Implement a basic EDN parser. (#149) r=rnewman,bgrins,nalexander
The parser mostly works and has a decent test suite. It parses all the queries issued by the Tofino UAS, with some caveats. Known flaws: * No support for tagged elements, comments, discarded elements or "'". * Incomplete support for escaped characters in strings and the range of characters that are allowed in keywords and symbols. * Possible whitespace handling problems.
1 parent 3707428 commit c473511

8 files changed

Lines changed: 1058 additions & 1 deletion

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,5 @@ pom.xml.asc
4747
/release-node/datomish/
4848
/release-node/goog/
4949
/release-node/honeysql/
50+
51+
/edn/target/

edn/Cargo.toml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
11
[package]
22
name = "edn"
3-
version = "0.0.1"
3+
version = "0.1.0"
4+
authors = ["Joe Walker <jwalker@mozilla.com>"]
5+
6+
license = "Apache-2.0"
7+
repository = "https://github.com/mozilla/mentat"
8+
description = "EDN parser for Project Mentat"
9+
build = "build.rs"
10+
readme = "./README.md"
11+
12+
[dependencies]
13+
num = "0.1.35"
14+
ordered-float = "0.3.0"
15+
16+
[build-dependencies]
17+
peg = "0.4"

edn/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# barnardsstar
2+
An experimental EDN parser for Project Mentat.

edn/build.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2016 Mozilla
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4+
// this file except in compliance with the License. You may obtain a copy of the
5+
// License at http://www.apache.org/licenses/LICENSE-2.0
6+
// Unless required by applicable law or agreed to in writing, software distributed
7+
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
8+
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
9+
// specific language governing permissions and limitations under the License.
10+
11+
extern crate peg;
12+
13+
fn main() {
14+
peg::cargo_build("src/edn.rustpeg");
15+
}

edn/src/edn.rustpeg

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/* vim: set filetype=rust.rustpeg */
2+
3+
// Copyright 2016 Mozilla
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
6+
// this file except in compliance with the License. You may obtain a copy of the
7+
// License at http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software distributed
9+
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
10+
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
// specific language governing permissions and limitations under the License.
12+
13+
use std::collections::{BTreeSet, BTreeMap, LinkedList};
14+
use std::iter::FromIterator;
15+
use num::BigInt;
16+
use types::Value;
17+
use ordered_float::OrderedFloat;
18+
19+
// Goal: Be able to parse https://github.com/edn-format/edn
20+
// Also extensible to help parse http://docs.datomic.com/query.html
21+
22+
// Debugging hint: test using `cargo test --features peg/trace -- --nocapture`
23+
// to trace where the parser is failing
24+
25+
// TODO: Support tagged elements
26+
// TODO: Support comments
27+
// TODO: Support discard
28+
29+
#[export]
30+
nil -> Value = "nil" {
31+
Value::Nil
32+
}
33+
34+
#[export]
35+
boolean -> Value =
36+
"true" { Value::Boolean(true) } /
37+
"false" { Value::Boolean(false) }
38+
39+
digit = [0-9]
40+
sign = "-" / "+"
41+
42+
#[export]
43+
bigint -> Value = b:$( sign? digit+ ) "N" {
44+
Value::BigInteger(b.parse::<BigInt>().unwrap())
45+
}
46+
47+
#[export]
48+
integer -> Value = i:$( sign? digit+ ) {
49+
Value::Integer(i.parse::<i64>().unwrap())
50+
}
51+
52+
frac = sign? digit+ "." digit+
53+
exp = sign? digit+ ("e" / "E") sign? digit+
54+
frac_exp = sign? digit+ "." digit+ ("e" / "E") sign? digit+
55+
56+
// The order here is important - frac_exp must come before (exp / frac) or the
57+
// parser assumes exp or frac when the float is really a frac_exp and fails
58+
#[export]
59+
float -> Value = f:$( frac_exp / exp / frac ) {
60+
Value::Float(OrderedFloat(f.parse::<f64>().unwrap()))
61+
}
62+
63+
// TODO: \newline, \return, \space and \tab
64+
special_char = quote / tab
65+
quote = "\\\""
66+
tab = "\\tab"
67+
char = [^"] / special_char
68+
69+
#[export]
70+
text -> Value = "\"" t:$( char* ) "\"" {
71+
Value::Text(t.to_string())
72+
}
73+
74+
// TODO: Be more picky here
75+
symbol_char_initial = [a-z] / [A-Z] / [0-9] / [*!_?$%&=<>/.]
76+
symbol_char_subsequent = [a-z] / [A-Z] / [0-9] / [*!_?$%&=<>/.] / "-"
77+
78+
#[export]
79+
symbol -> Value = s:$( symbol_char_initial symbol_char_subsequent* ) {
80+
Value::Symbol(s.to_string())
81+
}
82+
83+
keyword_char_initial = ":"
84+
// TODO: More chars here?
85+
keyword_char_subsequent = [a-z] / [A-Z] / [0-9] / "/"
86+
87+
#[export]
88+
keyword -> Value = k:$( keyword_char_initial keyword_char_subsequent+ ) {
89+
Value::Keyword(k.to_string())
90+
}
91+
92+
#[export]
93+
list -> Value = "(" __ v:(__ value)* __ ")" {
94+
Value::List(LinkedList::from_iter(v))
95+
}
96+
97+
#[export]
98+
vector -> Value = "[" __ v:(__ value)* __ "]" {
99+
Value::Vector(v)
100+
}
101+
102+
#[export]
103+
set -> Value = "#{" __ v:(__ value)* __ "}" {
104+
Value::Set(BTreeSet::from_iter(v))
105+
}
106+
107+
pair -> (Value, Value) = k:(value) " " v:(value) ", "? {
108+
(k, v)
109+
}
110+
111+
#[export]
112+
map -> Value = "{" __ v:(pair)* __ "}" {
113+
Value::Map(BTreeMap::from_iter(v))
114+
}
115+
116+
// It's important that float comes before integer or the parser assumes that
117+
// floats are integers and fails to parse
118+
#[export]
119+
value -> Value
120+
= nil / boolean / float / bigint / integer / text /
121+
keyword / symbol /
122+
list / vector / map / set
123+
124+
whitespace = (" " / "\r" / "\n" / "\t")
125+
126+
__ = whitespace*

edn/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,14 @@
88
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
99
// specific language governing permissions and limitations under the License.
1010

11+
#![allow(dead_code)]
12+
13+
extern crate ordered_float;
14+
extern crate num;
15+
1116
pub mod keyword;
17+
pub mod types;
18+
19+
pub mod parse {
20+
include!(concat!(env!("OUT_DIR"), "/edn.rs"));
21+
}

edn/src/types.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright 2016 Mozilla
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4+
// this file except in compliance with the License. You may obtain a copy of the
5+
// License at http://www.apache.org/licenses/LICENSE-2.0
6+
// Unless required by applicable law or agreed to in writing, software distributed
7+
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
8+
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
9+
// specific language governing permissions and limitations under the License.
10+
11+
use std::collections::{BTreeSet, BTreeMap, LinkedList};
12+
use std::cmp::{Ordering, Ord, PartialOrd};
13+
use num::BigInt;
14+
use ordered_float::OrderedFloat;
15+
16+
/// Value represents one of the allowed values in an EDN string.
17+
#[derive(PartialEq, Eq, Hash, Debug)]
18+
pub enum Value {
19+
Nil,
20+
Boolean(bool),
21+
Integer(i64),
22+
BigInteger(BigInt),
23+
// https://users.rust-lang.org/t/hashmap-key-cant-be-float-number-type-why/7892
24+
Float(OrderedFloat<f64>),
25+
Text(String),
26+
Symbol(String),
27+
Keyword(String),
28+
Vector(Vec<Value>),
29+
List(LinkedList<Value>),
30+
// We're using BTree{Set, Map} rather than Hash{Set, Map} because the BTree variants
31+
// implement Hash (unlike the Hash variants which don't in order to preserve O(n) hashing
32+
// time which is hard given recurrsive data structures)
33+
// See https://internals.rust-lang.org/t/implementing-hash-for-hashset-hashmap/3817/1
34+
Set(BTreeSet<Value>),
35+
Map(BTreeMap<Value, Value>),
36+
}
37+
38+
use self::Value::*;
39+
40+
impl PartialOrd for Value {
41+
fn partial_cmp(&self, other: &Value) -> Option<Ordering> {
42+
Some(self.cmp(other))
43+
}
44+
}
45+
46+
// TODO: Check we follow the equality rules at the bottom of https://github.com/edn-format/edn
47+
impl Ord for Value {
48+
fn cmp(&self, other: &Value) -> Ordering {
49+
50+
let ord_order = to_ord(self).cmp(&to_ord(other));
51+
match *self {
52+
Nil => match *other { Nil => Ordering::Equal, _ => ord_order },
53+
Boolean(bs) => match *other { Boolean(bo) => bo.cmp(&bs), _ => ord_order },
54+
BigInteger(ref bs) => match *other { BigInteger(ref bo) => bo.cmp(&bs), _ => ord_order },
55+
Integer(is) => match *other { Integer(io) => io.cmp(&is), _ => ord_order },
56+
Float(ref fs) => match *other { Float(ref fo) => fo.cmp(&fs), _ => ord_order },
57+
Text(ref ts) => match *other { Text(ref to) => to.cmp(&ts), _ => ord_order },
58+
Symbol(ref ss) => match *other { Symbol(ref so) => so.cmp(&ss), _ => ord_order },
59+
Keyword(ref ks) => match *other { Keyword(ref ko) => ko.cmp(&ks), _ => ord_order },
60+
Vector(ref vs) => match *other { Vector(ref vo) => vo.cmp(&vs), _ => ord_order },
61+
List(ref ls) => match *other { List(ref lo) => lo.cmp(&ls), _ => ord_order },
62+
Set(ref ss) => match *other { Set(ref so) => so.cmp(&ss), _ => ord_order },
63+
Map(ref ms) => match *other { Map(ref mo) => mo.cmp(&ms), _ => ord_order },
64+
}
65+
}
66+
}
67+
68+
fn to_ord(value: &Value) -> i32 {
69+
match *value {
70+
Nil => 0,
71+
Boolean(_) => 1,
72+
Integer(_) => 2,
73+
BigInteger(_) => 3,
74+
Float(_) => 4,
75+
Text(_) => 5,
76+
Symbol(_) => 6,
77+
Keyword(_) => 7,
78+
Vector(_) => 8,
79+
List(_) => 9,
80+
Set(_) => 10,
81+
Map(_) => 12,
82+
}
83+
}
84+
85+
pub struct Pair(Value, Value);

0 commit comments

Comments
 (0)