Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions examples/get-nth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,39 @@ fn maybe_forty_two<'a>(edn: &'a Edn<'a>) -> Option<&'a Edn<'a>> {
.nth(2)
}

fn namespace_get_contains() {
// (def edn-data (edn/read-string "#:thingy {:foo \"bar\" :baz/bar \"qux\" 42 24}"))
let edn_data = edn::read_string(r#"#:thingy {:foo "bar" :baz/bar "qux" 42 24}"#).unwrap();

// (get edn-data 42) -> 24
assert_eq!(edn_data.get(&Edn::Int(42)), Some(&Edn::Int(24)));
// (get edn-data :foo) -> nil
assert_eq!(edn_data.get(&Edn::Key("foo")), None);
// (get edn-data :thingy/foo) -> "bar"
assert_eq!(edn_data.get(&Edn::Key("thingy/foo")), Some(&Edn::Str("bar")));
// (get edn-data :baz/bar) -> "qux"
assert_eq!(edn_data.get(&Edn::Key("baz/bar")), Some(&Edn::Str("qux")));

// (contains? edn-data 42) -> true
assert!(edn_data.contains(&Edn::Int(42)));
// (contains? edn-data "42") -> false
assert!(!edn_data.contains(&Edn::Str("42")));
// (contains? edn-data :foo) -> false
assert!(!edn_data.contains(&Edn::Key("foo")));
// (contains? edn-data :thingy/foo) -> true
assert!(edn_data.contains(&Edn::Key("thingy/foo")));
// (contains? edn-data :baz/bar) -> true
assert!(edn_data.contains(&Edn::Key("baz/bar")));
// (contains? edn-data :bar/baz) -> false
assert!(!edn_data.contains(&Edn::Key("bar/baz")));
}

fn main() {
let e = edn::read_string("{:foo {猫 {{:foo :bar} [1 2 42 3]}}}").unwrap();
let edn = maybe_forty_two(&e).unwrap();
assert_eq!(edn, &Edn::Int(42));

namespace_get_contains();
}

#[test]
Expand Down
60 changes: 58 additions & 2 deletions src/edn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,47 @@ pub fn read(edn: &str) -> Result<(Edn<'_>, &str), error::Error> {
Ok((r.0, r.1))
}

fn get_tag<'a>(tag: &'a str, key: &'a str) -> Option<&'a str> {
// Break out early if there's no namespaces
if !key.contains('/') {
return None;
}

// ignore the leading ':'
if !tag.starts_with(':') {
return None;
}
let tag = tag.get(1..)?;
Some(tag)
}

fn check_key<'a>(tag: &'a str, key: &'a str) -> &'a str {
// check if the Key starts with the saved Tag
if key.starts_with(tag) {
let (_, key) = key.rsplit_once(tag).expect("Tag must exist, because it starts with it.");

// ensure there's a '/' and strip it
if let Some(k) = key.strip_prefix('/') {
return k;
}
}
key
}

impl Edn<'_> {
pub fn get(&self, e: &Self) -> Option<&Self> {
if let Edn::Map(m) = self {
if let Some(l) = m.get(e) {
return Some(l);
return m.get(e);
} else if let Edn::Tagged(tag, m) = self {
if let Edn::Key(key) = e {
let tag = get_tag(tag, key)?;
let key = check_key(tag, key);

return m.get(&Edn::Key(key));
}

// Cover cases where it's not a keyword
return m.get(e);
}
None
}
Expand All @@ -92,6 +127,27 @@ impl Edn<'_> {

vec.get(i)
}

pub fn contains(&self, e: &Self) -> bool {
match self {
Edn::Map(m) => m.contains_key(e),
Edn::Tagged(tag, m) => {
if let Edn::Key(key) = e {
let Some(tag) = get_tag(tag, key) else { return false };
let key = check_key(tag, key);

return m.contains(&Edn::Key(key));
}

// Cover cases where it's not a keyword
m.contains(e)
}
Edn::Vector(v) => v.contains(e),
Edn::Set(s) => s.contains(e),
Edn::List(l) => l.contains(e),
_ => false,
}
}
}

