Skip to content
← Back

src/driver/compile_server.ghul

1
namespace Driver is
2
use System.Exception
3
4
use Collections.LIST
5
6
use IO.File
7
use IO.Path
8
9
use Analysis.Protocol.COMPILE_REPLY
10
use Analysis.Protocol.COMPILE_REQUEST
11
use Analysis.Protocol.DIAGNOSTIC
12
use Analysis.Protocol.JSON_PROTOCOL
13
14
// Compiles submissions one after another in one process, so each pays
15
// for its own few lines rather than for starting a runtime and importing
16
// the reference set. Requests and replies are one line of JSON each; the
17
// first line out says the server is ready.
18
//
19
// Every setting a compile depends on is set again for each request, and
20
// everything the previous compile left - its diagnostics, its symbols,
21
// its queued files, its emitter - is cleared first, so that a request is
22
// answered exactly as a batch compile of the same file would be. What
23
// does carry over is the reference set, which only grows: each request's
24
// references are imported into it, and that is the point of keeping the
25
// process.
26
class COMPILE_SERVER(
27
_compiler: Compiler.COMPILER,
28
_container: IoC.CONTAINER,
29
_module_version: string,
30
_input: IO.TextReader,
31
_output: IO.TextWriter
32
) is
33
serve() is
34
_write(COMPILE_REPLY.ready_reply())
35
36
do
37
let line = _input.read_line()
38
39
if !line? then
40
return
41
fi
42
43
_write(answer(line))
44
od
45
si
46
47
// The reply to one request line. Nothing that goes wrong inside a
48
// compile escapes: it becomes a reply with status 2, and the server
49
// carries on with the next line.
50
answer(line: string) -> COMPILE_REPLY is
51
let request: COMPILE_REQUEST? mut = null
52
53
try
54
request = System.Text.Json.JsonSerializer.deserialize[COMPILE_REQUEST](line, JSON_PROTOCOL.options)
55
catch e: Exception
56
return COMPILE_REPLY.failure("malformed request: {e.message}")
57
yrt
58
59
if !request? then
60
return COMPILE_REPLY.failure("malformed request: expected an object")
61
fi
62
63
if request.check then
64
return check(request)
65
fi
66
67
let problem = COMPILE_REQUEST_CHECK.problem_with(request)
68
69
if problem? then
70
return COMPILE_REPLY.failure(problem)
71
fi
72
73
try
74
return compile(request)
75
catch e: Exception
76
return COMPILE_REPLY.failure("{e.get_type().name}: {e.message}")
77
yrt
78
si
79
80
// Whether a request's source is complete, parsed with a logger and a
81
// parser state of its own: nothing a compile reads is touched, so a
82
// check between two compiles changes neither.
83
check(request: COMPILE_REQUEST) -> COMPILE_REPLY is
84
if !request.text? /\ !request.source? then
85
return COMPILE_REPLY.failure("check request has neither source nor text")
86
fi
87
88
let path = COMPILE_REQUEST_CHECK.source_path(request)
89
90
try
91
let reader: IO.TextReader =
92
if let text = request.text then
93
IO.StringReader(text)
94
else
95
File.open_text(path)
96
fi
97
98
let use disposing_reader = reader
99
100
let checked = _compiler.check(path, reader)
101
102
return COMPILE_REPLY.checked(
103
case checked.completeness
104
when Syntax.Parsers.COMPLETENESS.COMPLETE then "complete"
105
when Syntax.Parsers.COMPLETENESS.INCOMPLETE then "incomplete"
106
else "invalid"
107
esac,
108
checked.last_statement)
109
catch e: Exception
110
return COMPILE_REPLY.failure("{e.get_type().name}: {e.message}")
111
yrt
112
si
113
114
compile(request: COMPILE_REQUEST) -> COMPILE_REPLY is
115
_reset(request)
116
117
let path = COMPILE_REQUEST_CHECK.source_path(request)
118
119
let reader: IO.TextReader =
120
if let text = request.text then
121
IO.StringReader(text)
122
else
123
File.open_text(path)
124
fi
125
126
let use disposing_reader = reader
127
128
_compiler.parse_and_queue(
129
path,
130
reader,
131
_container.build_flags.want_compile_up_to_expressions,
132
_container.build_flags.want_compile_expressions,
133
false)
134
135
_compiler.post_parse()
136
_compiler.build()
137
138
let diagnostics = LIST[DIAGNOSTIC]()
139
140
Analysis.DIAGNOSTICS_COLLECTOR.collect_into(_container.logger, diagnostics, LIST[string]())
141
142
let logger = _container.logger
143
144
if logger.any_errors then
145
return COMPILE_REPLY(false, 1, null, diagnostics)
146
elif logger.is_poisoned then
147
return COMPILE_REPLY(false, 2, "internal error", diagnostics)
148
fi
149
150
_container.ir_context.srm_assembly_emitter.write_to(request.output)
151
152
return COMPILE_REPLY(false, 0, null, diagnostics)
153
si
154
155
_reset(request: COMPILE_REQUEST) is
156
let flags = _container.build_flags
157
158
flags.submission_name = request.name
159
flags.want_library = true
160
flags.want_executable = false
161
162
_container.value_boxer.want_boxing = true
163
164
_clear_diagnostics()
165
166
_container.state_store_registry.clear_all()
167
168
_compiler.clear_queue()
169
_compiler.clear_symbols()
170
171
Semantic.DotNet.ADDITIONAL_REFERENCES.add(_container, request.references ?? Collections.LIST[string]())
172
173
_container.ir_context.install_srm_assembly_emitter(
174
Path.get_file_name_without_extension(request.output) ?? "module",
175
_module_version,
176
true)
177
si
178
179
_clear_diagnostics() is
180
let logger = _container.logger
181
182
for path in LIST[string](logger.diagnostic_paths) do
183
logger.clear(path, false)
184
od
185
186
logger.clear_suppression_regions()
187
logger.clear_consumed_error()
188
logger.clear_consumed_any()
189
si
190
191
_write(reply: COMPILE_REPLY) is
192
_output.write(System.Text.Json.JsonSerializer.serialize[COMPILE_REPLY](reply, JSON_PROTOCOL.options))
193
_output.write("\n")
194
_output.flush()
195
si
196
si
197
198
// What a compile request needs before the server can act on it, and
199
// the file name it compiles under.
200
class COMPILE_REQUEST_CHECK is
201
problem_with(request: COMPILE_REQUEST) -> string? static is
202
if string.is_null_or_empty(request.name) then
203
return "request has no name"
204
fi
205
206
if string.is_null_or_empty(request.output) then
207
return "request has no output"
208
fi
209
210
if !request.source? /\ !request.text? then
211
return "request has neither source nor text"
212
fi
213
214
if let source = request.source /\ !request.text? /\ !File.exists(source) then
215
return "source file not found: {source}"
216
fi
217
218
if let references = request.references then
219
for reference in references do
220
if !File.exists(reference) then
221
return "reference not found: {reference}"
222
fi
223
od
224
fi
225
226
return null
227
si
228
229
// Source carried inline is named after the submission, so its
230
// diagnostics name a file a reader can tell apart from any other.
231
source_path(request: COMPILE_REQUEST) -> string static =>
232
if request.text? then "{request.name}.ghul" else request.source ?? "{request.name}.ghul" fi
233
si
234
si