Skip to main content

gdck_syntax/
kind.rs

1//! The single kind enum covering both tokens (leaves) and nodes (interior).
2//!
3//! Keeping tokens and nodes in one enum is the usual arrangement for a lossless
4//! tree: it lets a child be described by one `SyntaxKind` regardless of whether
5//! it turned out to be a leaf or a subtree.
6
7/// A syntactic category. Values below [`SyntaxKind::FIRST_NODE`] are tokens.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
9#[repr(u16)]
10#[allow(clippy::upper_case_acronyms)]
11pub enum SyntaxKind {
12    // ---- Trivia -----------------------------------------------------------
13    // Trivia carries no meaning to the grammar but every byte of it lives in
14    // the tree, which is what makes formatting round-trips exact.
15    /// Spaces and tabs. Leading indentation is *also* whitespace; `Indent` and
16    /// `Dedent` are emitted alongside it, not instead of it.
17    Whitespace,
18    /// `# comment`
19    Comment,
20    /// `## documentation comment`
21    DocComment,
22    /// `\n` or `\r\n`
23    Newline,
24    /// A backslash followed by a newline, joining two physical lines.
25    LineContinuation,
26
27    // ---- Synthetic --------------------------------------------------------
28    // Zero-width markers produced by the lexer's indentation tracking.
29    Indent,
30    Dedent,
31    Eof,
32    /// A byte the lexer could not classify.
33    Unknown,
34
35    // ---- Literals ---------------------------------------------------------
36    Int,
37    Float,
38    /// `"..."`, `'...'`, triple-quoted, and `r`-prefixed raw variants.
39    Str,
40    /// `&"name"` — a `StringName` literal.
41    StringName,
42    /// `^"path"` — a `NodePath` literal.
43    NodePath,
44    /// `$Node/Path` or `$"Node/Path"`.
45    GetNode,
46    /// `%UniqueName` or `%"UniqueName"`.
47    UniqueNode,
48    Ident,
49
50    // ---- Keywords ---------------------------------------------------------
51    // Kept contiguous so `is_keyword` can be a range check. `abstract` is
52    // deliberately absent: Godot 4.5 spells it `@abstract`, an annotation, so
53    // treating it as a keyword would break `var abstract = 1`.
54    AndKw,
55    AsKw,
56    AssertKw,
57    AwaitKw,
58    BreakKw,
59    BreakpointKw,
60    ClassKw,
61    ClassNameKw,
62    ConstKw,
63    ContinueKw,
64    ElifKw,
65    ElseKw,
66    EnumKw,
67    ExtendsKw,
68    FalseKw,
69    ForKw,
70    FuncKw,
71    IfKw,
72    InKw,
73    IsKw,
74    MatchKw,
75    NamespaceKw,
76    NotKw,
77    NullKw,
78    OrKw,
79    PassKw,
80    PreloadKw,
81    ReturnKw,
82    SelfKw,
83    SignalKw,
84    StaticKw,
85    SuperKw,
86    TraitKw,
87    TrueKw,
88    VarKw,
89    VoidKw,
90    WhenKw,
91    WhileKw,
92    YieldKw,
93
94    // ---- Punctuation and operators ---------------------------------------
95    Plus,
96    Minus,
97    Star,
98    StarStar,
99    Slash,
100    Percent,
101    Eq,
102    PlusEq,
103    MinusEq,
104    StarEq,
105    StarStarEq,
106    SlashEq,
107    PercentEq,
108    AmpEq,
109    PipeEq,
110    CaretEq,
111    ShlEq,
112    ShrEq,
113    EqEq,
114    Bang,
115    BangEq,
116    Lt,
117    LtEq,
118    Gt,
119    GtEq,
120    Amp,
121    AmpAmp,
122    Pipe,
123    PipePipe,
124    Caret,
125    Tilde,
126    Shl,
127    Shr,
128    Arrow,
129    ColonEq,
130    Colon,
131    Semicolon,
132    Comma,
133    Dot,
134    DotDot,
135    /// `...`, introducing a variadic parameter.
136    Ellipsis,
137    At,
138    Dollar,
139    LParen,
140    RParen,
141    LBracket,
142    RBracket,
143    LBrace,
144    RBrace,
145
146    // ---- Nodes ------------------------------------------------------------
147    /// The root node. Always the outermost node of a parse.
148    SourceFile,
149
150    // Class-level declarations
151    Annotation,
152    ClassNameDecl,
153    ExtendsDecl,
154    SignalDecl,
155    EnumDecl,
156    EnumBody,
157    EnumVariant,
158    ConstDecl,
159    VarDecl,
160    FuncDecl,
161    ClassDecl,
162
163    // Declaration pieces
164    ParamList,
165    Param,
166    ArgList,
167    TypeHint,
168    ReturnType,
169    Initializer,
170    /// The `set`/`get` clause block hanging off a `var`.
171    Accessors,
172    Setter,
173    Getter,
174    Block,
175
176    // Statements
177    ExprStmt,
178    AssignStmt,
179    IfStmt,
180    ElifClause,
181    ElseClause,
182    WhileStmt,
183    ForStmt,
184    MatchStmt,
185    MatchArm,
186    MatchGuard,
187    ReturnStmt,
188    PassStmt,
189    BreakStmt,
190    ContinueStmt,
191    BreakpointStmt,
192    AssertStmt,
193
194    // Expressions
195    BinaryExpr,
196    UnaryExpr,
197    TernaryExpr,
198    CastExpr,
199    AwaitExpr,
200    CallExpr,
201    SubscriptExpr,
202    /// `a.b` — attribute access.
203    AttributeExpr,
204    ParenExpr,
205    ArrayExpr,
206    DictExpr,
207    DictEntry,
208    LambdaExpr,
209    PreloadExpr,
210    /// A bare identifier used as a value.
211    NameRef,
212    Literal,
213
214    /// Wraps tokens the parser could not fit into the grammar. Its presence is
215    /// what keeps the tree lossless in the face of a syntax error.
216    Error,
217}
218
219impl SyntaxKind {
220    /// The first node kind. Everything ordered before this is a token.
221    pub const FIRST_NODE: SyntaxKind = SyntaxKind::SourceFile;
222
223    const FIRST_KEYWORD: SyntaxKind = SyntaxKind::AndKw;
224    const LAST_KEYWORD: SyntaxKind = SyntaxKind::YieldKw;
225
226    /// Whether this token is a reserved word.
227    #[must_use]
228    pub fn is_keyword(self) -> bool {
229        self >= Self::FIRST_KEYWORD && self <= Self::LAST_KEYWORD
230    }
231
232    /// Whether this token is shaped like an identifier.
233    ///
234    /// Keywords count: annotation names and member names may reuse them, so
235    /// `@tool` and `x.get` have to be accepted.
236    #[must_use]
237    pub fn is_ident_like(self) -> bool {
238        self == Self::Ident || self.is_keyword()
239    }
240
241    /// Whether this token may stand where a *name* is expected.
242    ///
243    /// Two keywords may. `match` because `String.match()` is on the engine's
244    /// API and was there first, and `when` because it arrived as a `match`
245    /// guard long after code was already using it as a name. Godot spells this
246    /// out in `Token::is_identifier` in `gdscript_tokenizer.cpp`, whose comment
247    /// on `WHEN` reads "New keyword, avoid breaking existing code".
248    ///
249    /// That list also holds `PI`, `TAU`, `INF` and `NAN`, which are keywords to
250    /// Godot's tokenizer and plain identifiers here, so they need no exception.
251    ///
252    /// A keyword accepted here is recorded as an [`Ident`](Self::Ident), since
253    /// that is what it is being used as; the guard position in a `match` arm
254    /// keeps reading it as the keyword.
255    #[must_use]
256    pub fn is_name(self) -> bool {
257        matches!(self, Self::Ident | Self::MatchKw | Self::WhenKw)
258    }
259
260    /// Whether this kind describes a leaf produced by the lexer.
261    #[must_use]
262    pub fn is_token(self) -> bool {
263        self < Self::FIRST_NODE
264    }
265
266    /// Whether this kind describes an interior node produced by the parser.
267    #[must_use]
268    pub fn is_node(self) -> bool {
269        !self.is_token()
270    }
271
272    /// Trivia is skipped by the parser but retained in the tree.
273    ///
274    /// `Indent` and `Dedent` are deliberately *not* trivia: they are structural,
275    /// and the parser consumes them to delimit blocks.
276    #[must_use]
277    pub fn is_trivia(self) -> bool {
278        matches!(
279            self,
280            Self::Whitespace
281                | Self::Comment
282                | Self::DocComment
283                | Self::Newline
284                | Self::LineContinuation
285        )
286    }
287
288    /// Whether this token is a comment of either flavour.
289    #[must_use]
290    pub fn is_comment(self) -> bool {
291        matches!(self, Self::Comment | Self::DocComment)
292    }
293
294    /// Whether this token may begin a type annotation or a value expression.
295    #[must_use]
296    pub fn is_literal(self) -> bool {
297        matches!(
298            self,
299            Self::Int
300                | Self::Float
301                | Self::Str
302                | Self::StringName
303                | Self::NodePath
304                | Self::TrueKw
305                | Self::FalseKw
306                | Self::NullKw
307        )
308    }
309
310    /// Map an identifier-shaped string to its keyword kind, if it is one.
311    #[must_use]
312    pub fn from_keyword(text: &str) -> Option<Self> {
313        Some(match text {
314            "and" => Self::AndKw,
315            "as" => Self::AsKw,
316            "assert" => Self::AssertKw,
317            "await" => Self::AwaitKw,
318            "break" => Self::BreakKw,
319            "breakpoint" => Self::BreakpointKw,
320            "class" => Self::ClassKw,
321            "class_name" => Self::ClassNameKw,
322            "const" => Self::ConstKw,
323            "continue" => Self::ContinueKw,
324            "elif" => Self::ElifKw,
325            "else" => Self::ElseKw,
326            "enum" => Self::EnumKw,
327            "extends" => Self::ExtendsKw,
328            "false" => Self::FalseKw,
329            "for" => Self::ForKw,
330            "func" => Self::FuncKw,
331            "if" => Self::IfKw,
332            "in" => Self::InKw,
333            "is" => Self::IsKw,
334            "match" => Self::MatchKw,
335            "namespace" => Self::NamespaceKw,
336            "not" => Self::NotKw,
337            "null" => Self::NullKw,
338            "or" => Self::OrKw,
339            "pass" => Self::PassKw,
340            "preload" => Self::PreloadKw,
341            "return" => Self::ReturnKw,
342            "self" => Self::SelfKw,
343            "signal" => Self::SignalKw,
344            "static" => Self::StaticKw,
345            "super" => Self::SuperKw,
346            "trait" => Self::TraitKw,
347            "true" => Self::TrueKw,
348            "var" => Self::VarKw,
349            "void" => Self::VoidKw,
350            "when" => Self::WhenKw,
351            "while" => Self::WhileKw,
352            "yield" => Self::YieldKw,
353            _ => return None,
354        })
355    }
356}
357
358#[cfg(test)]
359mod tests {
360    use super::*;
361
362    #[test]
363    fn token_and_node_partition() {
364        assert!(SyntaxKind::Whitespace.is_token());
365        assert!(SyntaxKind::RBrace.is_token());
366        assert!(SyntaxKind::SourceFile.is_node());
367        assert!(SyntaxKind::Error.is_node());
368        assert!(!SyntaxKind::SourceFile.is_token());
369    }
370
371    #[test]
372    fn indent_is_structural_not_trivia() {
373        assert!(SyntaxKind::Whitespace.is_trivia());
374        assert!(SyntaxKind::Comment.is_trivia());
375        assert!(!SyntaxKind::Indent.is_trivia());
376        assert!(!SyntaxKind::Dedent.is_trivia());
377    }
378
379    #[test]
380    fn keywords_round_trip() {
381        assert_eq!(SyntaxKind::from_keyword("func"), Some(SyntaxKind::FuncKw));
382        assert_eq!(
383            SyntaxKind::from_keyword("class_name"),
384            Some(SyntaxKind::ClassNameKw)
385        );
386        assert_eq!(SyntaxKind::from_keyword("classname"), None);
387        assert_eq!(SyntaxKind::from_keyword("Func"), None);
388        assert_eq!(SyntaxKind::from_keyword("position"), None);
389    }
390}