Skip to content

Commit 8786360

Browse files
committed
Add support for ERROR and MISSING special nodes
1 parent 593ad33 commit 8786360

File tree

8 files changed

+525
-40
lines changed

8 files changed

+525
-40
lines changed

crates/plotnik-lib/src/ql/parser/grammar.rs

Lines changed: 63 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ impl Parser<'_> {
6161
SyntaxKind::Dot => self.parse_anchor(),
6262
SyntaxKind::Negation => self.parse_negated_field(),
6363
SyntaxKind::UpperIdent | SyntaxKind::LowerIdent => self.parse_node_or_field(),
64+
SyntaxKind::KwError | SyntaxKind::KwMissing => {
65+
// Bare ERROR/MISSING outside parens - treat as error
66+
self.error_and_bump("ERROR and MISSING must be inside parentheses: (ERROR) or (MISSING ...)");
67+
}
6468
_ => {
6569
self.error_and_bump("unexpected token; expected a pattern");
6670
}
@@ -71,28 +75,63 @@ impl Parser<'_> {
7175
self.exit_recursion();
7276
}
7377

74-
/// Named node: `(type child1 child2 ...)` or `(_ child1 ...)` for any node.
78+
/// Named node pattern: `(type ...)`, `(_ ...)`, `(ERROR)`, `(MISSING ...)`.
79+
/// Also handles supertype/subtype: `(expression/binary_expression)`.
7580
fn parse_named_node(&mut self) {
7681
self.start_node(SyntaxKind::NamedNode);
7782
self.expect(SyntaxKind::ParenOpen);
7883

79-
if self.peek() == SyntaxKind::ParenClose {
80-
self.error("empty node pattern - expected node type or children");
81-
self.expect(SyntaxKind::ParenClose);
82-
self.finish_node();
83-
return;
84-
}
85-
86-
// Optional type constraint: `(identifier ...)` or `(_ ...)` for wildcard
87-
if matches!(
88-
self.peek(),
89-
SyntaxKind::LowerIdent | SyntaxKind::UpperIdent | SyntaxKind::Underscore
90-
) {
91-
self.bump();
84+
match self.peek() {
85+
SyntaxKind::ParenClose => {
86+
self.error("empty node pattern - expected node type or children");
87+
}
88+
SyntaxKind::Underscore => {
89+
self.bump();
90+
}
91+
SyntaxKind::LowerIdent | SyntaxKind::UpperIdent => {
92+
self.bump();
93+
// Optional subtype: `expression/binary_expression` or `expr/"()"`
94+
if self.peek() == SyntaxKind::Slash {
95+
self.bump();
96+
match self.peek() {
97+
SyntaxKind::LowerIdent | SyntaxKind::StringLit => {
98+
self.bump();
99+
}
100+
_ => {
101+
self.error("expected subtype after '/' (e.g., expression/binary_expression)");
102+
}
103+
}
104+
}
105+
}
106+
SyntaxKind::KwError => {
107+
self.bump();
108+
if self.peek() != SyntaxKind::ParenClose {
109+
self.error("(ERROR) takes no arguments");
110+
self.parse_node_children(SyntaxKind::ParenClose, NAMED_NODE_RECOVERY);
111+
}
112+
self.expect(SyntaxKind::ParenClose);
113+
self.finish_node();
114+
return;
115+
}
116+
SyntaxKind::KwMissing => {
117+
self.bump();
118+
match self.peek() {
119+
SyntaxKind::LowerIdent | SyntaxKind::StringLit => {
120+
self.bump();
121+
}
122+
SyntaxKind::ParenClose => {}
123+
_ => {
124+
self.parse_node_children(SyntaxKind::ParenClose, NAMED_NODE_RECOVERY);
125+
}
126+
}
127+
self.expect(SyntaxKind::ParenClose);
128+
self.finish_node();
129+
return;
130+
}
131+
_ => {}
92132
}
93133

94134
self.parse_node_children(SyntaxKind::ParenClose, NAMED_NODE_RECOVERY);
95-
96135
self.expect(SyntaxKind::ParenClose);
97136
self.finish_node();
98137
}
@@ -104,11 +143,17 @@ impl Parser<'_> {
104143
until: SyntaxKind,
105144
recovery: crate::ql::syntax_kind::TokenSet,
106145
) {
107-
while !self.eof() {
146+
loop {
108147
let kind = self.peek();
109148
if kind == until {
110149
break;
111150
}
151+
if self.eof() {
152+
self.error(
153+
"unexpected end of input inside node; expected a child pattern or closing delimiter",
154+
);
155+
break;
156+
}
112157
if PATTERN_FIRST.contains(kind) {
113158
self.parse_pattern();
114159
} else if recovery.contains(kind) {
@@ -218,7 +263,7 @@ impl Parser<'_> {
218263
if self.peek_nth(1) == SyntaxKind::Colon {
219264
self.parse_field();
220265
} else {
221-
self.start_node(SyntaxKind::Pattern);
266+
self.start_node(SyntaxKind::NamedNode);
222267
self.bump();
223268
self.finish_node();
224269
}
@@ -249,4 +294,4 @@ impl Parser<'_> {
249294
self.finish_node();
250295
}
251296
}
252-
}
297+
}

