Skip to content
← Back

src/syntax/process/synthesise_top_level_entry.ghul

1
namespace Syntax.Process is
2
use Source
3
use Logging
4
use Trees
5
6
// Synthesises a global entry point from a file's top-level statements.
7
//
8
// A file with no namespace may carry bare statements at file scope. The
9
// parser collects them, in source order, onto the root definition list;
10
// this rewriter wraps them into a global function named after the
11
// configured entry point, so every downstream phase sees an ordinary
12
// function. Runs before expand_namespaces, which then wraps the
13
// synthesised function into the file's root namespace alongside any other
14
// global definitions.
15
//
16
// Top-level statements and namespaces are mutually exclusive in a file:
17
// a file that carries both is a compile error and no entry is synthesised.
18
//
19
// The entry takes the two shapes the runtime can hand an entry point -
20
// `args: string[]` and `env: Ghul.Environment` - so the statements can
21
// read the command line and the process environment directly, and a
22
// Main-compatible `_entry` wrapper is synthesised beside it to carry
23
// both in.
24
class SYNTHESISE_TOP_LEVEL_ENTRY(_ir_context: IR.CONTEXT, _logger: Logger) is
25
apply(node: Node) is
26
if !isa Definitions.LIST(node) then
27
return
28
fi
29
30
let list = cast Definitions.LIST(node)
31
let statements = list.top_level_statements
32
33
if !statements? then
34
return
35
fi
36
37
list.top_level_statements = null
38
39
for definition in list do
40
if isa Definitions.NAMESPACE(definition) then
41
_logger.error(
42
statements.location,
43
"top-level statements cannot appear in a file with a namespace"
44
)
45
46
return
47
fi
48
od
49
50
for statement in statements do
51
if isa Statements.LET(statement) then
52
for variable in statement.variables do
53
variable.is_top_level = true
54
55
if _reject_entry_parameter_collision(variable) then
56
statement.poison()
57
variable.poison()
58
fi
59
od
60
fi
61
od
62
63
// Pushed to the front so compile-expressions walks the entry
64
// before any other function in the file: the top-level `let`
65
// variables it declares settle their inferred types during the
66
// entry's own walk, and every sibling function reads them
67
// after that.
68
list.push(_synthesise_entry(statements))
69
si
70
71
// Whether a file's root definition list carries a synthesised
72
// top-level entry. Consulted wherever that file needs treating
73
// differently: it compiles first, and it takes the full-rebuild
74
// path in analysis mode.
75
has_top_level_entry(definition: Node?) -> bool static is
76
if let list = cast Definitions.LIST?(definition) then
77
for d in list do
78
let unwrapped = d.without_pragmas
79
80
if let function = cast Definitions.FUNCTION?(unwrapped) then
81
if function.is_top_level_entry then
82
return true
83
fi
84
elif let `namespace = cast Definitions.NAMESPACE?(unwrapped) then
85
// Expand-namespaces wraps a namespace-less file's
86
// definitions - the synthesised entry among them -
87
// into a file-private namespace, so the entry sits
88
// one list down once that pass has run.
89
if has_top_level_entry(`namespace.body) then
90
return true
91
fi
92
fi
93
od
94
fi
95
96
return false
97
si
98
99
// The entry's implicit `args` and `env` formals are in scope across
100
// the whole entry body, so a top-level `let` of either name would
101
// shadow - and be shadowed by - them inside the statements. Name
102
// resolution from the body finds the formal first, so the let's
103
// inferred type lands on the formal and the global's type is never
104
// set; reporting the collision here turns that silent mis-compile
105
// into a diagnostic.
106
_reject_entry_parameter_collision(variable: Variables.VARIABLE) -> bool is
107
let name = variable.name
108
109
if name? /\ (name.name =~ "args" \/ name.name =~ "env") then
110
_logger.error(
111
name.location,
112
"a top-level '{name.name}' collides with the entry point's '{name.name}' parameter; use another name")
113
114
return true
115
fi
116
117
return false
118
si
119
120
// A submission's entry returns the value of its final expression
121
// to whatever ran it, so it is declared to return any value or
122
// none and the ordinary tail rule does the rest: a final
123
// expression of any type is assignable to it, and a final
124
// statement with no value leaves the entry returning nothing.
125
_return_type(location: LOCATION) -> TypeExpressions.TypeExpression =>
126
if IoC.CONTAINER.instance.build_flags.submission_name? then
127
TypeExpressions.OPTIONAL(
128
location,
129
TypeExpressions.NAMED(location, Identifiers.Identifier(location, "object")))
130
else
131
TypeExpressions.INFER(location)
132
fi
133
134
_synthesise_entry(statements: Statements.LIST) -> Definitions.FUNCTION is
135
let location = statements.location
136
137
let arguments = Collections.LIST[Variables.VARIABLE]()
138
139
// The two shapes the runtime can hand an entry point (GHUL.md,
140
// "functions"), so the statements can read the command line as
141
// `args` and the process environment as `env`. The wrapper
142
// synthesise-entry-environment-wrapper builds beside this
143
// function is what carries them in.
144
//
145
// Like the entry's name below, the formals and their types
146
// carry an internal location: at the statement span they would
147
// record uses covering the whole region, and a hover or a
148
// completion anywhere in the statements would land on them.
149
let internal = LOCATION.internal
150
151
let args_type = TypeExpressions.ARRAY_(internal, TypeExpressions.NAMED(internal, Identifiers.Identifier(internal, "string")))
152
let args_param = Variables.VARIABLE(internal, Identifiers.Identifier(internal, "args"), args_type, false, true, null)
153
args_param.mark_argument()
154
arguments.add(args_param)
155
156
let env_type = TypeExpressions.NAMED(
157
internal,
158
Identifiers.QUALIFIED(
159
internal,
160
Identifiers.Identifier(internal, "Ghul"),
161
"Environment",
162
internal,
163
internal))
164
let env_param = Variables.VARIABLE(internal, Identifiers.Identifier(internal, "env"), env_type, false, true, null)
165
env_param.mark_argument()
166
arguments.add(env_param)
167
168
let result = Definitions.FUNCTION(
169
location,
170
// The entry has no declaration the source names, so its
171
// identifier carries an internal location: a name at the
172
// statement span would record a hover use covering the whole
173
// region, and any hover on a non-symbol position there would
174
// report the synthesised entry.
175
Identifiers.Identifier(LOCATION.internal, _ir_context.entry_point_name),
176
TypeExpressions.LIST(location, Collections.LIST[TypeExpressions.TypeExpression](0)),
177
Variables.LIST(location, arguments),
178
_return_type(LOCATION.internal),
179
Modifiers.LIST(location, null, null),
180
Bodies.BLOCK(location, statements)
181
)
182
183
result.is_top_level_entry = true
184
185
return result
186
si
187
si
188
si