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}