Skip to main content

cljrs_value/
keyword.rs

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