Skip to content

Commit 5507de8

Browse files
committed
Add namespaces to symbol / keyword, add namespace qualified symbols to reader
1 parent 7260ba8 commit 5507de8

File tree

3 files changed

+121
-25
lines changed

3 files changed

+121
-25
lines changed

src/keyword.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,26 @@ pub struct Keyword {
1010
impl Keyword {
1111
pub fn intern(name: &str) -> Keyword {
1212
Keyword {
13-
sym: Symbol {
14-
name: String::from(name),
15-
},
13+
sym: Symbol::intern(name)
14+
}
15+
}
16+
// Note; normally 'with_x' would imply x is the second argument
17+
// here, but we are keeping the semantics of interning that
18+
// Clojure proper has
19+
pub fn intern_with_ns(ns: &str, name: &str) -> Keyword {
20+
Keyword {
21+
sym: Symbol::intern_with_ns(name,ns)
1622
}
1723
}
1824
}
1925
impl fmt::Display for Keyword {
2026
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
21-
write!(f, ":{}", self.sym.name)
27+
if self.sym.ns != "" {
28+
write!(f, ":{}/{}", self.sym.ns,self.sym.name)
29+
}
30+
else {
31+
write!(f, ":{}", self.sym.name)
32+
}
33+
2234
}
2335
}

src/reader.rs

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
1111
use nom::{
1212
branch::alt, bytes::complete::tag, map, sequence::preceded, take_until, terminated,
13-
Err::Incomplete, IResult, Needed,
13+
combinator::opt, Err::Incomplete, IResult, Needed,
1414
};
1515

