Skip to main content

cljrs_value/
symbol.rs

1use std::sync::Arc;
2
3/// An interned Clojure symbol, optionally namespace-qualified.
4///
5/// `"foo"` → `Symbol { namespace: None, name: "foo" }`
6/// `"clojure.core/map"` → `Symbol { namespace: Some("clojure.core"), name: "map" }`
7#[derive(Debug, Clone, PartialEq, Eq, Hash)]
8pub struct Symbol {
9    pub namespace: Option<Arc<str>>,
10    pub name: Arc<str>,
11}
12
13impl Symbol {
14    /// Unqualified symbol.
15    pub fn simple(name: impl Into<Arc<str>>) -> Self {
16        Self {
17            namespace: None,
18            name: name.into(),
19        }
20    }
21
22    /// Namespace-qualified symbol.
23    pub fn qualified(ns: impl Into<Arc<str>>, name: impl Into<Arc<str>>) -> Self {
24        Self {
25            namespace: Some(ns.into()),
26            name: name.into(),
27        }
28    }
29
30    /// Parse a symbol from a string of the form `"ns/name"` or `"name"`.
31    pub fn parse(s: &str) -> Self {
32        match s.find('/') {
33            Some(idx) if idx > 0 && idx < s.len() - 1 => Self::qualified(&s[..idx], &s[idx + 1..]),
34            _ => Self::simple(s),
35        }
36    }
37
38    /// The fully-qualified string, e.g. `"clojure.core/map"` or `"foo"`.
39    pub fn full_name(&self) -> String {
40        match &self.namespace {
41            Some(ns) => format!("{}/{}", ns, self.name),
42            None => self.name.to_string(),
43        }
44    }
45}
46
47impl std::fmt::Display for Symbol {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        match &self.namespace {
50            Some(ns) => write!(f, "{}/{}", ns, self.name),
51            None => write!(f, "{}", self.name),
52        }
53    }
54}
55
56impl cljrs_gc::Trace for Symbol {
57    fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn test_simple() {
66        let s = Symbol::simple("foo");
67        assert_eq!(s.name.as_ref(), "foo");
68        assert!(s.namespace.is_none());
69        assert_eq!(s.full_name(), "foo");
70    }
71
72    #[test]
73    fn test_qualified() {
74        let s = Symbol::qualified("clojure.core", "map");
75        assert_eq!(s.full_name(), "clojure.core/map");
76    }
77
78    #[test]
79    fn test_parse() {
80        assert_eq!(Symbol::parse("foo"), Symbol::simple("foo"));
81        assert_eq!(Symbol::parse("a/b"), Symbol::qualified("a", "b"));
82        // bare "/" is unqualified
83        assert_eq!(Symbol::parse("/").namespace, None);
84    }
85}