crates/plotnik-lib/src/ql/parser/tests/errors_test.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ fn error_missing_paren() {
1313
ParenOpen "("
1414
LowerIdent "identifier"
1515
---
16-
error: unexpected token inside node; expected a child pattern or closing delimiter
16+
error: unexpected end of input inside node; expected a child pattern or closing delimiter
1717
|
1818
1 | (identifier
1919
| ^
@@ -65,7 +65,7 @@ fn error_missing_bracket() {
6565
LowerIdent "string"
6666
ParenClose ")"
6767
---
68-
error: unexpected token inside node; expected a child pattern or closing delimiter
68+
error: unexpected end of input inside node; expected a child pattern or closing delimiter
6969
|
7070
1 | [(identifier) (string)
7171
| ^
@@ -180,7 +180,7 @@ fn error_missing_field_value() {
180180
|
181181
1 | (call name:)
182182
| ^
183-
error: unexpected token inside node; expected a child pattern or closing delimiter
183+
error: unexpected end of input inside node; expected a child pattern or closing delimiter
184184
|
185185
1 | (call name:)
186186
| ^
@@ -234,7 +234,7 @@ fn error_nested_unclosed() {
234234
LowerIdent "c"
235235
ParenClose ")"
236236
---
237-
error: unexpected token inside node; expected a child pattern or closing delimiter
237+
error: unexpected end of input inside node; expected a child pattern or closing delimiter
238238
|
239239
1 | (a (b (c)
240240
| ^
@@ -336,7 +336,7 @@ fn error_unclosed_alternation_nested() {
336336
ParenOpen "("
337337
LowerIdent "b"
338338
---
339-
error: unexpected token inside node; expected a child pattern or closing delimiter
339+
error: unexpected end of input inside node; expected a child pattern or closing delimiter
340340
|
341341
1 | [(a) (b
342342
| ^
@@ -422,7 +422,7 @@ fn error_field_missing_colon() {
422422
NamedNode
423423
ParenOpen "("
424424
LowerIdent "call"
425-
Pattern
425+
NamedNode
426426
LowerIdent "name"
427427
NamedNode
428428
ParenOpen "("
@@ -477,7 +477,7 @@ fn error_deeply_nested_unclosed() {
477477
ParenOpen "("
478478
LowerIdent "d"
479479
---
480-
error: unexpected token inside node; expected a child pattern or closing delimiter
480+
error: unexpected end of input inside node; expected a child pattern or closing delimiter
481481
|
482482
1 | (a (b (c (d
483483
| ^
@@ -573,7 +573,7 @@ fn error_multiline_recovery() {
573573
LowerIdent "a"
574574
Error
575575
UnexpectedFragment "^^^"
576-
Pattern
576+
NamedNode
577577
LowerIdent "b"
578578
ParenClose ")"
579579
---

crates/plotnik-lib/src/ql/parser/tests/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,7 @@ mod fields_test;
77
mod helpers_test;
88
mod quantifiers_test;
99
mod sequences_test;
10+
mod special_nodes_test;
11+
mod supertypes_test;
1012
mod trivia_test;
11-
mod type_annotations_test;
13+
mod type_annotations_test;

crates/plotnik-lib/src/ql/parser/tests/sequences_test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ fn sequence_missing_close_brace() {
240240
LowerIdent "b"
241241
ParenClose ")"
242242
---
243-
error: unexpected token inside node; expected a child pattern or closing delimiter
243+
error: unexpected end of input inside node; expected a child pattern or closing delimiter
244244
|
245245
1 | {(a) (b)
246246
| ^

0 commit comments

Comments
 (0)