Skip to content
← Back

src/syntax/parsers/variables/destructure_left.ghul

1
namespace Syntax.Parsers.Variables is
2
use Source;
3
use Logging;
4
5
class DESTRUCTURING_VARIABLE_LEFT(
6
identifier_parser: Parser[Trees.Identifiers.Identifier],
7
qualified_identifier_parser: Parser[Trees.Identifiers.Identifier],
8
type_parser: Parser[Trees.TypeExpressions.TypeExpression]
9
): Base[Trees.Variables.DESTRUCTURING_VARIABLE_LEFT] is
10
super();
11
12
// True when the current token starts a literal leaf — an
13
// integer / float / string / char literal, a boolean, or
14
// `null`. A `Color.RED`-shaped enum-member leaf is handled
15
// separately: it begins with an IDENTIFIER and is detected by
16
// the qualified-identifier parser returning a QUALIFIED rather
17
// than a bare Identifier.
18
_is_literal_leaf_start(token: Lexical.TOKEN) -> bool =>
19
token == Lexical.TOKEN.INT_LITERAL \/
20
token == Lexical.TOKEN.FLOAT_LITERAL \/
21
token == Lexical.TOKEN.STRING_LITERAL \/
22
token == Lexical.TOKEN.CHAR_LITERAL \/
23
token == Lexical.TOKEN.TRUE \/
24
token == Lexical.TOKEN.FALSE \/
25
token == Lexical.TOKEN.NULL;
26
27
_parse_literal_expression(context: CONTEXT) -> Trees.Expressions.Expression? is
28
let location = context.location;
29
let value_string = context.current.value_string;
30
31
case context.current_token
32
when Lexical.TOKEN.INT_LITERAL then
33
context.next_token();
34
return Trees.Expressions.Literals.INTEGER(location, value_string);
35
when Lexical.TOKEN.FLOAT_LITERAL then
36
context.next_token();
37
return Trees.Expressions.Literals.FLOAT(location, value_string);
38
when Lexical.TOKEN.STRING_LITERAL then
39
context.next_token();
40
return Trees.Expressions.Literals.STRING(location, value_string);
41
when Lexical.TOKEN.CHAR_LITERAL then
42
context.next_token();
43
return Trees.Expressions.Literals.CHARACTER(location, value_string);
44
when Lexical.TOKEN.TRUE, Lexical.TOKEN.FALSE then
45
context.next_token();
46
return Trees.Expressions.Literals.BOOLEAN(location, value_string);
47
when Lexical.TOKEN.NULL then
48
context.next_token();
49
return Trees.Expressions.NULL(location);
50
else
51
return null;
52
esac
53
si
54
55
parse(context: CONTEXT) -> Trees.Variables.DESTRUCTURING_VARIABLE_LEFT? is
56
let start = context.location;
57
let end mut = context.location;
58
let elements = Collections.LIST[Trees.Variables.VariableLeft]();
59
60
let should_poison mut = false;
61
62
// Tracks whether this group is named (`local = field, …`)
63
// or positional. Set by the first element that carries
64
// an `= field` annotation (or doesn't). Subsequent elements
65
// must match; mixing is rejected with a diagnostic.
66
let group_is_named mut = false;
67
let group_named_decided mut = false;
68
69
if !context.next_token(Lexical.TOKEN.PAREN_OPEN) then
70
return null;
71
fi
72
73
end = context.location;
74
75
do
76
let element: Trees.Variables.VariableLeft mut;
77
78
if context.current_token == Lexical.TOKEN.PAREN_OPEN then
79
// we've reached the start of a nested list of destructure elements
80
element = parse(context)!;
81
elif _is_literal_leaf_start(context.current_token) then
82
// Literal-leaf: the destructure element is a runtime
83
// value-equality test against the parsed expression,
84
// not a binding. Allowed only in refutable contexts
85
// (`if let` / `case`-when patterns); a plain `let`
86
// with literal leaves is rejected at compile time.
87
let leaf_loc = context.location;
88
let expr = _parse_literal_expression(context);
89
90
if !expr? then
91
should_poison = true;
92
end = context.location;
93
break;
94
fi
95
96
element = Trees.Variables.LITERAL_VARIABLE_LEFT(leaf_loc, expr);
97
else
98
let identifier = qualified_identifier_parser.parse(context);
99
100
if !identifier? then
101
// identifier parser bailed (e.g. the current token is
102
// a reserved word or some other non-identifier we ran
103
// into during error recovery). Without this guard
104
// we'd deref identifier.location below and NRE.
105
should_poison = true;
106
end = context.location;
107
break;
108
fi
109
110
if isa Trees.Identifiers.QUALIFIED(identifier) then
111
// `Color.RED`-shaped enum-member leaf — treat
112
// it as a literal-leaf carrying a name expression.
113
let expr = Trees.Expressions.IDENTIFIER(identifier.location, identifier);
114
element = Trees.Variables.LITERAL_VARIABLE_LEFT(identifier.location, expr);
115
else
116
element = Trees.Variables.SIMPLE_VARIABLE_LEFT(identifier.location, identifier);
117
fi
118
fi
119
120
// Per-element type ascription: `(c: Cat, d: Dog)` or
121
// `((x, y): Point, c: Color)`. In an ordinary `let`
122
// this is a static type assertion on the bound slot;
123
// in an `if let` arm the compile pass promotes it to
124
// a runtime narrowing test.
125
if context.current_token == Lexical.TOKEN.COLON then
126
context.next_token();
127
let element_type = type_parser.parse(context);
128
129
if element_type? then
130
element.set_type_expression(element_type);
131
fi
132
fi
133
134
// By-name annotation: `local = field` binds `local`
135
// (or, in a refutable context, value-matches the
136
// literal) from `source.field`. All elements of the
137
// same `(...)` group must use the same form — mixed
138
// groups are a parse error. The outer `let (…) = expr`
139
// initializer's `=` lives outside this group, so the
140
// `=` we see here is unambiguously the by-name
141
// separator.
142
let element_is_named = context.current_token == Lexical.TOKEN.ASSIGN;
143
144
if !group_named_decided then
145
group_is_named = element_is_named;
146
group_named_decided = true;
147
elif element_is_named != group_is_named then
148
context.error(
149
element.location,
150
"destructure group cannot mix local = field (by name) with bare positional elements"
151
);
152
should_poison = true;
153
fi
154
155
if element_is_named then
156
context.next_token();
157
let field_name = identifier_parser.parse(context);
158
159
if field_name? then
160
element.set_source_field_name(field_name);
161
else
162
should_poison = true;
163
fi
164
fi
165
166
elements.add(element);
167
168
if context.current_token == Lexical.TOKEN.PAREN_CLOSE then
169
// we've reached the end of a list of destructure elements
170
171
end = element.location;
172
context.next_token();
173
174
break;
175
elif context.current_token == Lexical.TOKEN.COMMA then
176
// more elements to come
177
178
context.next_token();
179
180
// Trailing comma: stop when the comma is followed
181
// immediately by the closing paren. `let (a, b,) = …`
182
// reads cleanly alongside the list and tuple forms.
183
if context.current_token == Lexical.TOKEN.PAREN_CLOSE then
184
end = context.location;
185
context.next_token();
186
break;
187
fi
188
elif
189
(context.current.token == Lexical.TOKEN.IDENTIFIER \/ context.current.token == Lexical.TOKEN.PAREN_OPEN) /\
190
context.location.start_line >= element.location.end_line /\
191
context.location.start_column > element.location.end_column
192
then
193
// after parsing an element, we've arrived at something that could be another
194
// element with no intervening comma, and the indentation suggests it's part of
195
// the same list: we'll assume it is, report an error, but continue parsing:
196
197
context.expect_token(Lexical.TOKEN.COMMA);
198
should_poison = true;
199
else
200
// something else, so we'll assume we've reached the end of the list but the
201
// user forgot to close the parentheses:
202
203
should_poison = true;
204
end = context.location;
205
context.expect_token(Lexical.TOKEN.PAREN_CLOSE);
206
207
break;
208
fi
209
od
210
211
let result = Trees.Variables.DESTRUCTURING_VARIABLE_LEFT(start::end, elements);
212
213
if should_poison then
214
result.poison();
215
fi
216
217
return result;
218
si
219
si
220
si