Skip to main content

gdck_syntax/
lib.rs

1//! Lossless lexing and parsing for [GDScript](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/).
2//!
3//! The tree this crate produces keeps every byte of the input, including
4//! whitespace, comments and blank lines. That is a deliberate constraint rather
5//! than an implementation detail: a formatter that rewrites one declaration has
6//! to leave the comments around it exactly where they were, and a linter that
7//! reports a problem has to point at a real byte offset.
8//!
9//! Parsing never fails. Malformed input produces [`SyntaxKind::Error`] nodes
10//! and diagnostics on [`SyntaxTree::errors`], so tools can report several
11//! problems per run and editors can work with a half-typed buffer.
12//!
13//! # Example
14//!
15//! ```
16//! let tree = gdck_syntax::parse("func _ready() -> void:\n\tpass\n");
17//! assert!(!tree.has_errors());
18//! // The source is always recoverable from the tree.
19//! assert_eq!(tree.text(), "func _ready() -> void:\n\tpass\n");
20//! ```
21
22mod error;
23mod kind;
24mod lexer;
25mod parser;
26mod text;
27mod tree;
28
29pub use error::SyntaxError;
30pub use kind::SyntaxKind;
31pub use lexer::{LexResult, Token, tokenize};
32pub use parser::parse;
33pub use text::{LineCol, LineIndex, TextRange};
34pub use tree::{Checkpoint, Descendants, Element, NodeId, SyntaxNode, SyntaxTree, TreeBuilder};
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39
40    /// The invariant every downstream tool relies on.
41    fn assert_round_trips(source: &str) {
42        let tree = parse(source);
43        assert_eq!(tree.text(), source, "tree must reproduce its input exactly");
44    }
45
46    fn assert_parses(source: &str) -> SyntaxTree {
47        let tree = parse(source);
48        assert!(
49            !tree.has_errors(),
50            "expected a clean parse of:\n{source}\ngot: {:?}",
51            tree.errors()
52        );
53        assert_eq!(tree.text(), source);
54        tree
55    }
56
57    /// Collect the kinds of every node in the tree, for structural assertions.
58    fn node_kinds(tree: &SyntaxTree) -> Vec<SyntaxKind> {
59        tree.root().descendants().map(SyntaxNode::kind).collect()
60    }
61
62    #[test]
63    fn parses_an_empty_file() {
64        let tree = assert_parses("");
65        assert_eq!(tree.root().kind(), SyntaxKind::SourceFile);
66    }
67
68    #[test]
69    fn parses_class_level_declarations() {
70        let tree = assert_parses(
71            "@tool\nclass_name Player\nextends CharacterBody2D\n\n## The player.\nsignal died\nsignal hit(damage: int, source: Node)\n\nenum State { IDLE, WALKING = 2, }\n\nconst MAX_SPEED := 300.0\nconst GRAVITY: float = 9.8\n\n@export var health: int = 100\n@export_range(0, 10) var lives := 3\nvar _private_state: State = State.IDLE\n@onready var sprite: Sprite2D = $Sprite2D\n",
72        );
73        let kinds = node_kinds(&tree);
74        assert!(kinds.contains(&SyntaxKind::ClassNameDecl));
75        assert!(kinds.contains(&SyntaxKind::ExtendsDecl));
76        assert!(kinds.contains(&SyntaxKind::SignalDecl));
77        assert!(kinds.contains(&SyntaxKind::EnumDecl));
78        assert!(kinds.contains(&SyntaxKind::ConstDecl));
79        assert!(kinds.contains(&SyntaxKind::VarDecl));
80        assert!(kinds.contains(&SyntaxKind::Annotation));
81    }
82
83    #[test]
84    fn annotations_attach_to_the_declaration_they_modify() {
85        let tree = assert_parses("@export var health := 100\n");
86        let var_decl = tree
87            .root()
88            .descendants()
89            .find(|node| node.kind() == SyntaxKind::VarDecl)
90            .expect("a var declaration");
91        // `@export` must live inside the VarDecl, not beside it, so moving the
92        // declaration moves its annotations too.
93        assert!(
94            var_decl
95                .child_nodes()
96                .any(|child| child.kind() == SyntaxKind::Annotation),
97            "annotation should be a child of the declaration"
98        );
99    }
100
101    #[test]
102    fn file_level_annotations_stay_separate() {
103        let tree = assert_parses("@tool\nclass_name Foo\n");
104        let class_name = tree
105            .root()
106            .descendants()
107            .find(|node| node.kind() == SyntaxKind::ClassNameDecl)
108            .expect("a class_name declaration");
109        // `@tool` describes the file, not the class_name, so it must not be
110        // swallowed by it.
111        assert!(
112            !class_name
113                .child_nodes()
114                .any(|child| child.kind() == SyntaxKind::Annotation)
115        );
116    }
117
118    #[test]
119    fn parses_functions_and_control_flow() {
120        assert_parses(
121            "func _physics_process(delta: float) -> void:\n\tif health <= 0:\n\t\tdied.emit()\n\telif health < 20:\n\t\tblink()\n\telse:\n\t\tpass\n\n\tfor i in range(10):\n\t\tprint(i)\n\n\twhile true:\n\t\tbreak\n\n\tmatch state:\n\t\tState.IDLE:\n\t\t\tpass\n\t\tvar other when other > 2:\n\t\t\tpass\n\t\t_:\n\t\t\treturn\n",
122        );
123    }
124
125    #[test]
126    fn parses_static_and_abstract_members() {
127        assert_parses("static var count := 0\n\nstatic func reset() -> void:\n\tcount = 0\n");
128    }
129
130    #[test]
131    fn parses_inner_classes() {
132        let tree = assert_parses(
133            "class Inventory extends RefCounted:\n\tvar items: Array[String] = []\n\n\tfunc add(item: String) -> void:\n\t\titems.append(item)\n",
134        );
135        assert!(node_kinds(&tree).contains(&SyntaxKind::ClassDecl));
136    }
137
138    #[test]
139    fn parses_property_accessors() {
140        let tree = assert_parses(
141            "var health := 100:\n\tset(value):\n\t\thealth = maxi(value, 0)\n\tget:\n\t\treturn health\n",
142        );
143        let kinds = node_kinds(&tree);
144        assert!(kinds.contains(&SyntaxKind::Setter));
145        assert!(kinds.contains(&SyntaxKind::Getter));
146    }
147
148    #[test]
149    fn parses_expressions() {
150        assert_parses(
151            "func f():\n\tvar a = 1 + 2 * 3 - -4\n\tvar b = a > 1 and not a < 0 or false\n\tvar c = [1, 2, {\"key\": \"value\", other = 2}]\n\tvar d = c[0].method(1, 2).field\n\tvar e = \"yes\" if a else \"no\"\n\tvar g = a as float\n\tvar h = a is int\n\tvar i = await something()\n\tvar j = preload(\"res://x.tscn\")\n\tvar k = func(x): return x * 2\n\tvar m = 2 ** 3 ** 2\n",
152        );
153    }
154
155    #[test]
156    fn parses_assignment_operators() {
157        let tree = assert_parses(
158            "func f():\n\tx = 1\n\tx += 1\n\tx **= 2\n\tx >>= 1\n\tx[0] = 2\n\tx.y.z = 3\n",
159        );
160        let assignments = node_kinds(&tree)
161            .iter()
162            .filter(|kind| **kind == SyntaxKind::AssignStmt)
163            .count();
164        assert_eq!(assignments, 6);
165    }
166
167    #[test]
168    fn parses_inline_bodies_and_semicolons() {
169        assert_parses("func f():\n\tif true: pass\n\tvar a = 1; var b = 2\n");
170    }
171
172    #[test]
173    fn parses_wrapped_lines() {
174        assert_parses(
175            "func f():\n\tvar a = [\n\t\t1,\n\t\t2,\n\t]\n\tif a \\\n\t\t\tand true:\n\t\tpass\n\tvar b = (\n\t\t1\n\t\t+ 2\n\t)\n",
176        );
177    }
178
179    #[test]
180    fn round_trips_regardless_of_errors() {
181        // Losslessness must survive syntax errors, or a formatter could destroy
182        // a file it merely failed to understand.
183        for source in [
184            "func f(:\n\tpass\n",
185            "var = 5\n",
186            "class_name\n",
187            "func f():\n\treturn ]\n",
188            "@\n",
189            "if if if\n",
190            "enum { \n",
191            "var s = \"unterminated\n",
192            "\t\tweird_indent()\n",
193            "% ^ &\n",
194        ] {
195            assert_round_trips(source);
196        }
197    }
198
199    #[test]
200    fn reports_errors_without_giving_up() {
201        let tree = parse("func f(:\n\tpass\n\nfunc g():\n\tpass\n");
202        assert!(tree.has_errors());
203        // Recovery must find the second function despite the broken first one.
204        let functions = node_kinds(&tree)
205            .iter()
206            .filter(|kind| **kind == SyntaxKind::FuncDecl)
207            .count();
208        assert_eq!(functions, 2, "parser should recover and see both functions");
209    }
210
211    #[test]
212    fn preserves_comments_in_the_tree() {
213        let source = "# leading\nfunc f():  # trailing\n\t# inner\n\tpass\n";
214        let tree = assert_parses(source);
215        let comments: Vec<_> = tree
216            .root()
217            .descendants()
218            .flat_map(SyntaxNode::child_tokens)
219            .filter(|token| token.kind.is_comment())
220            .map(|token| token.text(source))
221            .collect();
222        assert_eq!(comments, vec!["# leading", "# trailing", "# inner"]);
223    }
224
225    #[test]
226    fn leading_comments_attach_to_what_they_document() {
227        let tree =
228            assert_parses("extends Node\n\n# Sets things up.\nfunc _ready() -> void:\n\tpass\n");
229        let func = tree
230            .root()
231            .descendants()
232            .find(|node| node.kind() == SyntaxKind::FuncDecl)
233            .expect("a function declaration");
234        // The comment describes `_ready`, so it must live inside FuncDecl and
235        // not trail the `extends` above it — otherwise reordering or
236        // reformatting the function would leave its comment behind.
237        assert!(
238            func.child_tokens().any(|token| token.kind.is_comment()),
239            "leading comment should belong to the declaration it precedes"
240        );
241    }
242
243    #[test]
244    fn distinguishes_inferred_from_explicit_types() {
245        let tree = assert_parses("var a := 1\nvar b: int = 1\n");
246        let has_type_hint = tree
247            .root()
248            .descendants()
249            .filter(|node| node.kind() == SyntaxKind::VarDecl)
250            .map(|node| {
251                node.child_nodes()
252                    .any(|child| child.kind() == SyntaxKind::TypeHint)
253            })
254            .collect::<Vec<_>>();
255        // `:=` carries no TypeHint; `: int` does. This is what the
256        // static-typing style rules key off.
257        assert_eq!(has_type_hint, vec![false, true]);
258    }
259
260    #[test]
261    fn parses_variadic_parameters() {
262        assert_parses("func foo(a, ...rest: Array) -> void:\n\tpass\n");
263    }
264
265    #[test]
266    fn parses_not_in_as_one_operator() {
267        let tree = assert_parses("func f():\n\tvar a = 1 not in [1] not in [true]\n");
268        // Two chained binary operators, not a prefix `not` and a stray `in`.
269        let binaries = node_kinds(&tree)
270            .iter()
271            .filter(|kind| **kind == SyntaxKind::BinaryExpr)
272            .count();
273        assert_eq!(binaries, 2);
274    }
275
276    #[test]
277    fn parses_abstract_annotations() {
278        // `@abstract` functions have no body at all.
279        assert_parses("@abstract\nclass_name Shape\n\n@abstract func area() -> float\n");
280        // `abstract` stays a normal identifier everywhere else.
281        assert_parses("func f():\n\tvar abstract = 1\n");
282    }
283
284    #[test]
285    fn parses_annotations_inside_function_bodies() {
286        assert_parses(
287            "func a():\n\t@warning_ignore(\"unused_variable\")\n\tvar x: Array[int] = [1, 2]\n\nfunc b():\n\t@warning_ignore(\"shadowed\") @warning_ignore(\"unused\") var y = 1\n",
288        );
289    }
290
291    #[test]
292    fn parses_semicolon_separated_declarations() {
293        assert_parses("const x = 1; const y = 2\nconst z = 3;\n");
294    }
295
296    #[test]
297    fn parses_docstrings_at_class_level() {
298        assert_parses("\"\"\"docstring\n\"\"\"\n\n\"another\"\n");
299    }
300
301    #[test]
302    fn parses_absolute_node_paths() {
303        assert_parses(
304            "func f():\n\t$/root.name = \"x\"\n\t$/root/A/B/C.free()\n\t$../Sibling.show()\n",
305        );
306    }
307
308    #[test]
309    fn sigil_literals_are_recognised_at_the_start_of_a_line() {
310        // The previous line ends in a value, which must not make `^` and `&`
311        // read as bitwise operators here.
312        assert_parses(
313            "func f():\n\tvar a = 1\n\t^\"node/path\"\n\t&\"string_name\"\n\t%Unique.show()\n",
314        );
315    }
316
317    #[test]
318    fn parses_inline_property_accessors() {
319        assert_parses(
320            "var p1: set = __set\nvar p2: set = __set, get = __get\nvar p3:\n\tget = __get,\n\tset = __set\n",
321        );
322    }
323
324    #[test]
325    fn parses_spaced_inference_operator() {
326        assert_parses("var is_enabled : = true\n");
327    }
328
329    #[test]
330    fn parses_match_patterns() {
331        assert_parses(
332            "func f(x):\n\tmatch x:\n\t\t[1, 2, [1, {1: 2, 2: var z, ..}]]:\n\t\t\tpass\n\t\t{\"name\", \"age\"}:\n\t\t\tpass\n\t\t{\"key\": \"v\", ..}:\n\t\t\tpass\n\t\t1 if true else 2:\n\t\t\tpass\n\t\tvar other when other > 2:\n\t\t\tpass\n\t\t_:\n\t\t\tpass\n",
333        );
334    }
335
336    #[test]
337    fn parses_multiline_lambdas_inside_brackets() {
338        // Indentation is meaningless inside brackets, except within a lambda
339        // body — the one case where the lexer has to turn it back on.
340        assert_parses(
341            "func f(source):\n\tstack(func():\n\t\tprint(\"foo\")\n\t\tif source == 1:\n\t\t\tpass)\n",
342        );
343        // A lambda body spanning lines inside a call spread over lines.
344        assert_parses(
345            "func f(button):\n\tbutton.pressed.connect(\n\t\tfunc() -> void:\n\t\t\tvar test := \"\"\n\t\t\tuse(test)\n\t)\n",
346        );
347        // Two lambdas in one array, the comma ending the first body.
348        assert_parses("func f():\n\tvar fs = [func():\n\t\treturn [1, 2, 3], func():\n\t\tpass]\n");
349        // A lambda whose body starts on the line it opened on.
350        assert_parses("func f():\n\tvar g = [func():\n\t\tpass]\n\tuse(g)\n");
351    }
352
353    #[test]
354    fn nested_lambdas_close_in_the_right_order() {
355        assert_parses(
356            "func f():\n\tvar a = [func():\n\t\tpass\n\t\tvar b = func():\n\t\t\tpass\n\t\t\tvar d = {\"f\": func():\n\t\t\t\tpass\n\t\t\t\treturn [1, 2, 3]}]\n",
357        );
358    }
359
360    #[test]
361    fn brackets_inside_a_lambda_body_still_suppress_indentation() {
362        // The array spans lines inside the lambda; only the lambda body itself
363        // gets indent handling back.
364        assert_parses(
365            "func f():\n\tcall(func():\n\t\tvar xs = [\n\t\t\t1,\n\t\t\t2,\n\t\t]\n\t\tuse(xs))\n",
366        );
367    }
368
369    #[test]
370    fn handles_crlf_and_missing_trailing_newline() {
371        assert_parses("var a = 1\r\nvar b = 2\r\n");
372        assert_parses("func f():\n\tpass");
373    }
374
375    #[test]
376    fn node_ranges_are_consistent_with_text() {
377        let source = "func hello() -> void:\n\tprint(\"hi\")\n";
378        let tree = parse(source);
379        for node in tree.root().descendants() {
380            let range = node.range();
381            assert!(
382                range.end() as usize <= source.len(),
383                "{:?} range {range} escapes the source",
384                node.kind()
385            );
386            assert_eq!(node.text(), range.slice(source));
387        }
388    }
389}