pub(crate) const fn char_to_edn(c: char) -> Option<&'static str> {
Expand Down
7 changes: 4 additions & 3 deletions src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,11 @@ impl Walker {
let starting_ptr = self.ptr;

loop {
if let Some(c) = self.nibble_next(slice) {
if c == ' ' {
return Ok(&slice[starting_ptr..self.ptr - 1]);
if let Some(c) = self.peek_next(slice) {
if c.is_whitespace() || DELIMITERS.contains(&c) {
return Ok(&slice[starting_ptr..self.ptr]);
}
let _ = self.nibble_next(slice);
} else {
return Err(Error {
code: Code::UnexpectedEOF,
Expand Down
97 changes: 97 additions & 0 deletions tests/navigation.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
extern crate alloc;

use alloc::collections::BTreeMap;

use clojure_reader::edn::{self, Edn};

#[test]
Expand All @@ -23,3 +27,96 @@ fn nth() {
assert_eq!(e.nth(3), Some(&Edn::Int(42)));
assert_eq!(e.nth(42), None);
}

#[test]
fn default_map_namespace_syntax() {
// see https://github.com/Grinkers/clojure-reader/issues/2
let variations = [
"{:thingy #:foo{:bar \"baz\"} :more \"stuff\"}",
"{:thingy #:foo {:bar \"baz\"} :more \"stuff\"}",
"{:more \"stuff\" :thingy #:foo{:bar \"baz\"}}",
"{:more \"stuff\" :thingy # :foo{:bar \"baz\"}}",
];
for v in variations {
let cfg = edn::read_string(&v).unwrap();

let Edn::Map(cfg) = cfg else { panic!() };
assert_eq!(
cfg.get(&Edn::Key("thingy")),
Some(&Edn::Tagged(
":foo",
Box::new(Edn::Map(BTreeMap::from([(Edn::Key("bar"), Edn::Str("baz"))])))
))
);
assert_eq!(cfg.get(&Edn::Key("more")), Some(&Edn::Str("stuff")));
}

// without keyword `:` symbol.
// the tag is parsed/preserved, but we don't support custom readers
let variations = [
"{:thingy #foo{:bar \"baz\"} :more \"stuff\"}",
"{:thingy #foo {:bar \"baz\"} :more \"stuff\"}",
"{:more \"stuff\" :thingy #foo{:bar \"baz\"}}",
"{:more \"stuff\" :thingy # foo{:bar \"baz\"}}",
];
for v in variations {
let cfg = edn::read_string(&v).unwrap();

let Edn::Map(cfg) = cfg else { panic!() };
assert_eq!(
cfg.get(&Edn::Key("thingy")),
Some(&Edn::Tagged(
"foo",
Box::new(Edn::Map(BTreeMap::from([(Edn::Key("bar"), Edn::Str("baz"))])))
))
);
assert_eq!(cfg.get(&Edn::Key("more")), Some(&Edn::Str("stuff")));
}
}

#[test]
fn namespace_syntax_edge_cases() {
let edn_data = edn::read_string(r#"#:thingy {:f#猫o "bar" :baz/bar "qux" 42 24}"#).unwrap();

assert_eq!(edn_data.get(&Edn::Key("thingy/f#猫o")), Some(&Edn::Str("bar")));
assert_eq!(edn_data.get(&Edn::Key("baz/bar")), Some(&Edn::Str("qux")));
assert_eq!(edn_data.get(&Edn::Key("foo")), None);
assert_eq!(edn_data.get(&Edn::Key("baz")), None);
assert_eq!(edn_data.get(&Edn::Key(":baz/bar")), None);
assert_eq!(edn_data.get(&Edn::Key("thingy/")), None);
assert_eq!(edn_data.get(&Edn::Key("thingy")), None);
assert_eq!(edn_data.get(&Edn::Key("thingything")), None);

let edn_data = edn::read_string(r#"#thingy {:f#猫o "bar" :baz/bar "qux" 42 24}"#).unwrap();
assert_eq!(edn_data.get(&Edn::Key("thingy/f#猫o")), None);
assert_eq!(edn_data.get(&Edn::Key("baz/bar")), None);
}

#[test]
fn get_contains() {
let edn_data = edn::read_string(r#"{:f#猫o "bar" :baz/bar "qux" 42 24}"#).unwrap();
assert_eq!(edn_data.get(&Edn::Key("f#猫o")), Some(&Edn::Str("bar")));
assert_eq!(edn_data.contains(&Edn::Key("f#猫o")), true);
assert_eq!(edn_data.get(&Edn::Key("foo")), None);
assert_eq!(edn_data.contains(&Edn::Key("foo")), false);

let edn_data = edn::read_string(r#"#{:f#猫o "bar" :baz/bar "qux" 42 24}"#).unwrap();
assert_eq!(edn_data.contains(&Edn::Key("f#猫o")), true);
assert_eq!(edn_data.contains(&Edn::Int(42)), true);
assert_eq!(edn_data.contains(&Edn::Key("foo")), false);

let edn_data = edn::read_string(r#"[:f#猫o "bar" :baz/bar "qux" 42 24]"#).unwrap();
assert_eq!(edn_data.contains(&Edn::Key("f#猫o")), true);
assert_eq!(edn_data.contains(&Edn::Int(42)), true);
assert_eq!(edn_data.contains(&Edn::Key("foo")), false);

let edn_data = edn::read_string(r#"(:f#猫o "bar" :baz/bar "qux" 42 24)"#).unwrap();
assert_eq!(edn_data.contains(&Edn::Key("f#猫o")), true);
assert_eq!(edn_data.contains(&Edn::Int(42)), true);
assert_eq!(edn_data.contains(&Edn::Key("foo")), false);

let edn_data = edn::read_string(r#"42"#).unwrap();
assert_eq!(edn_data.contains(&Edn::Key("f#猫o")), false);
assert_eq!(edn_data.contains(&Edn::Int(42)), false);
assert_eq!(edn_data.contains(&Edn::Key("foo")), false);
}
4 changes: 3 additions & 1 deletion tests/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,10 @@ fn lisp_quoted() {
}

#[test]
fn numeric_like_symbols() {
fn numeric_like_symbols_keywords() {
assert_eq!(edn::read_string("-foobar").unwrap(), Edn::Symbol("-foobar"));
assert_eq!(edn::read_string("-:thi#n=g").unwrap(), Edn::Symbol("-:thi#n=g"));
assert_eq!(edn::read_string(":thi#n=g").unwrap(), Edn::Key("thi#n=g"));

assert_eq!(
edn::read_string("(+foobar +foo+bar+ +'- '-+)").unwrap(),
Expand Down
Loading