1616
use crate::keyword::Keyword;
@@ -104,7 +104,7 @@ fn cons_str(head: char, tail: &str) -> String {
104104
/// - `*`,
105105
/// - `!`,
106106
fn is_identifier_char(chr: char) -> bool {
107-
chr.is_alphanumeric() || "|?<>+-_=^%&$*!".contains(chr)
107+
chr.is_alphanumeric() || "|?<>+-_=^%&$*!.".contains(chr)
108108
}
109109

110110
/// Returns whether if a character can be in the head of an identifier.
@@ -128,7 +128,7 @@ fn is_identifier_char(chr: char) -> bool {
128128
/// - `*`,
129129
/// - `!`,
130130
fn is_non_numeric_identifier_char(chr: char) -> bool {
131-
chr.is_alphabetic() || "|?<>+-_=^%&$*!".contains(chr)
131+
chr.is_alphabetic() || "|?<>+-_=^%&$*!.".contains(chr)
132132
}
133133

134134
/// Returns true if given character is a minus character
@@ -192,9 +192,22 @@ pub fn identifier_parser(input: &str) -> IResult<&str, String> {
192192
identifier(input)
193193
}
194194

195-
/// Parses valid Clojure symbols, whose name is a valid identifier
195+
/// Parses valid Clojure symbol
196+
/// Example Successes: a , b , |ab123|
197+
/// namespace.subnamespace/a cat/b a.b.c/|ab123|
196198
pub fn symbol_parser(input: &str) -> IResult<&str, Symbol> {
197-
identifier_parser(input).map(|(rest_input, name)| (rest_input, Symbol::intern(&name)))
199+
named!(namespace_parser <&str,String>,
200+
do_parse!(
201+
ns: identifier_parser >>
202+
tag!("/") >>
203+
(ns)));
204+
205+
let (rest_input,ns) = opt(namespace_parser)(input)?;
206+
let (rest_input,name) = identifier_parser(rest_input)?;
207+
match ns {
208+
Some(ns) => Ok((rest_input,Symbol::intern_with_ns(&ns,&name))),
209+
None => Ok((rest_input,Symbol::intern(&name)))
210+
}
198211
}
199212

200213
/// Parses valid integers
@@ -549,6 +562,7 @@ mod tests {
549562
}
550563

551564
mod symbol_parser_tests {
565+
use crate::reader::try_read_symbol;
552566
use crate::reader::symbol_parser;
553567
use crate::symbol::Symbol;
554568

@@ -557,9 +571,7 @@ mod tests {
557571
assert_eq!(
558572
Some((
559573
" this",
560-
Symbol {
561-
name: String::from("input->output?")
562-
}
574+
Symbol::intern("input->output?")
563575
)),
564576
symbol_parser("input->output? this").ok()
565577
);
@@ -574,6 +586,21 @@ mod tests {
574586
fn identifier_parser_does_not_parse_empty_input() {
575587
assert_eq!(None, symbol_parser("").ok());
576588
}
589+
590+
#[test]
591+
fn symbol_parser_normal_symbol_test() {
592+
assert_eq!(
593+
Symbol::intern("a"),
594+
symbol_parser("a ").ok().unwrap().1
595+
);
596+
}
597+
#[test]
598+
fn symbol_parser_namespace_qualified_symbol_test() {
599+
assert_eq!(
600+
Symbol::intern_with_ns("clojure.core","a"),
601+
symbol_parser("clojure.core/a ").ok().unwrap().1
602+
);
603+
}
577604
}
578605

579606
mod double_parser_tests {
@@ -653,9 +680,7 @@ mod tests {
653680
#[test]
654681
fn try_read_minus_as_valid_symbol_test() {
655682
assert_eq!(
656-
Value::Symbol(Symbol {
657-
name: String::from("-")
658-
}),
683+
Value::Symbol(Symbol::intern("-")),
659684
try_read_symbol("- ").unwrap().1
660685
);
661686
}
@@ -671,7 +696,7 @@ mod tests {
671696
use crate::value::Value;
672697
use crate::value::Value::{PersistentList, PersistentListMap, PersistentVector};
673698
use std::rc::Rc;
674-
699+
675700
#[test]
676701
fn try_read_empty_map_test() {
677702
assert_eq!(
@@ -706,29 +731,23 @@ mod tests {
706731
#[test]
707732
fn try_read_valid_symbol_test() {
708733
assert_eq!(
709-
Value::Symbol(Symbol {
710-
name: String::from("my-symbol")
711-
}),
734+
Value::Symbol(Symbol::intern("my-symbol")),
712735
try_read("my-symbol ").ok().unwrap().1
713736
);
714737
}
715738

716739
#[test]
717740
fn try_read_minus_as_valid_symbol_test() {
718741
assert_eq!(
719-
Value::Symbol(Symbol {
720-
name: String::from("-")
721-
}),
742+
Value::Symbol(Symbol::intern("-")),
722743
try_read("- ").ok().unwrap().1
723744
);
724745
}
725746

726747
#[test]
727748
fn try_read_minus_prefixed_as_valid_symbol_test() {
728749
assert_eq!(
729-
Value::Symbol(Symbol {
730-
name: String::from("-prefixed")
731-
}),
750+
Value::Symbol(Symbol::intern("-prefixed")),
732751
try_read("-prefixed ").ok().unwrap().1
733752
);
734753
}

src/symbol.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,81 @@ use std::hash::Hash;
44
#[derive(Hash, PartialEq, Eq, Clone, Debug)]
55
pub struct Symbol {
66
pub name: String,
7+
// @TODO Should this be an optional string?
8+
// on one hand, playing with this is closer to the original,
9+
// and slightly easier to read and understand (for me).
10+
// But you might say it doesn't force you to cover the None
11+
// route, the sort of invariants ADTs are good at.
12+
// Most likely, we will reimplement this as Option<String>
13+
pub ns: String
714
}
815
impl Symbol {
916
pub fn intern(name: &str) -> Symbol {
17+
let mut ns = "";
18+
let mut name = name;
19+
// @TODO See if we will have any problems with manipulating
20+
// text in other languages
21+
// I think we will be ok here though
22+
if let Some(ind) = name.chars().position(|c| c == '/') {
23+
// @TODO Make sure that the index given by ^
24+
// has the same meaning as the index
25+
// we are giving to this range
26+
// Ie, if the 6 in a[..6] refers to the 6th byte
27+
// and if the 6 in Some(6) means the 6th character,
28+
// we need to make sure each 'character' in this case
29+
// is 1 byte, and not some other grapheme abstraction
30+
// else,these are two different indexes
31+
ns = &name[..ind];
32+
name = &name[ind+1..];
33+
}
34+
Symbol::intern_with_ns(ns,name)
35+
}
36+
pub fn intern_with_ns(ns: &str,name: &str) -> Symbol {
1037
Symbol {
1138
name: String::from(name),
39+
ns: String::from(ns)
1240
}
1341
}
42+
pub fn unqualified(&self) -> Symbol {
43+
Symbol::intern(&self.name)
44+
}
1445
}
1546
impl fmt::Display for Symbol {
1647
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1748
write!(f, "{}", self.name)
1849
}
1950
}
51+
mod tests {
52+
53+
mod symbol_tests {
54+
use crate::symbol::Symbol;
55+
use std::cell::RefCell;
56+
use std::collections::HashMap;
57+
use std::rc::Rc;
58+
59+
#[test]
60+
fn test_intern() {
61+
assert_eq!(Symbol::intern("a"), Symbol { ns: String::from(""), name: String::from("a")});
62+
}
63+
64+
#[test]
65+
fn test_intern_with_ns() {
66+
assert_eq!(Symbol::intern_with_ns("clojure.core","a"), Symbol { ns: String::from("clojure.core"), name: String::from("a")});
67+
assert_eq!(Symbol::intern_with_ns("","a"), Symbol { ns: String::from(""), name: String::from("a")});
68+
assert_eq!(Symbol::intern("a"), Symbol { ns: String::from(""), name: String::from("a")});
69+
assert_eq!(Symbol::intern("clojure.core/a"), Symbol { ns: String::from("clojure.core"), name: String::from("a")});
70+
assert_eq!(Symbol::intern("clojure/a"), Symbol { ns: String::from("clojure"), name: String::from("a")});
71+
assert_eq!(Symbol::intern("/a"), Symbol { ns: String::from(""), name: String::from("a")});
72+
}
73+
fn test_work_with_hashmap() {
74+
let mut hashmap = HashMap::new();
75+
hashmap.insert(Symbol::intern("+"),1_i32);
76+
hashmap.insert(Symbol::intern("-"),2_i32);
77+
78+
assert_eq!(1_i32,*hashmap.get(&Symbol::intern("+")).unwrap());
79+
assert_eq!(2_i32,*hashmap.get(&Symbol::intern("-")).unwrap());
80+
assert_eq!(None,hashmap.get(&Symbol::intern("*")));
81+
82+
}
83+
}
84+
}

0 commit comments

Comments
 (0)