Skip to content
← Back

src/syntax/process/rewrite-syntax-trees/synthesise_entry_environment_wrapper.ghul

1
namespace Syntax.Process is
2
use Source
3
use Trees
4
5
// Synthesises the private CLR-facing `_entry` wrapper a hand-written
6
// entry function needs when it declares a `Ghul.Environment` parameter.
7
//
8
// A named function's signature is always fully explicit (GHUL.md), so
9
// recognising the two argument shapes - a `string[]` and a
10
// `Ghul.Environment` - needs nothing but the written type expressions,
11
// and this runs at the same syntax-tree-rewriting stage as
12
// synthesise-top-level-entry and rewrite-primary-constructors, before
13
// any symbol exists.
14
//
15
// Only a function whose name matches the configured entry-point name
16
// is considered - the same name select-entry-point itself matches -
17
// and only when every one of its one or two parameters is one of the
18
// two recognised shapes, with a `Ghul.Environment` one actually among
19
// them. Anything else (a lone `string[]`, no parameters, an
20
// unrecognised shape) is left untouched: select-entry-point's existing
21
// signature check decides its fate exactly as it always has.
22
//
23
// The wrapper takes the real Main-compatible shape - no parameters, or
24
// one `string[]` when the original declared one - and its body is a
25
// single call back to the original function, with `Ghul.ENVIRONMENT`
26
// .instance` in the environment slot and its own `args` parameter (if
27
// any) in the other, in whichever order the original declared them.
28
// The wrapper's own return type is a copy of the original's, so its
29
// tail - the call itself, with no explicit `return` - is handled by
30
// the ordinary ghul tail-return rule regardless of whether that type
31
// is `int` or void.
32
//
33
// The original function is marked so select-entry-point excludes it
34
// from candidacy outright: its own signature is never Main-compatible,
35
// and the wrapper is what stands in for it.
36
class SYNTHESISE_ENTRY_ENVIRONMENT_WRAPPER(_ir_context: IR.CONTEXT) is
37
_WRAPPER_NAME: string static => "_entry"
38
_ARGS_PARAMETER_NAME: string static => "args"
39
40
apply(node: Node) is
41
if let list = cast Definitions.LIST?(node) then
42
_process_list(list)
43
fi
44
si
45
46
_process_list(list: Definitions.LIST) is
47
let to_add = Collections.LIST[Definitions.FUNCTION]()
48
49
// `use` applies only within the namespace block it is written
50
// in (GHUL.md), so each list's own uses are what a bare name
51
// written in its own definitions can have come from.
52
let uses = list.uses
53
54
let has_named_entry = _has_hand_written_entry(list)
55
56
for d in list do
57
let unwrapped = d.without_pragmas
58
59
if let `namespace = cast Definitions.NAMESPACE?(unwrapped) then
60
_process_list(`namespace.body)
61
elif let function = cast Definitions.FUNCTION?(unwrapped) then
62
if let wrapper = _maybe_synthesise_wrapper(function, uses, has_named_entry) then
63
to_add.add(wrapper)
64
fi
65
fi
66
od
67
68
for wrapper in to_add do
69
list.add(wrapper)
70
od
71
si
72
73
// Whether a hand-written function in this list matches the
74
// entry-point name. A file may carry top-level statements and
75
// global definitions together, and the hand-written entry outranks
76
// the synthesised one in select-entry-point; giving both a wrapper
77
// would declare `_entry` twice.
78
_has_hand_written_entry(list: Definitions.LIST) -> bool is
79
for d in list do
80
if let function = cast Definitions.FUNCTION?(d.without_pragmas) then
81
if !function.is_top_level_entry /\ function.name? /\
82
function.name.name =~ _ir_context.entry_point_name
83
then
84
return true
85
fi
86
fi
87
od
88
89
return false
90
si
91
92
_maybe_synthesise_wrapper(function: Definitions.FUNCTION, uses: Collections.List[Definitions.USE], has_named_entry: bool) -> Definitions.FUNCTION? is
93
if function.is_top_level_entry /\ has_named_entry then
94
return null
95
fi
96
97
let name = function.name
98
99
if !name? \/ !(name.name =~ _ir_context.entry_point_name) then
100
return null
101
fi
102
103
let variables = function.arguments.variables
104
105
if variables.count < 1 \/ variables.count > 2 then
106
return null
107
fi
108
109
let args_variable: Variables.VARIABLE? mut = null
110
let env_variable: Variables.VARIABLE? mut = null
111
112
for variable in variables do
113
if _is_args_shaped(variable.type_expression) then
114
if args_variable? then
115
return null
116
fi
117
118
args_variable = variable
119
elif _is_environment_shaped(variable.type_expression, uses) then
120
if env_variable? then
121
return null
122
fi
123
124
env_variable = variable
125
else
126
return null
127
fi
128
od
129
130
if !env_variable? then
131
return null
132
fi
133
134
function.wrapped_by_synthesized_main = true
135
136
return _synthesise_wrapper(function, args_variable?, env_variable, function.is_top_level_entry)
137
si
138
139
_is_args_shaped(type_expression: TypeExpressions.TypeExpression) -> bool is
140
if let array = cast TypeExpressions.ARRAY_?(type_expression) then
141
return _is_bare_named(array.element, "string")
142
fi
143
144
return false
145
si
146
147
// Fully-qualified `Ghul.Environment` always matches. A bare name
148
// also matches when this namespace block's own `use` declarations
149
// (GHUL.md: a `use` applies only within the block it is written
150
// in) bring `Ghul.Environment` into scope under that name - the
151
// three ordinary (non-type-alias) import shapes: `use Ghul;`,
152
// `use Ghul.Environment;`, and `use X = Ghul.Environment;`.
153
_is_environment_shaped(type_expression: TypeExpressions.TypeExpression, uses: Collections.List[Definitions.USE]) -> bool is
154
if let named = cast TypeExpressions.NAMED?(type_expression) then
155
let id = named.name
156
157
if _names_ghul_environment(id) then
158
return true
159
fi
160
161
if id.is_qualified then
162
return false
163
fi
164
165
for u in uses do
166
if let imported = u.`use then
167
if let alias = u.name then
168
if alias.name =~ id.name /\ _names_ghul_environment(imported) then
169
return true
170
fi
171
elif imported.is_qualified then
172
if imported.name =~ id.name /\ _names_ghul_environment(imported) then
173
return true
174
fi
175
elif imported.name =~ "Ghul" /\ id.name =~ "Environment" then
176
return true
177
fi
178
fi
179
od
180
fi
181
182
return false
183
si
184
185
_names_ghul_environment(id: Identifiers.Identifier) -> bool =>
186
id.name =~ "Environment" /\
187
id.qualifier? /\
188
id.qualifier.name =~ "Ghul" /\
189
!id.qualifier.qualifier?
190
191
_is_bare_named(type_expression: TypeExpressions.TypeExpression, name: string) -> bool is
192
if let named = cast TypeExpressions.NAMED?(type_expression) then
193
return !named.name.is_qualified /\ named.name.name =~ name
194
fi
195
196
return false
197
si
198
199
_synthesise_wrapper(
200
original: Definitions.FUNCTION,
201
has_args: bool,
202
env_variable: Variables.VARIABLE,
203
wraps_top_level: bool
204
) -> Definitions.FUNCTION is
205
// The wrapper stands where the function it wraps stands, but
206
// nothing inside it does: the parts carry an internal location,
207
// so they record no use a hover or a completion in the wrapped
208
// function could land on.
209
let location = original.location
210
let internal = LOCATION.internal
211
let arg_vars = Collections.LIST[Variables.VARIABLE]()
212
213
if has_args then
214
let args_type = TypeExpressions.ARRAY_(internal, TypeExpressions.NAMED(internal, Identifiers.Identifier(internal, "string")))
215
let args_param = Variables.VARIABLE(internal, Identifiers.Identifier(internal, _ARGS_PARAMETER_NAME), args_type, false, true, null)
216
217
args_param.mark_argument()
218
arg_vars.add(args_param)
219
fi
220
221
let arg_list = Variables.LIST(internal, arg_vars)
222
let call_args = Collections.LIST[Expressions.Expression]()
223
224
for variable in original.arguments.variables do
225
if variable == env_variable then
226
call_args.add(_environment_instance_expression(internal))
227
else
228
call_args.add(Expressions.IDENTIFIER(internal, Identifiers.Identifier(internal, _ARGS_PARAMETER_NAME)))
229
fi
230
od
231
232
let call =
233
Expressions.CALL(
234
location,
235
Expressions.IDENTIFIER(internal, Identifiers.Identifier(internal, _ir_context.entry_point_name)),
236
Expressions.LIST(internal, call_args)
237
)
238
239
let body_statements = Collections.LIST[Statements.Statement]()
240
241
body_statements.add(Statements.EXPRESSION(internal, call))
242
243
let body = Bodies.BLOCK(internal, Statements.LIST(internal, body_statements))
244
let modifiers = Modifiers.LIST(internal, null, null)
245
246
let wrapper =
247
Definitions.FUNCTION(
248
location,
249
Identifiers.Identifier(LOCATION.internal, _WRAPPER_NAME),
250
TypeExpressions.LIST(location, Collections.LIST[TypeExpressions.TypeExpression](0)),
251
arg_list,
252
original.type_expression.copy(),
253
modifiers,
254
body
255
)
256
257
wrapper.mark_synthesized()
258
wrapper.is_synthesized_main_wrapper = true
259
wrapper.wraps_top_level_entry = wraps_top_level
260
261
return wrapper
262
si
263
264
_environment_instance_expression(location: LOCATION) -> Expressions.Expression is
265
let ghul = Expressions.IDENTIFIER(location, Identifiers.Identifier(location, "Ghul"))
266
let environment_class = Expressions.MEMBER(location, ghul, Identifiers.Identifier(location, "ENVIRONMENT"), location)
267
268
return Expressions.MEMBER(location, environment_class, Identifiers.Identifier(location, "instance"), location)
269
si
270
si
271
si