Skip to main content

token_lang/
kind.rs

1//! The [`TokenKind`] trait: the language-agnostic classification a parser reads.
2
3use intern_lang::Symbol;
4
5/// The classification a token kind exposes so that generic, language-agnostic
6/// code can reason about a token stream without knowing the concrete kind.
7///
8/// A language defines its own kind type — an `enum` of keywords, punctuation,
9/// literals, an end-of-input marker, and so on — and implements `TokenKind` for
10/// it. token-lang stays language-agnostic by owning only this seam: a parser's
11/// token cursor, a trivia filter, or a pretty-printer written against `TokenKind`
12/// works for *any* language's kind, while [`Token<K>`](crate::Token) carries the
13/// span.
14///
15/// Every method has a default, so a kind that has no trivia, no end marker, and
16/// carries no interned text satisfies the trait with an empty `impl` block.
17/// Override only the queries that apply to the language.
18///
19/// # Why a trait, not a fixed enum
20///
21/// The set of keywords and operators differs in every language, so token-lang
22/// cannot enumerate them once and freeze them. What *is* universal is the handful
23/// of questions a parser asks of any token regardless of language: should I skip
24/// this as trivia, have I reached the end, and what interned text (if any) does it
25/// carry. Those three questions are the whole trait.
26///
27/// # Examples
28///
29/// A small kind for a calculator language. Whitespace is trivia; identifiers carry
30/// an interned [`Symbol`]; the rest are plain markers.
31///
32/// ```
33/// use intern_lang::Interner;
34/// use token_lang::{Symbol, TokenKind};
35///
36/// #[derive(Clone, Copy, Debug, PartialEq, Eq)]
37/// enum Kind {
38///     Ident(Symbol),
39///     Plus,
40///     Whitespace,
41///     Eof,
42/// }
43///
44/// impl TokenKind for Kind {
45///     fn is_trivia(&self) -> bool {
46///         matches!(self, Kind::Whitespace)
47///     }
48///     fn is_eof(&self) -> bool {
49///         matches!(self, Kind::Eof)
50///     }
51///     fn symbol(&self) -> Option<Symbol> {
52///         match self {
53///             Kind::Ident(sym) => Some(*sym),
54///             _ => None,
55///         }
56///     }
57/// }
58///
59/// let mut interner = Interner::new();
60/// let total = Kind::Ident(interner.intern("total"));
61///
62/// assert!(Kind::Whitespace.is_trivia());
63/// assert!(Kind::Eof.is_eof());
64/// assert_eq!(total.symbol().and_then(|s| interner.resolve(s)), Some("total"));
65/// assert_eq!(Kind::Plus.symbol(), None);
66/// ```
67///
68/// A kind with no trivia or interned text needs no method bodies at all:
69///
70/// ```
71/// use token_lang::TokenKind;
72///
73/// #[derive(Clone, Copy, Debug, PartialEq, Eq)]
74/// enum Op {
75///     Add,
76///     Sub,
77/// }
78///
79/// impl TokenKind for Op {}
80///
81/// assert!(!Op::Add.is_trivia());
82/// assert!(!Op::Add.is_eof());
83/// assert_eq!(Op::Add.symbol(), None);
84/// ```
85pub trait TokenKind {
86    /// Whether this kind is *trivia*: whitespace, comments, and anything else a
87    /// parser skips because it is not part of the grammar.
88    ///
89    /// A lexer that preserves trivia (for a formatter or a lossless syntax tree)
90    /// emits these kinds inline; a parser filters them out with this query.
91    /// Defaults to `false`, so a language that discards trivia in the lexer need
92    /// not override it.
93    #[inline]
94    fn is_trivia(&self) -> bool {
95        false
96    }
97
98    /// Whether this kind is the end-of-input marker the lexer emits once the
99    /// source is exhausted.
100    ///
101    /// A parser uses this to detect the end of the stream without tracking a
102    /// separate length, and to stop before reading past it. Defaults to `false`.
103    #[inline]
104    fn is_eof(&self) -> bool {
105        false
106    }
107
108    /// The interned lexeme this token carries, if any.
109    ///
110    /// Returns the [`Symbol`] for a kind whose text was interned — an identifier,
111    /// a keyword, or an interned literal — and `None` for a kind with no textual
112    /// payload, which is most punctuation, structural tokens, and the end marker.
113    /// It lets generic code read a token's name (to build a path, look up a
114    /// keyword, or render the source back) without matching on the concrete kind.
115    ///
116    /// Defaults to `None`, so a language that stores no interned text — or none on
117    /// this kind — need not override it.
118    #[inline]
119    fn symbol(&self) -> Option<Symbol> {
120        None
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    // Reconstructing a `Symbol` from a known id is `Option`-returning; unwrapping
127    // it in a test where the id is a literal is the clearest form.
128    #![allow(clippy::unwrap_used)]
129
130    use super::*;
131
132    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
133    enum Kind {
134        Ident(Symbol),
135        Plus,
136        Whitespace,
137        Eof,
138    }
139
140    impl TokenKind for Kind {
141        fn is_trivia(&self) -> bool {
142            matches!(self, Kind::Whitespace)
143        }
144        fn is_eof(&self) -> bool {
145            matches!(self, Kind::Eof)
146        }
147        fn symbol(&self) -> Option<Symbol> {
148            match self {
149                Kind::Ident(sym) => Some(*sym),
150                _ => None,
151            }
152        }
153    }
154
155    // A kind that overrides nothing exercises every default method.
156    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
157    struct Bare;
158    impl TokenKind for Bare {}
159
160    #[test]
161    fn test_is_trivia_true_only_for_whitespace() {
162        assert!(Kind::Whitespace.is_trivia());
163        assert!(!Kind::Plus.is_trivia());
164        assert!(!Kind::Eof.is_trivia());
165    }
166
167    #[test]
168    fn test_is_eof_true_only_for_eof() {
169        assert!(Kind::Eof.is_eof());
170        assert!(!Kind::Plus.is_eof());
171        assert!(!Kind::Whitespace.is_eof());
172    }
173
174    #[test]
175    fn test_symbol_present_only_for_ident() {
176        let sym = Symbol::from_u32(7).unwrap();
177        assert_eq!(Kind::Ident(sym).symbol(), Some(sym));
178        assert_eq!(Kind::Plus.symbol(), None);
179    }
180
181    #[test]
182    fn test_defaults_are_all_false_and_none() {
183        assert!(!Bare.is_trivia());
184        assert!(!Bare.is_eof());
185        assert_eq!(Bare.symbol(), None);
186    }
187}