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
36
// Assemblies named with `--reference`: imported as well as the
37
// reference set, where `-a` names the whole of it.
38
references: LIST[string]
39
40
// Each is `<path>=<logical-name>`, collected from `--resource`
41
// and written into the manifest resource table at the end of
42
// the build.
43
resources: LIST[(path: string, logical_name: string)]
44
45
ghul_source_files: LIST[string]
46
analyse_files: LIST[string]
47
module_version: string
48
assembly_info: Semantic.DotNet.ASSEMBLY_INFO
49
50
want_format: bool
51
52
want_dump_tokens: bool
53
want_compile_server: bool
54
55
// --check-complete parses the one source named and prints whether it
56
// is complete, incomplete or invalid, running no other pass.
57
want_check_complete: bool
58
59
// --format-in-place rewrites each source file rather than printing
60
// to standard output. The write goes through a sibling temporary
61
// file and an atomic rename, so a failure anywhere leaves the
62
// original untouched.
63
want_format_in_place: bool
64
65
// --annotate-inferred prints each named source with the types the
66
// build inferred written in; --annotate-inferred-in-place rewrites
67
// the files instead.
68
want_annotate_inferred: bool
69
70
// --strip-annotations <selection> prints each named source with
71
// the chosen written annotations left out, for a program to be
72
// rebuilt with less than its author wrote; `list` prints the
73
// numbered sites instead, each with the span its annotation
74
// occupies.
75
want_strip_annotations: bool
76
strip_selection: string
77
want_annotate_inferred_in_place: bool
78
79
// Column the formatter wraps at. 100 unless --format-width says otherwise: source is
80
// read in narrower places than an editor - a fixed-width block on a web page, a prose
81
// column beside it - and what fits there is the caller's to say.
82
format_width: int
83
84
compiler_version: string =>
85
let assembly_version =
86
System.Reflection.Assembly
87
.get_entry_assembly()!
88
.get_custom_attributes(typeof System.Reflection.AssemblyInformationalVersionAttribute, false)
89
|> map(va => cast System.Reflection.AssemblyInformationalVersionAttribute?(va)!)
90
|> first()
91
92
in
93
if assembly_version? then
94
"{assembly_version.informational_version}"
95
else
96
"v0.0.0-unknown.1"
97
fi
98
99
entry(arguments: string[]) static is
100
let full_arguments = LIST[string](arguments.count + 1)
101
102
add_arguments(full_arguments, arguments)
103
104
let instance = MAIN(LIST[string](full_arguments))
105
si
106
107
add_arguments(result: LIST[string], source: Iterable[string]) static is
108
let seen_files = SET()
109
110
add_arguments(seen_files, result, source)
111
si
112
113
add_arguments(seen_files: SET[string], result: LIST[string], source: Iterable[string]) static is
114
for argument in source do
115
if argument.starts_with('@') then
116
read_arguments_from_file(seen_files, result, argument.substring(1))
117
elif argument.length > 0 then
118
result.add(argument)
119
fi
120
od
121
si
122
123
read_arguments_from_file(seen_files: SET[string], result: LIST[string], path: string) static is
124
// Keyed on the resolved path rather than on what was written, so
125
// that two spellings of one file - one relative and one absolute,
126
// or one with a redundant ./ - are recognised as the same file and
127
// a cycle between them ends.
128
let key = Path.get_full_path(path)
129
130
if seen_files.contains(key) then
131
return
132
fi
133
134
seen_files.add(key)
135
136
let parser = ARGUMENTS_PARSER()
137
138
let text = File.read_all_text(path)
139
140
let arguments = parser.parse_arguments(text)
141
142
add_arguments(seen_files, result, arguments)
143
si
144
145
// This constructor is the whole program: it sets its state up
146
// inside the try, and the only paths that leave without doing
147
// so report the failure and exit the process.
148
@suppress("field-definite-assignment")
149
init(arguments: LIST[string]) is
150
let result mut = 1
151
152
try
153
Std.input_encoding = System.Text.UTF8Encoding(false)
154
Std.output_encoding = System.Text.UTF8Encoding(false)
155
156
if arguments.count == 0 then
157
Std.out.write("ghūl {compiler_version}\n")
158
Std.out.flush()
159
160
System.Environment.exit(0)
161
fi
162
163
if USAGE.is_requested(arguments) then
164
Std.out.write(USAGE.text)
165
Std.out.flush()
166
167
System.Environment.exit(0)
168
fi
169
170
container = IoC.CONTAINER.instance
171
172
paths = container.path_config
173
flags = container.build_flags
174
compiler = COMPILER()
175
output_file_name_generator = OUTPUT_FILE_NAME_GENERATOR()
176
assembly_info = container.assembly_info
177
module_version = "0:0:0:0"
178
179
parse_flags(arguments)
180
181
if want_check_complete then
182
check_complete()
183
184
System.Environment.exit(0)
185
fi
186
187
check_assemblies_exist()
188
189
IoC.CONTAINER.instance.assemblies.import(assemblies, references)
190
191
if flags.want_analyse then
192
analyse()
193
194
System.Environment.exit(0)
195
fi
196
197
if want_compile_server then
198
serve_compiles()
199
200
System.Environment.exit(0)
201
fi
202
203
if want_dump_tokens then
204
dump_tokens()
205
206
System.Environment.exit(0)
207
fi
208
209
if want_format then
210
format_files()
211
212
System.Environment.exit(0)
213
fi
214
215
if want_strip_annotations then
216
strip_annotations_files()
217
218
System.Environment.exit(0)
219
fi
220
221
// A warning rather than an error: every ghul.runtime release so
222
// far passes AssemblyMetadataAttribute a single string, and a
223
// build using one must not start failing. The attribute is left
224
// out either way, so nothing refers to a missing constructor.
225
for problem in assembly_info.resolve(name => IoC.CONTAINER.instance.assemblies.find_type(name)) do
226
container.logger.warn(Source.LOCATION.internal, Semantic.DotNet.ASSEMBLY_INFO.UNMATCHED, problem)
227
od
228
229
start_build()
230
231
compiler.post_parse()
232
233
compiler.build()
234
235
result = finish_build()
236
237
if want_annotate_inferred /\ result == 0 then
238
annotate_inferred_files()
239
fi
240
catch e: INPUT_EXCEPTION
241
Std.error.write_line("error: {e.message}")
242
Std.error.flush()
243
244
result = 1
245
catch e: Exception
246
Std.error.write(e)
247
Std.error.write("\n")
248
Std.error.flush()
249
250
result = 1
251
yrt
252
253
if result == 0 then
254
Std.error.write("*** succeeded ***")
255
else
256
Std.error.write("!!! failed !!!")
257
fi
258
259
Std.error.write("\n")
260
261
System.Environment.exit(result)
262
si
263
264
// Every -a names an assembly the compiler is to read. A path that is
265
// not there is always a mistake in what supplied it - nothing is
266
// compiled more successfully for having skipped one - so it is
267
// reported rather than left to surface as whatever the load happens to
268
// throw, or worse, as the missing symbols of a reference that was
269
// silently dropped.
270
check_assemblies_exist() is
271
let message = missing_assemblies_message(assemblies |> cat(references))
272
273
if message? then
274
throw INPUT_EXCEPTION(message)
275
fi
276
si
277
278
// The message naming the assembly paths that are not there, or null
279
// when they all are.
280
missing_assemblies_message(paths: Iterable[string]) -> string? static is
281
let missing = LIST[string]()
282
283
for path in paths do
284
if !File.exists(path) then
285
missing.add(path)
286
fi
287
od
288
289
if missing.count == 0 then
290
return null
291
fi
292
293
let names = missing |> join(", ")
294
295
return
296
if missing.count == 1 then
297
"reference assembly not found: {names}"
298
else
299
"reference assemblies not found: {names}"
300
fi
301
si
302
303
// The value a flag takes from the argument after it.
304
next_flag_value(arguments: Collections.Iterator[string], flag: string) -> string static is
305
if !arguments.move_next() then
306
throw INPUT_EXCEPTION("{flag} needs a value")
307
fi
308
309
return arguments.current.trim()
310
si
311
312
parse_flags(args: Iterable[string]) is
313
assemblies = LIST()
314
references = LIST()
315
resources = LIST()
316
ghul_source_files = LIST()
317
318
format_width = 100
319
320
let args_iterator = args.iterator
321
322
flags.want_compile_up_to_expressions = true
323
flags.want_compile_expressions = true
324
flags.want_assembler = true
325
flags.want_executable = true
326
327
let want_type_check = false
328
let do_not_want_type_check = false
329
330
let deprecated_no_warn = (flag: string, slug: string) is
331
Std.error.write_line("warning: {flag} is deprecated; use --suppress {slug} instead")
332
container.logger.suppress(slug)
333
si
334
335
let conditional_defines = Collections.LIST[string]()
336
337
for s in args_iterator do
338
if s =~ "-A" \/ s =~ "--analyse" then
339
flags.want_analyse = true
340
flags.want_assembler = false
341
flags.want_executable = false
342
elif s =~ "-G" \/ s =~ "--type-check" then
343
flags.want_assembler = false
344
flags.want_executable = false
345
elif s =~ "-g" \/ s =~ "--no-type-check" then
346
flags.want_compile_up_to_expressions = false
347
flags.want_compile_expressions = false
348
flags.want_assembler = false
349
flags.want_executable = false
350
elif s =~ "-E" \/ s =~ "--ignore-errors" then
351
flags.ignore_errors = true
352
elif s =~ "-S" \/ s =~ "--assembler" then
353
flags.want_executable = false
354
elif s =~ "--library" then
355
flags.want_library = true
356
elif s =~ "--debug" then
357
flags.want_debug = true
358
elif s =~ "--trace-inference" then
359
Semantic.INFERENCE_TRACE.enable("-")
360
elif s =~ "--trace-inference-file" then
361
Semantic.INFERENCE_TRACE.enable(next_flag_value(args_iterator, s))
362
elif s =~ "--define" then
363
conditional_defines.add(next_flag_value(args_iterator, s))
364
elif s =~ "--entry" then
365
container.ir_context.entry_point_name = next_flag_value(args_iterator, s)
366
container.ir_context.entry_point_name_is_explicit = true
367
elif s =~ "--v3" then
368
conditional_defines.add("v3")
369
elif s =~ "--test-run" then
370
flags.is_test_run = true
371
elif s =~ "--keep-duplicate-diagnostics" then
372
flags.keep_duplicate_diagnostics = true
373
elif s =~ "--msbuild-diagnostics" then
374
flags.want_msbuild_diagnostics = true
375
elif s =~ "-N" \/ s =~ "--dotnet" then
376
// do nothing - .NET is the only supported option
377
elif s =~ "-o" \/ s =~ "--output" then
378
output_file_name_generator.force(next_flag_value(args_iterator, s))
379
elif s =~ "-p" \/ s =~ "--library-prefix" then
380
paths.library_prefix = next_flag_value(args_iterator, s)
381
elif s =~ "-a" \/ s =~ "--assembly" then
382
assemblies.add(next_flag_value(args_iterator, s))
383
elif s =~ "--reference" then
384
references.add(next_flag_value(args_iterator, s))
385
elif s =~ "--resource" then
386
let resource_arg = next_flag_value(args_iterator, s)
387
let separator_index = resource_arg.last_index_of('=')
388
389
if separator_index > 0 /\ separator_index < resource_arg.length - 1 then
390
resources.add((
391
path = resource_arg.substring(0, separator_index),
392
logical_name = resource_arg.substring(separator_index + 1)))
393
else
394
Std.error.write_line("warning: ignoring garbled --resource value: {resource_arg}")
395
fi
396
elif s =~ "--assembly-info-string" then
397
let info_arg = next_flag_value(args_iterator, s)
398
let separator_index = info_arg.index_of('=')
399
400
if separator_index > 0 then
401
assembly_info.add_attribute(
402
info_arg.substring(0, separator_index).trim(),
403
info_arg.substring(separator_index + 1).trim())
404
else
405
Std.error.write_line("warning: ignoring garbled assembly info: {info_arg}")
406
fi
407
elif s =~ "--version" then
408
let version_string = next_flag_value(args_iterator, s)
409
410
let version_string_first_part = version_string.split(['-', '+']) |> first()
411
412
let version: System.Version mut
413
414
try
415
if
416
version_string_first_part?
417
then
418
let version_parts = version_string_first_part.split(['.'])
419
420
if version_parts.count > 4 then
421
throw Exception("invalid version number")
422
fi
423
424
let b = System.Text.StringBuilder()
425
let seen_any mut = false
426
427
for p in version_parts do
428
if seen_any then
429
b.append(":")
430
fi
431
432
b.append(int.parse(p))
433
434
seen_any = true
435
od
436
437
for i in version_parts.count..4 do
438
b.append(":0")
439
od
440
441
module_version = b.to_string()
442
else
443
throw Exception("invalid version number")
444
fi
445
446
catch ex: Exception
447
Std.error.write_line("warning: ignoring garbled version number: {version_string}")
448
yrt
449
elif s =~ "--sdk-ref-path" then
450
paths.sdk_reference_path = next_flag_value(args_iterator, s)
451
elif s =~ "--target-framework" then
452
paths.target_framework = next_flag_value(args_iterator, s)
453
elif s =~ "--exclude-runtime-symbols" then
454
flags.exclude_runtime_symbols = true
455
elif s =~ "--show-analysis-stats" then
456
flags.want_analysis_stats = true
457
elif s =~ "--no-analysis-heap-watchdog" then
458
flags.no_analysis_heap_watchdog = true
459
elif s =~ "--incremental-analysis" then
460
flags.want_incremental_analysis = true
461
elif s =~ "--no-incremental-analysis" then
462
flags.want_incremental_analysis = false
463
elif s =~ "--analysis-idle-timeout" then
464
let seconds_string = next_flag_value(args_iterator, s)
465
466
try
467
flags.analysis_idle_timeout_seconds = int.parse(seconds_string)
468
catch ex: Exception
469
Std.error.write_line("warning: ignoring garbled --analysis-idle-timeout value: {seconds_string}")
470
yrt
471
elif s =~ "--default-use" then
472
for name in next_flag_value(args_iterator, s).split([',']) do
473
let trimmed = name.trim()
474
475
if trimmed.length > 0 then
476
flags.default_uses.add(trimmed)
477
fi
478
od
479
elif s =~ "--implicit-default-use" then
480
flags.want_implicit_default_use = true
481
elif s =~ "--submission" then
482
flags.submission_name = next_flag_value(args_iterator, s)
483
elif s =~ "--global-namespace" then
484
flags.want_global_namespace = true
485
elif s =~ "--underscore-access" then
486
let mode = next_flag_value(args_iterator, s)
487
if mode =~ "private" then
488
flags.underscore_access = UnderscoreAccess.PRIVATE
489
elif mode =~ "protected" then
490
flags.underscore_access = UnderscoreAccess.PROTECTED
491
elif mode =~ "legacy" then
492
flags.underscore_access = UnderscoreAccess.LEGACY
493
else
494
Std.error.write_line("warning: unrecognized --underscore-access mode: {mode} (expected legacy, private or protected)")
495
fi
496
elif s =~ "--compile-server" then
497
want_compile_server = true
498
elif s =~ "--dump-tokens" then
499
want_dump_tokens = true
500
elif s =~ "--check-complete" then
501
want_check_complete = true
502
elif s =~ "--format" then
503
want_format = true
504
elif s =~ "--format-in-place" then
505
want_format = true
506
want_format_in_place = true
507
elif s =~ "--strip-annotations" then
508
want_strip_annotations = true
509
strip_selection = next_flag_value(args_iterator, s)
510
elif s =~ "--annotate-inferred" then
511
want_annotate_inferred = true
512
flags.want_assembler = false
513
flags.want_executable = false
514
elif s =~ "--annotate-inferred-in-place" then
515
want_annotate_inferred = true
516
want_annotate_inferred_in_place = true
517
flags.want_assembler = false
518
flags.want_executable = false
519
elif s =~ "--format-width" then
520
let width = next_flag_value(args_iterator, s)
521
522
try
523
format_width = int.parse(width)
524
525
if format_width < 20 then
526
Std.error.write_line("warning: --format-width {format_width} is too narrow to lay anything out; using 20")
527
528
format_width = 20
529
fi
530
catch ex: Exception
531
Std.error.write_line("warning: --format-width expects a number, not '{width}'")
532
yrt
533
elif s =~ "--warn-implicit-mutable-let" then
534
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")
535
elif s =~ "--no-warn-definite-return" then
536
deprecated_no_warn("--no-warn-definite-return", "definite-return")
537
elif s =~ "--no-warn-definite-assignment" then
538
deprecated_no_warn("--no-warn-definite-assignment", "definite-assignment")
539
elif s =~ "--no-warn-non-optional" then
540
deprecated_no_warn("--no-warn-non-optional", "non-optional")
541
elif s =~ "--no-warn-impossible-cast" then
542
deprecated_no_warn("--no-warn-impossible-cast", "impossible-cast")
543
elif s =~ "--no-warn-narrowing-always-succeeds" then
544
deprecated_no_warn("--no-warn-narrowing-always-succeeds", "narrowing-always-succeeds")
545
elif s =~ "--no-warn-likely-override-mismatch" then
546
deprecated_no_warn("--no-warn-likely-override-mismatch", "likely-override-mismatch")
547
elif s =~ "--suppress" then
548
for slug in next_flag_value(args_iterator, s).split([',']) do
549
let trimmed = slug.trim()
550
if trimmed.length > 0 then
551
container.logger.suppress(trimmed)
552
fi
553
od
554
elif s =~ "--warn-as-error" then
555
for slug in next_flag_value(args_iterator, s).split([',']) do
556
let trimmed = slug.trim()
557
if trimmed =~ "all" then
558
container.logger.set_all_warnings_are_errors(true)
559
elif trimmed.length > 0 then
560
container.logger.promote_to_error(trimmed)
561
fi
562
od
563
elif s =~ "--warn-as-hint" then
564
for slug in next_flag_value(args_iterator, s).split([',']) do
565
let trimmed = slug.trim()
566
if trimmed.length > 0 then
567
container.logger.demote_to_hint(trimmed)
568
fi
569
od
570
elif s =~ "--warn-as-info" then
571
for slug in next_flag_value(args_iterator, s).split([',']) do
572
let trimmed = slug.trim()
573
if trimmed.length > 0 then
574
container.logger.demote_to_info(trimmed)
575
fi
576
od
577
elif s =~ "--warn" then
578
for slug in next_flag_value(args_iterator, s).split([',']) do
579
let trimmed = slug.trim()
580
if trimmed.length > 0 then
581
container.logger.unsuppress(trimmed)
582
fi
583
od
584
elif s =~ "--inlay" then
585
for kind in next_flag_value(args_iterator, s).split([',']) do
586
let trimmed = kind.trim()
587
588
if trimmed.length > 0 then
589
if Logging.INLAY_KINDS.is_known(trimmed) then
590
container.logger.enable_inlay_kind(trimmed)
591
else
592
Std.error.write_line("warning: ignoring unknown inlay kind: {trimmed}")
593
fi
594
fi
595
od
596
elif s =~ "--no-inlay" then
597
for kind in next_flag_value(args_iterator, s).split([',']) do
598
let trimmed = kind.trim()
599
600
if trimmed.length > 0 then
601
if Logging.INLAY_KINDS.is_known(trimmed) then
602
container.logger.disable_inlay_kind(trimmed)
603
else
604
Std.error.write_line("warning: ignoring unknown inlay kind: {trimmed}")
605
fi
606
fi
607
od
608
elif s.starts_with('-') then
609
Std.error.write_line("warning: ignoring unknown option: {s}")
610
elif SOURCE_FILE_CATEGORIZER.is_ghul(s) then
611
output_file_name_generator.seen_file(s)
612
ghul_source_files.add(s)
613
elif IO.Directory.exists(s) then
614
ghul_source_files.add(s)
615
else
616
Std.error.write_line("warning: ignoring unrecognized argument: {s}")
617
fi
618
od
619
620
flags.mark_valid()
621
622
if flags.want_msbuild_diagnostics then
623
container.want_msbuild_logger(Std.error)
624
elif flags.is_test_run then
625
container.want_human_readable_logger(Std.error)
626
container.want_duplicate_diagnostics()
627
fi
628
629
if flags.keep_duplicate_diagnostics then
630
container.want_duplicate_diagnostics()
631
fi
632
633
container.conditional_compilation.set_is_enabled(conditional_defines)
634
si
635
636
// One word on standard output, and a zero exit status whatever the
637
// answer: a non-zero status means the check itself could not run.
638
check_complete() is
639
if ghul_source_files.count != 1 then
640
throw INPUT_EXCEPTION("--check-complete takes exactly one source file")
641
fi
642
643
let path = ghul_source_files[0]
644
645
let completeness = compiler.check_complete(path, File.open_text(path))
646
647
Std.out.write_line(
648
case completeness
649
when Syntax.Parsers.COMPLETENESS.COMPLETE then "complete"
650
when Syntax.Parsers.COMPLETENESS.INCOMPLETE then "incomplete"
651
else "invalid"
652
esac
653
)
654
655
Std.out.flush()
656
si
657
658
dump_tokens() is
659
for path in expand_format_sources() do
660
try
661
let tokenizer = Lexical.TOKENIZER(
662
IoC.CONTAINER.instance.logger,
663
path,
664
File.open_text(path),
665
false
666
)
667
668
let pair mut = tokenizer.read_token()
669
670
while pair.token != Lexical.TOKEN.END_OF_INPUT do
671
let value = pair.value_string
672
.replace("\\", "\\\\")
673
.replace("\t", "\\t")
674
.replace("\n", "\\n")
675
.replace("\r", "\\r")
676
677
Std.out.write_line("{path}\t{pair.location.start_line}\t{pair.location.start_column}\t{pair.location.end_line}\t{pair.location.end_column}\t{pair.token}\t{value}")
678
679
pair = tokenizer.read_token()
680
od
681
catch ex: Exception
682
Std.error.write_line("error: could not tokenize {path}: {ex.message}")
683
yrt
684
od
685
686
Std.out.flush()
687
si
688
689
format_files() is
690
for path in expand_format_sources() do
691
try
692
let source_file =
693
compiler.parse(path, File.open_text(path), flags.want_compile_up_to_expressions, flags.want_compile_expressions, false)
694
695
let formatter =
696
Syntax.Process.Printer.FORMATTER(source_file.trivia, format_width)
697
698
let formatted = formatter.format(source_file.definition)
699
700
if want_format_in_place then
701
write_formatted_in_place(path, formatted)
702
else
703
Std.out.write(formatted)
704
Std.out.flush()
705
fi
706
catch ex: Exception
707
Std.error.write_line("error: could not format {path}: {ex.message}")
708
yrt
709
od
710
si
711
712
// Each named source printed with the selected written annotations
713
// left out. Under `list` the numbered sites go to standard output
714
// in place of the source; otherwise the source goes there and the
715
// sites to standard error, one per line.
716
strip_annotations_files() is
717
for path in expand_format_sources() do
718
let source_file =
719
compiler.parse(path, File.open_text(path), false, false, false)
720
721
let formatter =
722
Syntax.Process.Printer.FORMATTER(source_file.trivia, format_width)
723
724
let stripper = Syntax.Process.Printer.ANNOTATION_STRIPPER(strip_selection)
725
726
formatter.stripper = stripper
727
728
let formatted = formatter.format(source_file.definition)
729
730
if strip_selection =~ "list" then
731
for site in stripper.sites do
732
Std.out.write_line(site)
733
od
734
else
735
Std.out.write(formatted)
736
737
for site in stripper.sites do
738
Std.error.write_line(site)
739
od
740
fi
741
742
Std.out.flush()
743
Std.error.flush()
744
od
745
si
746
747
// Each named source printed with the types the build settled
748
// at its inferred sites. The built tree is not what is printed:
749
// the passes splice nodes the source never had, so the types are
750
// collected from it by position and written into a fresh parse.
751
annotate_inferred_files() is
752
let annotated mut = 0
753
let skipped mut = 0
754
755
for source_file in compiler.source_files do
756
let path = source_file.file_name
757
758
if !(ghul_source_files |> any(f => f =~ path)) then
759
continue
760
fi
761
762
let annotations =
763
Syntax.Process.INFERRED_ANNOTATIONS(container.logger, container.symbol_table, container.namespaces)
764
765
annotations.collect(source_file.definition)
766
767
annotated = annotated + annotations.annotated
768
skipped = skipped + annotations.skipped
769
770
let use reader = File.open_text(path)
771
772
let fresh =
773
compiler.parse(path, reader, false, false, false, Logging.DIAGNOSTICS_STORE())
774
775
let formatter =
776
Syntax.Process.Printer.FORMATTER(fresh.trivia, format_width)
777
778
formatter.annotations = annotations
779
780
let formatted = formatter.format(fresh.definition)
781
782
if want_annotate_inferred_in_place then
783
write_formatted_in_place(path, formatted)
784
else
785
Std.out.write(formatted)
786
Std.out.flush()
787
fi
788
od
789
790
Std.error.write_line("annotated {annotated} inferred sites, skipped {skipped}")
791
si
792
793
// Under --format a directory argument names a tree of sources: every
794
// .ghul file beneath it, recursively.
795
expand_format_sources() -> LIST[string] is
796
let paths = LIST[string]()
797
798
for path in ghul_source_files do
799
if IO.Directory.exists(path) then
800
for file in IO.Directory.get_files(path, "*.ghul", IO.SearchOption.ALL_DIRECTORIES) do
801
paths.add(file)
802
od
803
else
804
paths.add(path)
805
fi
806
od
807
808
return paths
809
si
810
811
// Write through a sibling temporary file and rename it over the
812
// original: the rename is atomic, so a failure anywhere before it
813
// leaves the original untouched rather than truncated.
814
write_formatted_in_place(path: string, contents: string) is
815
let temporary = "{path}.tmp"
816
817
File.write_all_text(temporary, contents)
818
File.move(temporary, path, true)
819
si
820
821
start_build() is
822
queue_source_files()
823
824
if flags.want_assembler then
825
container.value_boxer.want_boxing = true
826
fi
827
828
let module_name =
829
Path.get_file_name_without_extension(output_file_name_generator.result) ?? "module"
830
831
install_assembly_emitter(module_name)
832
si
833
834
finish_build() -> int is
835
container.logger.write_all_diagnostics(container.logger_writer, container.logger_formatter)
836
837
if container.logger.any_errors /\ !flags.ignore_errors then
838
return 1
839
elif container.logger.is_poisoned then
840
container.logger.write_poison_messages()
841
842
Std.error.write_line("internal error")
843
return 2
844
fi
845
846
if flags.want_assembler then
847
return finish_build_dotnet()
848
else
849
return 0
850
fi
851
si
852
853
finish_build_dotnet() -> int is
854
let output_file mut = output_file_name_generator.result
855
856
if output_file.last_index_of('.') < 0 then
857
output_file = "{output_file}.{if flags.want_library then "dll" else "exe" fi}"
858
fi
859
860
if !flags.want_executable then
861
return 0
862
fi
863
864
for r in resources do
865
container.ir_context.srm_assembly_emitter.add_resource(
866
r.logical_name,
867
File.read_all_bytes(r.path))
868
od
869
870
container.ir_context.srm_assembly_emitter.write_to(output_file)
871
872
let result mut = 0
873
874
// A library assembly is loaded into a host's runtime config,
875
// not run directly, so it needs neither its own
876
// `.runtimeconfig.json` nor the executable bit.
877
if !flags.want_library then
878
let runtime_config_file =
879
if output_file.last_index_of('.') == output_file.length - 4 then
880
"{output_file.substring(0, output_file.last_index_of('.'))}.runtimeconfig.json"
881
else
882
"{output_file}.runtimeconfig.json"
883
fi
884
885
let resolver = TARGET_FRAMEWORK_RESOLVER()
886
let tfm = resolver.resolve_tfm(paths.target_framework, paths.sdk_reference_path, paths.host_target_framework)
887
let framework_version = resolver.resolve_framework_version(tfm)
888
889
let runtime_config = File.create_text(runtime_config_file)
890
runtime_config.write("{{\"runtimeOptions\":{{\"tfm\":\"{tfm}\",\"framework\":{{\"name\":\"Microsoft.NETCore.App\",\"version\":\"{framework_version}\"}} }} }}")
891
runtime_config.close()
892
893
if !RuntimeInformation.is_o_s_platform(OSPlatform.windows) then
894
let chmod = System.Diagnostics.Process.start("/bin/chmod", "+x {output_file}")
895
896
chmod.wait_for_exit()
897
898
result = chmod.exit_code
899
900
if result != 0 then
901
Std.error.write_line("compiled successfully but failed to set executable bit on resulting binary: {output_file}")
902
fi
903
fi
904
fi
905
906
return result
907
si
908
909
queue_source_files() is
910
for file in ghul_source_files do
911
if file.ends_with(".ghul") then
912
queue_source_file(file, false)
913
elif IO.Directory.exists(file) then
914
Std.error.write_line(
915
"warning: ignoring directory argument {file}: a directory is expanded only by --format")
916
fi
917
od
918
si
919
920
queue_source_file(path: string, is_internal_file: bool) is
921
if flags.want_analyse then
922
analyse_files.add(path)
923
924
return
925
fi
926
927
let use reader = File.open_text(path)
928
929
compiler.parse_and_queue(path, reader, flags.want_compile_up_to_expressions, flags.want_compile_expressions, is_internal_file)
930
si
931
932
// Every mode that runs the passes needs an emitter, whether or not
933
// it writes an assembly: a pass's prologue runs before the pass
934
// decides it has nothing to emit, so analysis reaches one too. It
935
// is installed from here rather than built with the context,
936
// because the emitter is reached through the container the
937
// context is being built into.
938
install_assembly_emitter(module_name: string) is
939
container.ir_context.install_srm_assembly_emitter(
940
module_name, module_version, flags.want_library)
941
si
942
943
serve_compiles() is
944
COMPILE_SERVER(compiler, container, module_version, Std.`in, Std.out).serve()
945
si
946
947
analyse() is
948
// Analysis names no output, and writes none: this emitter is
949
// what the passes hold, not something they fill.
950
install_assembly_emitter("module")
951
952
analyse_files = LIST()
953
954
container.watchdog.heap_check_disabled = flags.no_analysis_heap_watchdog
955
956
// The analyser reads protocol frames and EDIT payloads from stdin a
957
// character at a time. Console.In (Std.`in`) buffers only 256 bytes,
958
// so a large EDIT payload costs thousands of read() syscalls. Read
959
// instead through a 256 KB buffer over the raw standard-input
960
// stream: that exceeds every compiler source file (largest ~230 KB,
961
// p99 ~44 KB), so a single-file EDIT is drained in one read() and a
962
// whole-project EDIT in few. A buffered read still returns as soon
963
// as any bytes are available — a fully-received request is never
964
// delayed waiting for the buffer to fill.
965
let analyser_input =
966
IO.StreamReader(
967
Std.open_standard_input(),
968
System.Text.UTF8Encoding(false),
969
false,
970
262144
971
)
972
973
let analyser = ANALYSER(
974
compiler,
975
container.timers,
976
container.symbol_table,
977
container.symbol_use_locations,
978
container.symbol_definition_locations,
979
container.completer,
980
container.signature_help,
981
analyser_input,
982
Std.out,
983
analyse_files,
984
flags,
985
container.watchdog
986
)
987
988
Std.error.write_line("ghūl compiler {compiler_version}: serving analysis requests")
989
990
analyser.run()
991
si
992
si
993
si
994