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 kind describes a leaf produced by the lexer.
242    #[must_use]
243    pub fn is_token(self) -> bool {
244        self < Self::FIRST_NODE
245    }
246
247    /// Whether this kind describes an interior node produced by the parser.
248    #[must_use]
249    pub fn is_node(self) -> bool {
250        !self.is_token()
251    }
252
253    /// Trivia is skipped by the parser but retained in the tree.
254    ///
255    /// `Indent` and `Dedent` are deliberately *not* trivia: they are structural,
256    /// and the parser consumes them to delimit blocks.
257    #[must_use]
258    pub fn is_trivia(self) -> bool {
259        matches!(
260            self,
261            Self::Whitespace
262                | Self::Comment
263                | Self::DocComment
264                | Self::Newline
265                | Self::LineContinuation
266        )
267    }
268
269    /// Whether this token is a comment of either flavour.
270    #[must_use]
271    pub fn is_comment(self) -> bool {
272        matches!(self, Self::Comment | Self::DocComment)
273    }
274
275    /// Whether this token may begin a type annotation or a value expression.
276    #[must_use]
277    pub fn is_literal(self) -> bool {
278        matches!(
279            self,
280            Self::Int
281                | Self::Float
282                | Self::Str
283                | Self::StringName
284                | Self::NodePath
285                | Self::TrueKw
286                | Self::FalseKw
287                | Self::NullKw
288        )
289    }
290
291    /// Map an identifier-shaped string to its keyword kind, if it is one.
292    #[must_use]
293    pub fn from_keyword(text: &str) -> Option<Self> {
294        Some(match text {
295            "and" => Self::AndKw,
296            "as" => Self::AsKw,
297            "assert" => Self::AssertKw,
298            "await" => Self::AwaitKw,
299            "break" => Self::BreakKw,
300            "breakpoint" => Self::BreakpointKw,
301            "class" => Self::ClassKw,
302            "class_name" => Self::ClassNameKw,
303            "const" => Self::ConstKw,
304            "continue" => Self::ContinueKw,
305            "elif" => Self::ElifKw,
306            "else" => Self::ElseKw,
307            "enum" => Self::EnumKw,
308            "extends" => Self::ExtendsKw,
309            "false" => Self::FalseKw,
310            "for" => Self::ForKw,
311            "func" => Self::FuncKw,
312            "if" => Self::IfKw,
313            "in" => Self::InKw,
314            "is" => Self::IsKw,
315            "match" => Self::MatchKw,
316            "namespace" => Self::NamespaceKw,
317            "not" => Self::NotKw,
318            "null" => Self::NullKw,
319            "or" => Self::OrKw,
320            "pass" => Self::PassKw,
321            "preload" => Self::PreloadKw,
322            "return" => Self::ReturnKw,
323            "self" => Self::SelfKw,
324            "signal" => Self::SignalKw,
325            "static" => Self::StaticKw,
326            "super" => Self::SuperKw,
327            "trait" => Self::TraitKw,
328            "true" => Self::TrueKw,
329            "var" => Self::VarKw,
330            "void" => Self::VoidKw,
331            "when" => Self::WhenKw,
332            "while" => Self::WhileKw,
333            "yield" => Self::YieldKw,
334            _ => return None,
335        })
336    }
337}
338
339#[cfg(test)]
340mod tests {
341    use super::*;
342
343    #[test]
344    fn token_and_node_partition() {
345        assert!(SyntaxKind::Whitespace.is_token());
346        assert!(SyntaxKind::RBrace.is_token());
347        assert!(SyntaxKind::SourceFile.is_node());
348        assert!(SyntaxKind::Error.is_node());
349        assert!(!SyntaxKind::SourceFile.is_token());
350    }
351
352    #[test]
353    fn indent_is_structural_not_trivia() {
354        assert!(SyntaxKind::Whitespace.is_trivia());
355        assert!(SyntaxKind::Comment.is_trivia());
356        assert!(!SyntaxKind::Indent.is_trivia());
357        assert!(!SyntaxKind::Dedent.is_trivia());
358    }
359
360    #[test]
361    fn keywords_round_trip() {
362        assert_eq!(SyntaxKind::from_keyword("func"), Some(SyntaxKind::FuncKw));
363        assert_eq!(
364            SyntaxKind::from_keyword("class_name"),
365            Some(SyntaxKind::ClassNameKw)
366        );
367        assert_eq!(SyntaxKind::from_keyword("classname"), None);
368        assert_eq!(SyntaxKind::from_keyword("Func"), None);
369        assert_eq!(SyntaxKind::from_keyword("position"), None);
370    }
371}