rune/core/env/
symbol_map.rs

1use crate::core::{
2    gc::{Block, Context},
3    object::{CloneIn, Function, LispBuffer, Symbol, WithLifetime},
4};
5use anyhow::Result;
6use rune_core::hashmap::HashMap;
7
8pub(crate) struct SymbolMap {
9    map: SymbolMapCore,
10    block: Block<true>,
11}
12
13struct SymbolMapCore {
14    map: HashMap<&'static str, Symbol<'static>>,
15}
16
17impl SymbolMapCore {
18    fn with_capacity(cap: usize) -> Self {
19        Self {
20            map: HashMap::with_capacity_and_hasher(cap, std::hash::BuildHasherDefault::default()),
21        }
22    }
23
24    fn get(&self, name: &str) -> Option<Symbol> {
25        self.map.get(name).map(|x| unsafe { x.with_lifetime() })
26    }
27
28    fn intern<'ob>(&mut self, name: &str, block: &Block<true>, cx: &'ob Context) -> Symbol<'ob> {
29        match self.get(name) {
30            Some(x) => cx.bind(x),
31            None => {
32                let name = name.to_owned();
33                // Leak the memory so that it is static
34                let static_name: &'static str = unsafe {
35                    let name_ptr: *const str = Box::into_raw(name.into_boxed_str());
36                    &*name_ptr
37                };
38                let sym = Symbol::new(static_name, block);
39                self.map.insert(static_name, unsafe { sym.with_lifetime() });
40                cx.bind(sym)
41            }
42        }
43    }
44
45    fn pre_init(&mut self, sym: Symbol<'static>) {
46        use std::collections::hash_map::Entry;
47        let name = sym.get().name();
48        let entry = self.map.entry(name);
49        assert!(matches!(entry, Entry::Vacant(_)), "Attempt to intitalize {name} twice");
50        entry.or_insert_with(|| sym);
51    }
52}
53
54impl SymbolMap {
55    pub(crate) fn intern<'ob>(&mut self, name: &str, cx: &'ob Context) -> Symbol<'ob> {
56        self.map.intern(name, &self.block, cx)
57    }
58
59    pub(crate) fn set_func(&self, symbol: Symbol, func: Function) -> Result<()> {
60        let new_func = func.clone_in(&self.block);
61        self.block.uninterned_symbol_map.clear();
62        // SAFETY: The object is marked read-only, we have cloned in the map's
63        // context, and it is const, so calling this function is safe.
64        unsafe { symbol.set_func(new_func) }
65    }
66
67    pub(crate) fn global_block(&self) -> &Block<true> {
68        &self.block
69    }
70
71    pub(crate) fn create_buffer(&self, name: &str) -> &LispBuffer {
72        LispBuffer::create(name.to_owned(), &self.block)
73    }
74
75    pub(crate) fn get(&self, name: &str) -> Option<Symbol> {
76        self.map.get(name)
77    }
78}
79
80// This file includes all symbol definitions. Generated by build.rs
81include!(concat!(env!("OUT_DIR"), "/sym.rs"));
82
83/// Intern a new symbol based on `name`
84pub(crate) fn intern<'ob>(name: &str, cx: &'ob Context) -> Symbol<'ob> {
85    INTERNED_SYMBOLS.lock().unwrap().intern(name, cx)
86}
87
88#[cfg(test)]
89mod test {
90    use super::*;
91    use crate::core::gc::RootSet;
92    use crate::core::object::FunctionType;
93    use crate::core::{cons::Cons, env::Env, object::Object};
94    use rune_core::macros::{list, root};
95
96    #[test]
97    fn size() {
98        assert_eq!(size_of::<isize>(), size_of::<Symbol>());
99        assert_eq!(size_of::<isize>(), size_of::<Function>());
100    }
101
102    #[test]
103    fn init() {
104        let roots = &RootSet::default();
105        let cx = &Context::new(roots);
106        intern("foo", cx);
107    }
108
109    #[test]
110    fn symbol_func() {
111        let roots = &RootSet::default();
112        let cx = &Context::new(roots);
113        sym::init_symbols();
114        let sym = Symbol::new_uninterned("foo", cx);
115        assert_eq!("foo", sym.name());
116        assert!(sym.func(cx).is_none());
117        let func1 = Cons::new1(1, cx);
118        unsafe {
119            sym.set_func(func1.into()).unwrap();
120        }
121        let cell1 = sym.func(cx).unwrap();
122        let FunctionType::Cons(before) = cell1.untag() else {
123            unreachable!("Type should be a lisp function")
124        };
125        assert_eq!(before.car(), 1);
126        let func2 = Cons::new1(2, cx);
127        unsafe {
128            sym.set_func(func2.into()).unwrap();
129        }
130        let cell2 = sym.func(cx).unwrap();
131        let FunctionType::Cons(after) = cell2.untag() else {
132            unreachable!("Type should be a lisp function")
133        };
134        assert_eq!(after.car(), 2);
135        assert_eq!(before.car(), 1);
136
137        unsafe {
138            sym.set_func(sym::NIL.into()).unwrap();
139        }
140        assert!(!sym.has_func());
141    }
142
143    #[test]
144    fn test_mutability() {
145        let roots = &RootSet::default();
146        let cx = &Context::new(roots);
147        let cons = list!(1, 2, 3; cx);
148        assert_eq!(cons, list!(1, 2, 3; cx));
149        // is mutable
150        if let crate::core::object::ObjectType::Cons(cons) = cons.untag() {
151            cons.set_car(4.into()).unwrap();
152        } else {
153            unreachable!();
154        }
155        assert_eq!(cons, list!(4, 2, 3; cx));
156        let sym = intern("cons-test", cx);
157        crate::data::fset(sym, cons).unwrap();
158        // is not mutable
159        if let FunctionType::Cons(cons) = sym.func(cx).unwrap().untag() {
160            assert!(cons.set_car(5.into()).is_err());
161            let obj: Object = cons.into();
162            assert_eq!(obj, list!(4, 2, 3; cx));
163        } else {
164            unreachable!();
165        }
166    }
167
168    #[test]
169    fn test_init_variables() {
170        let roots = &RootSet::default();
171        let cx = &Context::new(roots);
172        root!(env, new(Env), cx);
173        init_variables(cx, env);
174    }
175}