1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
use crate::Symbol;
use std::borrow::Borrow;
use std::fmt::{Debug, Formatter};
use std::hash::{Hash, Hasher};

/// A reference to a fully resolved symbol. Like `Symbol` (a fully resolved symbol with a
/// static lifetime), a `SymbolRef` may have known or undefined text (i.e. `$0`).
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct SymbolRef<'a> {
    text: Option<&'a str>,
}

impl<'a> Debug for SymbolRef<'a> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.text.unwrap_or("$0"))
    }
}

impl<'a> SymbolRef<'a> {
    /// If this symbol has known text, returns `Some(&str)`. Otherwise, returns `None`.
    pub fn text(&self) -> Option<&str> {
        self.text
    }

    /// Constructs a `SymbolRef` with unknown text.
    pub fn with_unknown_text() -> Self {
        SymbolRef { text: None }
    }

    /// Constructs a `SymbolRef` with the specified text.
    pub fn with_text(text: &str) -> SymbolRef {
        SymbolRef { text: Some(text) }
    }

    pub fn to_owned(self) -> Symbol {
        match self.text() {
            None => Symbol::unknown_text(),
            Some(text) => Symbol::owned(text),
        }
    }
}

impl<'a, A> PartialEq<A> for SymbolRef<'a>
where
    A: AsSymbolRef,
{
    fn eq(&self, other: &A) -> bool {
        let other_symbol_ref = other.as_symbol_ref();
        self == &other_symbol_ref
    }
}

/// Allows a `SymbolRef` to be constructed from a source value. This enables non-symbol types to be
/// viewed as a symbol with little to no runtime overhead.
pub trait AsSymbolRef {
    fn as_symbol_ref(&self) -> SymbolRef;
}

// All text types can be viewed as a `SymbolRef`.
impl<'a, A: AsRef<str> + 'a> AsSymbolRef for A {
    fn as_symbol_ref(&self) -> SymbolRef {
        SymbolRef {
            text: Some(self.as_ref()),
        }
    }
}

impl<'a> Hash for SymbolRef<'a> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        match self.text {
            None => 0.hash(state),
            Some(text) => text.hash(state),
        }
    }
}

impl<'a> From<&'a str> for SymbolRef<'a> {
    fn from(text: &'a str) -> Self {
        Self { text: Some(text) }
    }
}

impl<'a> From<&'a Symbol> for SymbolRef<'a> {
    fn from(symbol: &'a Symbol) -> Self {
        Self {
            text: symbol.text(),
        }
    }
}

// Note that this method panics if the SymbolRef has unknown text! This is unfortunate but is required
// in order to allow a HashMap<SymbolRef, _> to do lookups with a &str instead of a &SymbolRef
impl<'a> Borrow<str> for SymbolRef<'a> {
    fn borrow(&self) -> &str {
        self.text()
            .expect("cannot borrow a &str from a SymbolRef with unknown text")
    }
}

// Owned `Symbol` values can be viewed as a `SymbolRef`. Due to lifetime conflicts in the
// trait definitions, this cannot be achieved with `AsRef` or `Borrow`.
impl AsSymbolRef for Symbol {
    fn as_symbol_ref(&self) -> SymbolRef {
        self.text()
            .map(SymbolRef::with_text)
            .unwrap_or_else(SymbolRef::with_unknown_text)
    }
}

impl AsSymbolRef for &Symbol {
    fn as_symbol_ref(&self) -> SymbolRef {
        self.text()
            .map(SymbolRef::with_text)
            .unwrap_or_else(SymbolRef::with_unknown_text)
    }
}

impl<'borrow, 'data> AsSymbolRef for &'borrow SymbolRef<'data> {
    fn as_symbol_ref(&self) -> SymbolRef<'data> {
        // This is essentially free; the only data inside is an Option<&str>
        (*self).clone()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn symbol_ref_with_text() {
        let symbol_ref = SymbolRef::with_text("foo");
        assert_eq!(Some("foo"), symbol_ref.text());
    }

    #[test]
    fn symbol_ref_with_unknown_text() {
        let symbol_ref = SymbolRef::with_unknown_text();
        assert_eq!(None, symbol_ref.text());
    }

    #[test]
    fn str_as_symbol_ref() {
        let symbol_ref: SymbolRef = "foo".as_symbol_ref();
        assert_eq!(Some("foo"), symbol_ref.text());
    }

    #[test]
    fn symbol_as_symbol_ref() {
        let symbol = Symbol::owned("foo");
        let symbol_ref: SymbolRef = symbol.as_symbol_ref();
        assert_eq!(Some("foo"), symbol_ref.text());
    }

    #[test]
    fn symbol_with_unknown_text_as_symbol_ref() {
        let symbol = Symbol::unknown_text();
        let symbol_ref: SymbolRef = symbol.as_symbol_ref();
        assert_eq!(None, symbol_ref.text());
    }
}