Skip to main content

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}