lang_forge/kind.rs
1//! [`Kind`]: the one kind type every forged language uses for its tokens and
2//! nodes.
3
4use core::cmp::Ordering;
5use core::fmt;
6use core::hash::{Hash, Hasher};
7
8use syntax_lang::TokenKind;
9
10/// The kind of a token or node in a forged language's syntax tree.
11///
12/// A schematic declares its vocabulary as text — rule names, keywords,
13/// punctuation — so a forged language cannot have a Rust `enum` of its own.
14/// `Kind` stands in for one: a small `Copy` value, assigned when the language is
15/// forged, that compares as cheaply as an enum discriminant. Tokens and nodes
16/// share the type, the model [`syntax_lang`] is built on.
17///
18/// Kinds are looked up by name with [`Language::kind`](crate::Language::kind)
19/// and named with [`Language::kind_name`](crate::Language::kind_name). Look a
20/// kind up once, keep it, and compare against it while walking trees: the
21/// comparison is a single integer compare.
22///
23/// | Name | Kind of |
24/// |---|---|
25/// | a rule name, such as `let_stmt` | the node the rule builds |
26/// | a literal's text, such as `let` or `+=` | the keyword or symbol token |
27/// | a Pratt level's `node`, or `binary`, `prefix`, `postfix` | an operator node |
28/// | `IDENT`, `NUMBER`, `STRING`, `NEWLINE` | the built-in token classes |
29/// | `WHITESPACE`, `COMMENT` | trivia tokens |
30/// | `UNKNOWN` | a character the lexer did not recognize (trivia) |
31/// | `ERROR` | a node wrapping tokens the parser skipped |
32///
33/// A format-2 sketch adds its own token classes, string classes and the kinds
34/// generated for them, and the format-2 built-ins (`DOC_COMMENT`, `SHEBANG`,
35/// …); `docs/API.md` has the full list.
36///
37/// A kind belongs to the language that produced it. Comparing kinds from two
38/// different languages is meaningless, and naming one language's kind with
39/// another language returns the wrong name or `"<unknown>"`.
40///
41/// # Index
42///
43/// Every kind has a dense [`index`](Kind::index), its position in the
44/// language's kind table, and [`Language::kind_at`](crate::Language::kind_at)
45/// turns an index back into the kind. Adapters that need a plain integer (an
46/// incremental reparser's constants, a tree-sitter export, a language server)
47/// use the pair. For a format-2 sketch the numbering is the deterministic one
48/// of LSF2 §5.4; for a format-1 schematic it is an implementation detail that
49/// may change between releases.
50///
51/// # Field labels
52///
53/// A format-2 grammar can label what a rule matches (`cond:expr`). The label
54/// of the edge from a node to one of its children is carried by the child's
55/// kind: [`label`](Kind::label) returns it. Labels never take part in
56/// comparison, ordering, or hashing — a labelled `expr` is still equal to
57/// `lang.kind("expr")` — so code that only matches kinds is unaffected. See
58/// [`Language::field_label`](crate::Language::field_label).
59///
60/// # Trivia
61///
62/// [`TokenKind::is_trivia`] answers without the language: whitespace, comments,
63/// unrecognized characters, and — unless the schematic sets `newlines = true` —
64/// line breaks are trivia. Trivia is kept in the tree, so it stays lossless,
65/// but the parser never sees it.
66///
67/// # Examples
68///
69/// ```
70/// use lang_forge::Language;
71/// use lang_forge::syntax_lang::TokenKind;
72///
73/// let lang = Language::from_lsf(
74/// r#"
75/// [language]
76/// name = "sum"
77///
78/// [rules]
79/// sum = "NUMBER ('+' NUMBER)*"
80/// "#,
81/// )?;
82///
83/// let plus = lang.kind("+").expect("the grammar uses '+'");
84/// let space = lang.kind("WHITESPACE").expect("built in");
85/// assert_eq!(lang.kind_name(plus), "+");
86/// assert!(space.is_trivia());
87/// assert!(!plus.is_trivia());
88/// assert_eq!(lang.kind_at(plus.index()), Some(plus));
89///
90/// let parse = lang.parse("1 + 2");
91/// let pluses = parse.tree().tokens().filter(|t| *t.kind() == plus).count();
92/// assert_eq!(pluses, 1);
93/// # Ok::<(), lang_forge::Error>(())
94/// ```
95#[derive(Clone, Copy)]
96pub struct Kind(u32);
97
98/// The bit that marks a kind as trivia. Kept inside the value so that
99/// [`TokenKind::is_trivia`] needs no access to the language.
100const TRIVIA: u32 = 0x8000;
101
102/// The bits that hold the kind's index.
103const INDEX: u32 = 0x7FFF;
104
105/// The bits that make up a kind's identity: index and trivia flag. The field
106/// label above them is carried along but never compared.
107const IDENTITY: u32 = 0xFFFF;
108
109/// The largest number of kinds a language can have: indexes use the 15 bits
110/// below the trivia flag.
111pub(crate) const MAX_KINDS: usize = TRIVIA as usize;
112
113/// The largest number of field labels a language can have: a label is stored
114/// as its id plus one in the top 16 bits, zero meaning "no label".
115pub(crate) const MAX_LABELS: usize = 0xFFFF;
116
117impl Kind {
118 /// A kind with the given index, flagged as trivia or not.
119 #[inline]
120 pub(crate) const fn new(index: u16, trivia: bool) -> Self {
121 let index = index as u32 & INDEX;
122 Self(if trivia { index | TRIVIA } else { index })
123 }
124
125 /// The raw bits, label included, for the language image.
126 #[inline]
127 pub(crate) const fn bits(self) -> u32 {
128 self.0
129 }
130
131 /// A kind from raw bits read back from a language image.
132 #[inline]
133 pub(crate) const fn from_bits(bits: u32) -> Self {
134 Self(bits)
135 }
136
137 /// The kind's position in its language's kind table.
138 ///
139 /// Indexes are dense, from 0 to one less than
140 /// [`Language::kind_count`](crate::Language::kind_count);
141 /// [`Language::kind_at`](crate::Language::kind_at) is the inverse.
142 ///
143 /// # Examples
144 ///
145 /// ```
146 /// use lang_forge::Language;
147 ///
148 /// let lang = Language::from_lsf("[language]\nname = \"x\"\n[rules]\nitem = \"'go' NUMBER\"\n")?;
149 /// let go = lang.kind("go").expect("a keyword");
150 /// assert!(usize::from(go.index()) < lang.kind_count());
151 /// assert_eq!(lang.kind_at(go.index()), Some(go));
152 /// # Ok::<(), lang_forge::Error>(())
153 /// ```
154 #[inline]
155 #[must_use]
156 pub const fn index(self) -> u16 {
157 (self.0 & INDEX) as u16
158 }
159
160 /// The kind's index as a `usize`, for indexing tables.
161 #[inline]
162 pub(crate) const fn slot(self) -> usize {
163 (self.0 & INDEX) as usize
164 }
165
166 /// The field label of the tree edge this kind sits on, if the grammar
167 /// labelled it.
168 ///
169 /// Only kinds read from a tree carry labels: a token or node a format-2
170 /// rule matched under `name:` gets that label's id. Kinds returned by
171 /// [`Language::kind`](crate::Language::kind) have none. Name a label with
172 /// [`Language::label_name`](crate::Language::label_name).
173 ///
174 /// # Examples
175 ///
176 /// ```
177 /// use lang_forge::Language;
178 ///
179 /// let lang = Language::from_lsf(
180 /// "[sketch]\nformat = 2\n[language]\nname = \"pair\"\nversion = \"1.0.0\"\n\
181 /// [rules]\npair = \"key:IDENT '=' value:NUMBER\"\n",
182 /// )?;
183 /// let parse = lang.parse("x = 1");
184 /// let labels: Vec<Option<&str>> = parse
185 /// .tree()
186 /// .child_tokens()
187 /// .map(|t| t.kind().label().map(|l| lang.label_name(l).unwrap_or("?")))
188 /// .collect();
189 /// assert_eq!(labels, [Some("key"), None, None, None, Some("value")]);
190 /// # Ok::<(), lang_forge::Error>(())
191 /// ```
192 #[inline]
193 #[must_use]
194 pub const fn label(self) -> Option<u16> {
195 match self.0 >> 16 {
196 0 => None,
197 n => Some((n - 1) as u16),
198 }
199 }
200
201 /// The same kind without a field label.
202 ///
203 /// # Examples
204 ///
205 /// ```
206 /// use lang_forge::Language;
207 ///
208 /// let lang = Language::from_lsf("[language]\nname = \"x\"\n[rules]\nitem = \"NUMBER\"\n")?;
209 /// let number = lang.kind("NUMBER").expect("built in");
210 /// assert_eq!(number.unlabelled().label(), None);
211 /// assert_eq!(number.unlabelled(), number);
212 /// # Ok::<(), lang_forge::Error>(())
213 /// ```
214 #[inline]
215 #[must_use]
216 pub const fn unlabelled(self) -> Self {
217 Self(self.0 & IDENTITY)
218 }
219
220 /// The same kind carrying `label`.
221 #[inline]
222 pub(crate) const fn with_label(self, label: Option<u16>) -> Self {
223 match label {
224 None => Self(self.0 & IDENTITY),
225 Some(l) => Self((self.0 & IDENTITY) | ((l as u32 + 1) << 16)),
226 }
227 }
228}
229
230impl PartialEq for Kind {
231 #[inline]
232 fn eq(&self, other: &Self) -> bool {
233 self.0 & IDENTITY == other.0 & IDENTITY
234 }
235}
236
237impl Eq for Kind {}
238
239impl PartialOrd for Kind {
240 #[inline]
241 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
242 Some(self.cmp(other))
243 }
244}
245
246impl Ord for Kind {
247 #[inline]
248 fn cmp(&self, other: &Self) -> Ordering {
249 (self.0 & IDENTITY).cmp(&(other.0 & IDENTITY))
250 }
251}
252
253impl Hash for Kind {
254 #[inline]
255 fn hash<H: Hasher>(&self, state: &mut H) {
256 (self.0 & IDENTITY).hash(state);
257 }
258}
259
260impl TokenKind for Kind {
261 #[inline]
262 fn is_trivia(&self) -> bool {
263 self.0 & TRIVIA != 0
264 }
265}
266
267impl fmt::Debug for Kind {
268 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
269 match self.label() {
270 None => write!(f, "Kind({})", self.index()),
271 Some(label) => write!(f, "Kind({}, label {label})", self.index()),
272 }
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279
280 #[test]
281 fn test_kind_trivia_flag_is_independent_of_index() {
282 let plain = Kind::new(7, false);
283 let trivia = Kind::new(7, true);
284 assert_eq!(plain.index(), 7);
285 assert_eq!(trivia.index(), 7);
286 assert!(!plain.is_trivia());
287 assert!(trivia.is_trivia());
288 assert_ne!(plain, trivia);
289 }
290
291 #[test]
292 fn test_kind_debug_shows_index_only() {
293 assert_eq!(alloc::format!("{:?}", Kind::new(3, true)), "Kind(3)");
294 assert_eq!(
295 alloc::format!("{:?}", Kind::new(3, false).with_label(Some(2))),
296 "Kind(3, label 2)"
297 );
298 }
299
300 #[test]
301 fn test_kind_max_index_fits_below_flag() {
302 let last = Kind::new((MAX_KINDS - 1) as u16, false);
303 assert_eq!(last.slot(), MAX_KINDS - 1);
304 assert!(!last.is_trivia());
305 }
306
307 #[test]
308 fn test_kind_labels_do_not_affect_identity() {
309 use core::hash::BuildHasher;
310 let k = Kind::new(9, true);
311 let labelled = k.with_label(Some(0));
312 let other = k.with_label(Some(MAX_LABELS as u16 - 1));
313 assert_eq!(labelled.label(), Some(0));
314 assert_eq!(other.label(), Some(MAX_LABELS as u16 - 1));
315 assert_eq!(k.label(), None);
316 assert_eq!(k, labelled);
317 assert_eq!(labelled, other);
318 assert_eq!(labelled.cmp(&k), Ordering::Equal);
319 assert!(labelled.is_trivia());
320 assert_eq!(labelled.index(), 9);
321 assert_eq!(labelled.unlabelled().label(), None);
322 assert_eq!(labelled.with_label(None).label(), None);
323 let state = std::hash::RandomState::new();
324 assert_eq!(state.hash_one(k), state.hash_one(other));
325 assert_eq!(Kind::from_bits(other.bits()).label(), other.label());
326 }
327}