Skip to content
← Back

src/syntax/parsers/definitions/union.ghul

1
namespace Syntax.Parsers.Definitions is
2
use Source;
3
use Logging;
4
5
class UNION(
6
identifier_parser: Parser[Trees.Identifiers.Identifier],
7
type_parser: Parser[Trees.TypeExpressions.TypeExpression],
8
type_list_parser: Parser[Trees.TypeExpressions.LIST],
9
modifier_list_parser: Parser[Trees.Modifiers.LIST],
10
variable_list_parser: Parser[Trees.Variables.LIST],
11
variant_list_parser: Parser[Trees.Definitions.LIST]
12
): Base[Trees.Definitions.UNION] is
13
super();
14
15
parse(context: CONTEXT) -> Trees.Definitions.UNION? is
16
let start = context.location;
17
18
context.in_classy = true;
19
context.global_indent = start.start_column;
20
21
/*
22
union_definition ::= "union" identifier type_parameters? primary_params? (":" type_list)? modifiers? "is" variant_definition+ "si"
23
24
type_parameters ::= "[" type_parameter ("," type_parameter)* "]"
25
type_parameter ::= identifier
26
27
primary_params ::= "(" primary_param ("," primary_param)* ")"
28
primary_param ::= identifier ":" type_expression modifier*
29
30
type_list ::= type_expression ("," type_expression)*
31
32
variant_definition ::= identifier variant_fields? ";"
33
variant_fields ::= "(" variant_field ("," variant_field)* ")"
34
variant_field ::= identifier ":" type_expression | ".."
35
36
A `..` in a variant_field list splices in the union's primary
37
parameters at that position. Only valid when the union declares
38
a primary_params header. If the union has a primary_params
39
header and the variant_definition omits variant_fields, the
40
splice is implied — the variant inherits the primary
41
parameters with no additional fields.
42
43
type_arguments ::= "[" type_expression ("," type_expression)* "]"```
44
45
identifier ::= ...
46
type_expression ::= ...
47
*/
48
49
try
50
context.next_token(Lexical.TOKEN.UNION);
51
52
let identifier = identifier_parser.parse(context);
53
54
let should_poison mut = false;
55
56
if !identifier? then
57
return null;
58
fi
59
60
let arguments: Trees.TypeExpressions.LIST? mut = null;
61
62
if context.current.token == Lexical.TOKEN.SQUARE_OPEN then
63
context.next_token();
64
65
context.in_type_parameters = true;
66
arguments = type_list_parser.parse(context)!;
67
context.in_type_parameters = false;
68
69
arguments.check_no_reference_types(context.logger);
70
71
should_poison = arguments.is_poisoned;
72
73
if
74
!should_poison \/
75
context.current.token == Lexical.TOKEN.SQUARE_CLOSE
76
then
77
should_poison = !context.next_token(Lexical.TOKEN.SQUARE_CLOSE) \/ should_poison;
78
fi
79
fi
80
81
let primary_params: Trees.Variables.LIST? mut = null;
82
83
if context.current.token == Lexical.TOKEN.PAREN_OPEN then
84
context.next_token();
85
86
if context.current.token != Lexical.TOKEN.PAREN_CLOSE then
87
let previous_in_primary_ctor_params = context.in_primary_ctor_params;
88
context.in_primary_ctor_params = true;
89
try
90
primary_params = variable_list_parser.parse(context);
91
finally
92
context.in_primary_ctor_params = previous_in_primary_ctor_params;
93
yrt
94
95
if primary_params? then
96
for p in primary_params do
97
p.mark_argument();
98
od
99
100
should_poison = should_poison \/ primary_params.is_poisoned;
101
fi
102
else
103
primary_params = Trees.Variables.LIST(
104
context.location,
105
Collections.LIST[Trees.Variables.VARIABLE](0)
106
);
107
fi
108
109
should_poison = !context.next_token(Lexical.TOKEN.PAREN_CLOSE) \/ should_poison;
110
fi
111
112
let ancestors: Trees.TypeExpressions.LIST? mut = null;
113
114
if context.current.token == Lexical.TOKEN.COLON then
115
context.next_token();
116
ancestors = type_list_parser.parse(context)!;
117
ancestors.check_no_reference_types(context.logger);
118
119
should_poison = should_poison \/ ancestors.is_poisoned;
120
fi
121
122
let modifiers = modifier_list_parser.parse(context)!;
123
124
let read_a_body mut = false;
125
126
let body: Trees.Definitions.LIST mut;
127
let expect_body mut = false;
128
129
if !should_poison then
130
if context.next_token(Lexical.TOKEN.IS) then
131
expect_body = true;
132
elif lookahead_for_body(context) then
133
should_poison = true;
134
expect_body = true;
135
fi
136
else
137
expect_body = lookahead_for_body(context);
138
fi
139
140
if expect_body then
141
body = variant_list_parser.parse(context)!;
142
143
read_a_body = true;
144
else
145
body = Trees.Definitions.LIST(LOCATION.internal, Collections.LIST[Trees.Definitions.Definition](0));
146
fi
147
148
if arguments? then
149
for member in body do
150
if isa Trees.Definitions.VARIANT(member) then
151
let variant = member;
152
153
variant.set_arguments(arguments.deep_copy());
154
fi
155
od
156
fi
157
158
let result = Trees.Definitions.UNION(
159
start::context.location,
160
identifier!,
161
arguments,
162
ancestors,
163
modifiers,
164
body
165
);
166
167
if primary_params? then
168
result.set_primary_params(primary_params);
169
fi
170
171
result.poison(should_poison);
172
173
if read_a_body then
174
if !should_poison then
175
context.next_token(Lexical.TOKEN.SI);
176
elif context.current_token == Lexical.TOKEN.SI /\ context.current.location.start_column >= start.start_column then
177
context.next_token();
178
fi
179
fi
180
181
return result;
182
finally
183
context.in_classy = false;
184
yrt
185
si
186
187
lookahead_for_body(context: CONTEXT) -> bool is
188
let want_backtrack = true;
189
190
let line = context.current.location.start_line;
191
192
let use diagnostics_snapshot = context.diagnostics_speculate_then_backtrack();
193
194
// search on the current line for an 'is'
195
while context.current.location.start_line == line /\ context.current.location.start_column >= context.global_indent do
196
if context.current.token == Lexical.TOKEN.IS then
197
diagnostics_snapshot.commit();
198
199
context.next_token();
200
201
// is on the same line as the start of the class definition means it's likely the block is associated with the
202
// class, and we should parse it as such, even if the first part of the class definition is invalid
203
return true;
204
fi
205
206
context.next_token();
207
od
208
209
if context.current_token == Lexical.TOKEN.IS /\ context.current.location.start_column >= context.global_indent then
210
diagnostics_snapshot.commit();
211
context.next_token();
212
213
// again, if the `is` is on the line immediately following the class definition, and it's properly indented,
214
// it's likely the block is associated with the class, and we should parse it as such, even if the first part of
215
// the class definition is invalid
216
return true;
217
fi
218
219
return false;
220
si
221
si
222
si