1use std::{
2 cell::{Cell, RefCell},
3 cmp::Ordering,
4 collections::{hash_map::Entry, HashMap},
5 fmt::{self, Debug, Display},
6 hash::{Hash, Hasher},
7 marker::PhantomData,
8};
9
10use derivative::Derivative;
11
12use crate::{names::UnknownKind, span::SimpleSpan};
13
14#[derive(Default)]
15pub struct CodeIdentAllocator {
16 ids: RefCell<HashMap<String, u16>>,
17 max_id: Cell<u16>,
18
19 #[cfg(feature = "strict")]
20 generation: Cell<u8>,
21 max_kind: Cell<u8>,
22}
23impl CodeIdentAllocator {
24 #[cfg(feature = "strict")]
25 pub fn next_generation(&self) {
26 let max_kind = self.max_kind.get();
27 self.generation.set(
28 self.generation
29 .get()
30 .checked_add(max_kind + 1)
31 .expect("out of generations"),
32 );
33 self.max_kind.set(0);
34 }
35 fn name(&self, id: u16) -> String {
36 let ids = self.ids.borrow();
37 for (k, v) in ids.iter() {
38 if *v == id {
39 return k.to_owned();
40 }
41 }
42 unreachable!("identifier with unknown owner")
43 }
44 fn ident<K: Kind>(&self, span: SimpleSpan, name: &str) -> Ident<K> {
45 let kind = K::id();
46 self.max_kind.set(self.max_kind.get().max(kind));
47 let kind = {
48 #[cfg(feature = "strict")]
49 {
50 self.generation
51 .get()
52 .checked_add(kind)
53 .expect("out of kinds")
54 }
55 #[cfg(not(feature = "strict"))]
56 {
57 0
58 }
59 };
60 let mut ids = self.ids.borrow_mut();
61 match ids.entry(name.to_owned()) {
62 Entry::Occupied(v) => {
63 return Ident {
64 #[cfg(feature = "strict")]
65 kind,
66 id: *v.get(),
67 span,
68 _marker: PhantomData,
69 }
70 }
71 Entry::Vacant(v) => {
72 let id = self.max_id.get();
73 self.max_id.set(id.checked_add(1).expect("out of ids"));
74 v.insert(id);
75 Ident {
76 #[cfg(feature = "strict")]
77 kind,
78 id,
79 span,
80 _marker: PhantomData,
81 }
82 }
83 }
84 }
85}
86
87pub trait Kind {
88 fn id() -> u8;
89}
90
91thread_local! {
92 static ALLOCATOR: (Cell<bool>, CodeIdentAllocator) = (Cell::new(false), CodeIdentAllocator::default());
93}
94pub fn in_allocator<T>(f: impl FnOnce() -> T) -> T {
95 ALLOCATOR.with(|a| {
96 assert!(!a.0.get(), "already in allocator");
97 a.0.set(true);
98 });
99 let v = f();
100 ALLOCATOR.with(|a| {
101 assert!(a.0.get(), "should be in allocator");
102 a.0.set(false);
103 #[cfg(feature = "strict")]
104 a.1.next_generation();
105 });
106 v
107}
108
109pub struct Ident<K> {
110 #[cfg(feature = "strict")]
111 kind: u8,
112 id: u16,
113 span: SimpleSpan,
114 _marker: PhantomData<fn() -> K>,
115}
116impl<K> Ident<K> {
117 pub fn name(&self) -> String {
118 ALLOCATOR.with(|a| a.1.name(self.id))
119 }
120 pub fn span(&self) -> SimpleSpan {
121 self.span
122 }
123}
124impl<K: Kind> Ident<K> {
125 pub fn alloc((span, name): (SimpleSpan, &str)) -> Self {
126 ALLOCATOR.with(|a| {
127 assert!(a.0.get(), "should be in allocator");
128 a.1.ident(span, name)
129 })
130 }
131 pub fn unchecked_cast<U: Kind>(v: Ident<U>) -> Self {
132 assert_eq!(
133 K::id(),
134 U::id(),
135 "types should be explicitly marked as compatible"
136 );
137 Ident {
138 #[cfg(feature = "strict")]
139 kind: v.kind,
140 id: v.id,
141 span: v.span,
142 _marker: PhantomData,
143 }
144 }
145 pub fn to_unknown(self) -> Ident<UnknownKind> {
146 Ident {
148 #[cfg(feature = "strict")]
149 kind: UnknownKind::id(),
150 id: self.id,
151 span: self.span,
152 _marker: PhantomData,
153 }
154 }
155}
156impl<K> Clone for Ident<K> {
157 fn clone(&self) -> Self {
158 *self
159 }
160}
161impl<K> Copy for Ident<K> {}
162impl<K> PartialEq for Ident<K> {
163 fn eq(&self, other: &Ident<K>) -> bool {
164 #[cfg(feature = "strict")]
165 assert_eq!(
166 self.kind, other.kind,
167 "comparing idents of a different generations"
168 );
169 self.id == other.id
170 }
171}
172impl<K> Eq for Ident<K> {}
173impl<K> Ord for Ident<K> {
174 fn cmp(&self, other: &Ident<K>) -> Ordering {
175 #[cfg(feature = "strict")]
176 assert_eq!(
177 self.kind, other.kind,
178 "comparing idents of a different generations"
179 );
180 self.id.cmp(&other.id)
181 }
182}
183impl<K> Hash for Ident<K> {
184 fn hash<H: Hasher>(&self, state: &mut H) {
185 self.id.hash(state);
186 }
187}
188impl<K> PartialOrd for Ident<K> {
189 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
190 Some(self.cmp(other))
191 }
192}
193impl<K> Debug for Ident<K> {
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 let n = ALLOCATOR.with(|a| a.1.name(self.id));
196 write!(f, "{n}")
197 }
198}
199
200#[derive(Derivative)]
201#[derivative(Default(bound = ""), Ord(bound = ""))]
202pub struct DbIdent<K> {
203 id: String,
204 _marker: PhantomData<K>,
205}
206impl<K> PartialOrd for DbIdent<K> {
207 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
208 Some(self.cmp(other))
209 }
210}
211impl<K> DbIdent<K> {
212 pub fn new(v: &str) -> Self {
213 assert_ne!(v, "");
214 Self {
215 id: v.to_owned(),
216 _marker: PhantomData,
217 }
218 }
219 pub fn raw(&self) -> &str {
220 &self.id
221 }
222}
223impl<K> PartialEq for DbIdent<K> {
224 fn eq(&self, other: &Self) -> bool {
225 self.id == other.id
226 }
227}
228impl<K> Eq for DbIdent<K> {}
229impl<K> Hash for DbIdent<K> {
230 fn hash<H: Hasher>(&self, state: &mut H) {
231 self.id.hash(state);
232 }
233}
234impl<K> Debug for DbIdent<K> {
241 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242 write!(f, "{:?}", self.id)
243 }
244}
245impl<K> Clone for DbIdent<K> {
246 fn clone(&self) -> Self {
247 Self {
248 id: self.id.clone(),
249 _marker: PhantomData,
250 }
251 }
252}
253
254impl<T> DbIdent<T> {
255 pub fn unchecked_from<U>(t: DbIdent<U>) -> Self {
256 DbIdent {
257 id: t.id,
258 _marker: PhantomData,
259 }
260 }
261}