Skip to content
← Back

src/driver/main.ghul

1
namespace Driver is
2
use System.Exception;
3
4
use IO.Std;
5
use IO.Path;
6
use IO.File;
7
use IO.Directory;
8
9
use Collections.Iterable;
10
use Collections.LIST;
11
use Collections.SET;
12
13
use System.Text.RegularExpressions.Regex;
14
use System.Text.RegularExpressions.Match;
15
16
use System.Runtime.InteropServices.RuntimeInformation;
17
use System.Runtime.InteropServices.OSPlatform;
18
19
use Ghul.Pipes;
20
21
use IoC;
22
use Logging;
23
use Compiler;
24
25
use Analysis.ANALYSER;
26
27
class MAIN is
28
paths: Driver.PATH_CONFIG;
29
project_name: string;
30
container: IoC.CONTAINER;
31
compiler: COMPILER;
32
flags: GLOBAL_BUILD_FLAGS;
33
output_file_name_generator: OUTPUT_FILE_NAME_GENERATOR;
34
assemblies: LIST[string];
35
ghul_source_files: LIST[string];
36
analyse_files: LIST[string];
37
module_version: string;
38
assembly_info: Semantic.DotNet.ASSEMBLY_INFO;
39
40
emit_boilerplate_il: string?;
41
42
want_format: bool;
43
44
compiler_version: string =>
45
let assembly_version =
46
System.Reflection.Assembly
47
.get_entry_assembly()!
48
.get_custom_attributes(typeof System.Reflection.AssemblyInformationalVersionAttribute, false)
49
|> map(va => cast System.Reflection.AssemblyInformationalVersionAttribute?(va)!)
50
|> first()
51
52
in
53
if assembly_version? then
54
"{assembly_version.informational_version}"
55
else
56
"v0.0.0-unknown.1"
57
fi;
58
59
entry(arguments: string[]) static is
60
let full_arguments = LIST[string](arguments.count + 1);
61
62
add_arguments(full_arguments, arguments);
63
64
let instance = MAIN(LIST[string](full_arguments));
65
si
66
67
add_arguments(result: LIST[string], source: Iterable[string]) static is
68
let seen_files = SET();
69
70
add_arguments(seen_files, result, source);
71
si
72
73
add_arguments(seen_files: SET[string], result: LIST[string], source: Iterable[string]) static is
74
for argument in source do
75
if argument.starts_with('@') then
76
read_arguments_from_file(seen_files, result, argument.substring(1));
77
elif argument.length > 0 then
78
result.add(argument);
79
fi
80
od
81
si
82
83
read_arguments_from_file(seen_files: SET[string], result: LIST[string], path: string) static is
84
if seen_files.contains(path) then
85
return;
86
fi
87
88
let parser = ARGUMENTS_PARSER();
89
90
let text = File.read_all_text(path);
91
92
let arguments = parser.parse_arguments(text);
93
94
add_arguments(seen_files, result, arguments);
95
si
96
97
init(arguments: LIST[string]) is
98
let result mut = 1;
99
100
try
101
Std.input_encoding = System.Text.UTF8Encoding(false);
102
Std.output_encoding = System.Text.UTF8Encoding(false);
103
104
if arguments.count == 0 then
105
Std.out.write("ghūl {compiler_version}\n");
106
Std.out.flush();
107
108
System.Environment.exit(0);
109
fi
110
111
container = IoC.CONTAINER.instance;
112
113
paths = container.path_config;
114
flags = container.build_flags;
115
compiler = COMPILER();
116
output_file_name_generator = OUTPUT_FILE_NAME_GENERATOR();
117
assembly_info = container.assembly_info;
118
module_version = "0:0:0:0";
119
120
parse_flags(arguments);
121
122
IoC.CONTAINER.instance.assemblies.import(assemblies);
123
124
if flags.want_analyse then
125
analyse();
126
127
System.Environment.exit(0);
128
fi
129
130
if want_format then
131
format_files();
132
133
System.Environment.exit(0);
134
fi
135
136
start_build();
137
138
compiler.post_parse();
139
140
compiler.build();
141
142
result = finish_build();
143
catch e: Exception
144
Std.error.write(e);
145
Std.error.write("\n");
146
Std.error.flush();
147
148
result = 1;
149
yrt
150
151
if result == 0 then
152
Std.error.write("*** succeeded ***");
153
else
154
Std.error.write("!!! failed !!!");
155
fi
156
157
Std.error.write("\n");
158
159
System.Environment.exit(result);
160
si
161
162
parse_flags(args: Iterable[string]) is
163
assemblies = LIST();
164
ghul_source_files = LIST();
165
166
let args_iterator = args.iterator;
167
168
flags.want_compile_up_to_expressions = true;
169
flags.want_compile_expressions = true;
170
flags.want_assembler = true;
171
flags.want_executable = true;
172
173
let want_type_check = false;
174
let do_not_want_type_check = false;
175
176
let get_next_argument = (a: Collections.Iterator[string]) is
177
a.move_next();
178
return a.current.trim();
179
si;
180
181
let deprecated_no_warn = (flag: string, slug: string) is
182
Std.error.write_line("warning: {flag} is deprecated; use --suppress {slug} instead");
183
container.logger.suppress(slug);
184
si;
185
186
let conditional_defines = Collections.LIST[string]();
187
let emit_boilerplate_il_path: string? mut = null;
188
189
for s in args_iterator do
190
if s =~ "-A" \/ s =~ "--analyse" then
191
flags.want_analyse = true;
192
flags.want_assembler = false;
193
flags.want_executable = false;
194
elif s =~ "-G" \/ s =~ "--type-check" then
195
flags.want_assembler = false;
196
flags.want_executable = false;
197
elif s =~ "-g" \/ s =~ "--no-type-check" then
198
flags.want_compile_up_to_expressions = false;
199
flags.want_compile_expressions = false;
200
flags.want_assembler = false;
201
flags.want_executable = false;
202
elif s =~ "-E" \/ s =~ "--ignore-errors" then
203
flags.ignore_errors = true;
204
elif s =~ "-S" \/ s =~ "--assembler" then
205
flags.want_executable = false;
206
elif s =~ "--library" then
207
flags.want_library = true;
208
elif s =~ "--debug" then
209
flags.want_debug = true;
210
elif s =~ "--define" then
211
conditional_defines.add(get_next_argument(args_iterator));
212
elif s =~ "--entry" then
213
container.ir_context.entry_point_name = get_next_argument(args_iterator);
214
elif s =~ "--v3" then
215
conditional_defines.add("v3");
216
elif s =~ "--test-run" then
217
flags.is_test_run = true;
218
elif s =~ "-N" \/ s =~ "--dotnet" then
219
// do nothing - .NET is the only supported option
220
elif s =~ "-o" \/ s =~ "--output" then
221
output_file_name_generator.force(get_next_argument(args_iterator));
222
elif s =~ "-p" \/ s =~ "--library-prefix" then
223
paths.library_prefix = get_next_argument(args_iterator);
224
elif s =~ "-a" \/ s =~ "--assembly" then
225
assemblies.add(get_next_argument(args_iterator));
226
elif s =~ "--assembly-info-string" then
227
let info_arg = get_next_argument(args_iterator);
228
let info = info_arg.split(['=']);
229
230
if info.count == 2 then
231
assembly_info.add_attribute(info[0].trim(), info[1].trim());
232
else
233
Std.error.write_line("warning: ignoring garbled assembly info: {info_arg}");
234
fi
235
elif s =~ "--version" then
236
let version_string = get_next_argument(args_iterator);
237
238
let version_string_first_part = version_string.split(['-', '+']) |> first();
239
240
let version: System.Version mut;
241
242
try
243
if
244
version_string_first_part?
245
then
246
let version_parts = version_string_first_part.split(['.']);
247
248
if version_parts.count > 4 then
249
throw Exception("invalid version number");
250
fi
251
252
let b = System.Text.StringBuilder();
253
let seen_any mut = false;
254
255
for p in version_parts do
256
if seen_any then
257
b.append(":");
258
fi
259
260
b.append(int.parse(p));
261
262
seen_any = true;
263
od
264
265
for i in version_parts.count..4 do
266
b.append(":0");
267
od
268
269
module_version = b.to_string();
270
else
271
throw Exception("invalid version number");
272
fi
273
274
catch ex: Exception
275
Std.error.write_line("warning: ignoring garbled version number: {version_string}");
276
yrt
277
elif s =~ "--ilasm-packages" then
278
let package_paths = get_next_argument(args_iterator);
279
280
paths.add_ilasm_packages(package_paths.split([';']));
281
elif s =~ "--sdk-ref-path" then
282
paths.sdk_reference_path = get_next_argument(args_iterator);
283
elif s =~ "--target-framework" then
284
paths.target_framework = get_next_argument(args_iterator);
285
elif s =~ "--exclude-runtime-symbols" then
286
flags.exclude_runtime_symbols = true;
287
elif s =~ "--keep-out-il" then
288
flags.want_keep_out_il = true;
289
elif s =~ "--emit-boilerplate-il" then
290
emit_boilerplate_il_path = get_next_argument(args_iterator);
291
elif s =~ "--show-analysis-stats" then
292
flags.want_analysis_stats = true;
293
elif s =~ "--no-analysis-heap-watchdog" then
294
flags.no_analysis_heap_watchdog = true;
295
elif s =~ "--incremental-analysis" then
296
flags.want_incremental_analysis = true;
297
elif s =~ "--global-namespace" then
298
flags.want_global_namespace = true;
299
elif s =~ "--underscore-access" then
300
let mode = get_next_argument(args_iterator);
301
if mode =~ "private" then
302
flags.underscore_access = UnderscoreAccess.PRIVATE;
303
elif mode =~ "protected" then
304
flags.underscore_access = UnderscoreAccess.PROTECTED;
305
elif mode =~ "legacy" then
306
flags.underscore_access = UnderscoreAccess.LEGACY;
307
else
308
Std.error.write_line("warning: unrecognized --underscore-access mode: {mode} (expected legacy, private or protected)");
309
fi
310
elif s =~ "--format" then
311
want_format = true;
312
elif s =~ "--warn-implicit-mutable-let" then
313
Std.error.write_line("warning: --warn-implicit-mutable-let is deprecated; the implicit-mutable-let warning is now an error and the flag has no effect");
314
elif s =~ "--no-warn-definite-return" then
315
deprecated_no_warn("--no-warn-definite-return", "definite-return");
316
elif s =~ "--no-warn-definite-assignment" then
317
deprecated_no_warn("--no-warn-definite-assignment", "definite-assignment");
318
elif s =~ "--no-warn-null-deref" then
319
deprecated_no_warn("--no-warn-null-deref", "null-deref");
320
elif s =~ "--no-warn-non-optional" then
321
deprecated_no_warn("--no-warn-non-optional", "non-optional");
322
elif s =~ "--no-warn-impossible-cast" then
323
deprecated_no_warn("--no-warn-impossible-cast", "impossible-cast");
324
elif s =~ "--no-warn-narrowing-always-succeeds" then
325
deprecated_no_warn("--no-warn-narrowing-always-succeeds", "narrowing-always-succeeds");
326
elif s =~ "--no-warn-likely-override-mismatch" then
327
deprecated_no_warn("--no-warn-likely-override-mismatch", "likely-override-mismatch");
328
elif s =~ "--suppress" then
329
for slug in get_next_argument(args_iterator).split([',']) do
330
let trimmed = slug.trim();
331
if trimmed.length > 0 then
332
container.logger.suppress(trimmed);
333
fi
334
od
335
elif s =~ "--warn-as-error" then
336
for slug in get_next_argument(args_iterator).split([',']) do
337
let trimmed = slug.trim();
338
if trimmed =~ "all" then
339
container.logger.set_all_warnings_are_errors(true);
340
elif trimmed.length > 0 then
341
container.logger.promote_to_error(trimmed);
342
fi
343
od
344
elif s =~ "--warn-as-hint" then
345
for slug in get_next_argument(args_iterator).split([',']) do
346
let trimmed = slug.trim();
347
if trimmed.length > 0 then
348
container.logger.demote_to_hint(trimmed);
349
fi
350
od
351
elif s =~ "--warn-as-info" then
352
for slug in get_next_argument(args_iterator).split([',']) do
353
let trimmed = slug.trim();
354
if trimmed.length > 0 then
355
container.logger.demote_to_info(trimmed);
356
fi
357
od
358
elif s.starts_with('-') then
359
Std.error.write_line("warning: ignoring unknown option: {s}");
360
elif SOURCE_FILE_CATEGORIZER.is_ghul(s) then
361
output_file_name_generator.seen_file(s);
362
ghul_source_files.add(s);
363
else
364
Std.error.write_line("warning: ignoring unrecognized argument: {s}");
365
fi
366
od
367
368
if emit_boilerplate_il_path? then
369
if !File.exists(emit_boilerplate_il_path) then
370
Std.error.write_line("warning: library boilerplate IL file not found: {emit_boilerplate_il_path}");
371
else
372
emit_boilerplate_il = File.read_all_text(emit_boilerplate_il_path);
373
fi
374
fi
375
376
flags.mark_valid();
377
378
if flags.is_test_run then
379
container.want_human_readable_logger(Std.error);
380
fi
381
382
container.conditional_compilation.set_is_enabled(conditional_defines);
383
si
384
385
format_files() is
386
for path in ghul_source_files do
387
try
388
let source_file =
389
compiler.parse(path, File.open_text(path), flags.want_compile_up_to_expressions, flags.want_compile_expressions, false);
390
391
let formatter =
392
Syntax.Process.Printer.FORMATTER(source_file.trivia, 100);
393
394
Std.out.write(formatter.format(source_file.definition));
395
Std.out.flush();
396
catch ex: Exception
397
Std.error.write_line("error: could not format {path}: {ex.message}");
398
yrt
399
od
400
si
401
402
start_build() is
403
queue_source_files();
404
405
if flags.want_assembler then
406
container.value_boxer.want_boxing = true;
407
container.ir_context.enter_file("out.il", true);
408
409
let module_name = Path.get_file_name_without_extension(output_file_name_generator.result) ?? "module";
410
411
container.boilerplate_generator.gen(
412
"header-top",
413
[module_name, module_version]
414
);
415
416
assembly_info.gen();
417
418
container.boilerplate_generator.gen(
419
"header-tail",
420
[module_name]
421
);
422
fi
423
si
424
425
finish_build() -> int is
426
container.logger.write_all_diagnostics(container.logger_writer, container.logger_formatter);
427
428
if container.logger.any_errors /\ !flags.ignore_errors then
429
return 1;
430
elif container.logger.is_poisoned then
431
container.logger.write_poison_messages();
432
433
Std.error.write_line("internal error");
434
return 2;
435
fi
436
437
if flags.want_assembler then
438
return finish_build_dotnet();
439
else
440
return 0;
441
fi
442
si
443
444
finish_build_dotnet() -> int is
445
let ir_context = container.ir_context;
446
let output_file mut = output_file_name_generator.result;
447
448
if emit_boilerplate_il? then
449
container.ir_context.write(emit_boilerplate_il!);
450
fi
451
452
if output_file.last_index_of('.') < 0 then
453
output_file = "{output_file}.{if flags.want_library then "dll" else "exe" fi}";
454
fi
455
456
ir_context.leave_file("out.il");
457
458
if !flags.want_executable then
459
return 0;
460
fi
461
462
let ilasm_path = paths.ilasm_path;
463
464
if !File.exists(ilasm_path) then
465
Std.error.write_line("error: ilasm not found at '{ilasm_path}'");
466
return 0;
467
fi
468
469
let ilasm_args mut =
470
"-quiet out.il -output={output_file}";
471
472
if flags.want_library then
473
ilasm_args = "{ilasm_args} -DLL";
474
fi
475
476
if flags.want_debug then
477
ilasm_args = "{ilasm_args} -debug -pdbfmt=portable";
478
fi
479
480
let ilasm = System.Diagnostics.Process.start(ilasm_path, ilasm_args);
481
482
ilasm.wait_for_exit();
483
484
let result mut = ilasm.exit_code;
485
486
if result != 0 then
487
Std.error.write_line("error: ilasm failed");
488
489
return result;
490
fi
491
492
// A library assembly is loaded into a host's runtime config,
493
// not run directly, so it needs neither its own
494
// `.runtimeconfig.json` nor the executable bit.
495
if !flags.want_library then
496
let runtime_config_file =
497
if output_file.last_index_of('.') == output_file.length - 4 then
498
"{output_file.substring(0, output_file.last_index_of('.'))}.runtimeconfig.json";
499
else
500
"{output_file}.runtimeconfig.json";
501
fi;
502
503
let resolver = TARGET_FRAMEWORK_RESOLVER();
504
let tfm = resolver.resolve_tfm(paths.target_framework, paths.sdk_reference_path, paths.host_target_framework);
505
let framework_version = resolver.resolve_framework_version(tfm);
506
507
let runtime_config = File.create_text(runtime_config_file);
508
runtime_config.write("{{\"runtimeOptions\":{{\"tfm\":\"{tfm}\",\"framework\":{{\"name\":\"Microsoft.NETCore.App\",\"version\":\"{framework_version}\"}} }} }}");
509
runtime_config.close();
510
511
if !RuntimeInformation.is_o_s_platform(OSPlatform.windows) then
512
let chmod = System.Diagnostics.Process.start("/bin/chmod", "+x {output_file}");
513
514
chmod.wait_for_exit();
515
516
result = chmod.exit_code;
517
518
if result != 0 then
519
Std.error.write_line("compiled successfully but failed to set executable bit on resulting binary: {output_file}");
520
fi
521
fi
522
fi
523
524
@IF.release()
525
if !flags.want_keep_out_il then
526
File.delete("out.il");
527
fi
528
529
return result;
530
si
531
532
queue_source_files() is
533
for file in ghul_source_files do
534
if file.ends_with(".ghul") then
535
queue_source_file(file, false);
536
fi
537
od
538
si
539
540
queue_source_file(path: string, is_internal_file: bool) is
541
if flags.want_analyse then
542
analyse_files.add(path);
543
544
return;
545
fi
546
547
let use reader = File.open_text(path);
548
549
compiler.parse_and_queue(path, reader, flags.want_compile_up_to_expressions, flags.want_compile_expressions, is_internal_file);
550
si
551
552
analyse() is
553
analyse_files = LIST();
554
555
container.watchdog.heap_check_disabled = flags.no_analysis_heap_watchdog;
556
557
// The analyser reads protocol frames and EDIT payloads from stdin a
558
// character at a time. Console.In (Std.`in`) buffers only 256 bytes,
559
// so a large EDIT payload costs thousands of read() syscalls. Read
560
// instead through a 256 KB buffer over the raw standard-input
561
// stream: that exceeds every compiler source file (largest ~230 KB,
562
// p99 ~44 KB), so a single-file EDIT is drained in one read() and a
563
// whole-project EDIT in few. A buffered read still returns as soon
564
// as any bytes are available — a fully-received request is never
565
// delayed waiting for the buffer to fill.
566
let analyser_input =
567
IO.StreamReader(
568
Std.open_standard_input(),
569
System.Text.UTF8Encoding(false),
570
false,
571
262144
572
);
573
574
let analyser = ANALYSER(
575
compiler,
576
container.timers,
577
container.symbol_table,
578
container.symbol_use_locations,
579
container.symbol_definition_locations,
580
container.completer,
581
container.signature_help,
582
analyser_input,
583
Std.out,
584
analyse_files,
585
flags,
586
container.watchdog
587
);
588
589
Std.error.write_line("ghūl compiler {compiler_version}: serving analysis requests");
590
591
analyser.run();
592
si
593
si
594
si
595