Skip to content

Commit 133484f

Browse files
committed
Implement contains for Lists, Vectors and Sets, to match clojure's contains?
1 parent 91f8ca2 commit 133484f

3 files changed

Lines changed: 119 additions & 31 deletions

File tree

examples/get-nth.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ fn maybe_forty_two<'a>(edn: &'a Edn<'a>) -> Option<&'a Edn<'a>> {
1616
.nth(2)
1717
}
1818

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

@@ -28,14 +28,27 @@ fn namespace_get() {
2828
assert_eq!(edn_data.get(&Edn::Key("thingy/foo")), Some(&Edn::Str("bar")));
2929
// (get edn-data :baz/bar) -> "qux"
3030
assert_eq!(edn_data.get(&Edn::Key("baz/bar")), Some(&Edn::Str("qux")));
31+
32+
// (contains? edn-data 42) -> true
33+
assert!(edn_data.contains(&Edn::Int(42)));
34+
// (contains? edn-data "42") -> false
35+
assert!(!edn_data.contains(&Edn::Str("42")));
36+
// (contains? edn-data :foo) -> false
37+
assert!(!edn_data.contains(&Edn::Key("foo")));
38+
// (contains? edn-data :thingy/foo) -> true
39+
assert!(edn_data.contains(&Edn::Key("thingy/foo")));
40+
// (contains? edn-data :baz/bar) -> true
41+
assert!(edn_data.contains(&Edn::Key("baz/bar")));
42+
// (contains? edn-data :bar/baz) -> false
43+
assert!(!edn_data.contains(&Edn::Key("bar/baz")));
3144
}
3245

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

38-
namespace_get();
51+
namespace_get_contains();
3952
}
4053

4154
#[test]

src/edn.rs

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -74,38 +74,43 @@ pub fn read(edn: &str) -> Result<(Edn<'_>, &str), error::Error> {
7474
Ok((r.0, r.1))
7575
}
7676

