Skip to content
← Back

src/syntax/parsers/definitions/union.ghul

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