77+
fn get_tag<'a>(tag: &'a str, key: &'a str) -> Option<&'a str> {
78+
// Break out early if there's no namespaces
79+
if !key.contains('/') {
80+
return None;
81+
}
82+
83+
// ignore the leading ':'
84+
if !tag.starts_with(':') {
85+
return None;
86+
}
87+
let tag = tag.get(1..)?;
88+
Some(tag)
89+
}
90+
91+
fn check_key<'a>(tag: &'a str, key: &'a str) -> &'a str {
92+
// check if the Key starts with the saved Tag
93+
if key.starts_with(tag) {
94+
let (_, key) = key.rsplit_once(tag).expect("Tag must exist, because it starts with it.");
95+
96+
// ensure there's a '/' and strip it
97+
if let Some(k) = key.strip_prefix('/') {
98+
return k;
99+
}
100+
}
101+
key
102+
}
103+
77104
impl Edn<'_> {
78105
pub fn get(&self, e: &Self) -> Option<&Self> {
79106
if let Edn::Map(m) = self {
80-
if let Some(l) = m.get(e) {
81-
return Some(l);
82-
}
107+
return m.get(e);
83108
} else if let Edn::Tagged(tag, m) = self {
84-
if let Edn::Key(e) = e {
85-
// Break out early if there's no namespaces
86-
if !e.contains('/') {
87-
return None;
88-
}
109+
if let Edn::Key(key) = e {
110+
let tag = get_tag(tag, key)?;
111+
let key = check_key(tag, key);
89112

90-
// ignore the leading ':'
91-
if !tag.starts_with(':') {
92-
return None;
93-
}
94-
let tag = tag.get(1..)?;
95-
96-
// check if the Key starts with the saved Tag
97-
if e.starts_with(tag) {
98-
let (_, key) = e.rsplit_once(tag)?;
99-
100-
// ensure there's a '/' and strip it
101-
if key.chars().nth(0) != Some('/') {
102-
return None;
103-
}
104-
let key = key.get(1..)?;
105-
106-
return m.get(&Edn::Key(key));
107-
}
108-
return m.get(&Edn::Key(e));
113+
return m.get(&Edn::Key(key));
109114
}
110115

111116
// Cover cases where it's not a keyword
@@ -122,6 +127,27 @@ impl Edn<'_> {
122127

123128
vec.get(i)
124129
}
130+
131+
pub fn contains(&self, e: &Self) -> bool {
132+
match self {
133+
Edn::Map(m) => m.contains_key(e),
134+
Edn::Tagged(tag, m) => {
135+
if let Edn::Key(key) = e {
136+
let Some(tag) = get_tag(tag, key) else { return false };
137+
let key = check_key(tag, key);
138+
139+
return m.contains(&Edn::Key(key));
140+
}
141+
142+
// Cover cases where it's not a keyword
143+
m.contains(e)
144+
}
145+
Edn::Vector(v) => v.contains(e),
146+
Edn::Set(s) => s.contains(e),
147+
Edn::List(l) => l.contains(e),
148+
_ => false,
149+
}
150+
}
125151
}
126152

127153
pub(crate) const fn char_to_edn(c: char) -> Option<&'static str> {

tests/read.rs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,10 @@ fn lisp_quoted() {
139139
}
140140

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

145147
assert_eq!(
146148
edn::read_string("(+foobar +foo+bar+ +'- '-+)").unwrap(),
@@ -238,7 +240,7 @@ fn tagged() {
238240
}
239241

240242
#[test]
241-
fn test_default_map_namespace_syntax() {
243+
fn default_map_namespace_syntax() {
242244
// see https://github.com/Grinkers/clojure-reader/issues/2
243245
let variations = [
244246
"{:thingy #:foo{:bar \"baz\"} :more \"stuff\"}",
@@ -282,3 +284,50 @@ fn test_default_map_namespace_syntax() {
282284
assert_eq!(cfg.get(&Edn::Key("more")), Some(&Edn::Str("stuff")));
283285
}
284286
}
287+
288+
#[test]
289+
fn namespace_syntax_edge_cases() {
290+
let edn_data = edn::read_string(r#"#:thingy {:f#猫o "bar" :baz/bar "qux" 42 24}"#).unwrap();
291+
292+
assert_eq!(edn_data.get(&Edn::Key("thingy/f#猫o")), Some(&Edn::Str("bar")));
293+
assert_eq!(edn_data.get(&Edn::Key("baz/bar")), Some(&Edn::Str("qux")));
294+
assert_eq!(edn_data.get(&Edn::Key("foo")), None);
295+
assert_eq!(edn_data.get(&Edn::Key("baz")), None);
296+
assert_eq!(edn_data.get(&Edn::Key(":baz/bar")), None);
297+
assert_eq!(edn_data.get(&Edn::Key("thingy/")), None);
298+
assert_eq!(edn_data.get(&Edn::Key("thingy")), None);
299+
assert_eq!(edn_data.get(&Edn::Key("thingything")), None);
300+
301+
let edn_data = edn::read_string(r#"#thingy {:f#猫o "bar" :baz/bar "qux" 42 24}"#).unwrap();
302+
assert_eq!(edn_data.get(&Edn::Key("thingy/f#猫o")), None);
303+
assert_eq!(edn_data.get(&Edn::Key("baz/bar")), None);
304+
}
305+
306+
#[test]
307+
fn get_contains() {
308+
let edn_data = edn::read_string(r#"{:f#猫o "bar" :baz/bar "qux" 42 24}"#).unwrap();
309+
assert_eq!(edn_data.get(&Edn::Key("f#猫o")), Some(&Edn::Str("bar")));
310+
assert_eq!(edn_data.contains(&Edn::Key("f#猫o")), true);
311+
assert_eq!(edn_data.get(&Edn::Key("foo")), None);
312+
assert_eq!(edn_data.contains(&Edn::Key("foo")), false);
313+
314+
let edn_data = edn::read_string(r#"#{:f#猫o "bar" :baz/bar "qux" 42 24}"#).unwrap();
315+
assert_eq!(edn_data.contains(&Edn::Key("f#猫o")), true);
316+
assert_eq!(edn_data.contains(&Edn::Int(42)), true);
317+
assert_eq!(edn_data.contains(&Edn::Key("foo")), false);
318+
319+
let edn_data = edn::read_string(r#"[:f#猫o "bar" :baz/bar "qux" 42 24]"#).unwrap();
320+
assert_eq!(edn_data.contains(&Edn::Key("f#猫o")), true);
321+
assert_eq!(edn_data.contains(&Edn::Int(42)), true);
322+
assert_eq!(edn_data.contains(&Edn::Key("foo")), false);
323+
324+
let edn_data = edn::read_string(r#"(:f#猫o "bar" :baz/bar "qux" 42 24)"#).unwrap();
325+
assert_eq!(edn_data.contains(&Edn::Key("f#猫o")), true);
326+
assert_eq!(edn_data.contains(&Edn::Int(42)), true);
327+
assert_eq!(edn_data.contains(&Edn::Key("foo")), false);
328+
329+
let edn_data = edn::read_string(r#"42"#).unwrap();
330+
assert_eq!(edn_data.contains(&Edn::Key("f#猫o")), false);
331+
assert_eq!(edn_data.contains(&Edn::Int(42)), false);
332+
assert_eq!(edn_data.contains(&Edn::Key("foo")), false);
333+
}

0 commit comments

Comments
 (0)