@asmlift/core 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +34 -30
  2. package/package.json +1 -1
  3. package/src/aggregate.ts +248 -0
  4. package/src/backend/c.ts +5 -2
  5. package/src/backend/cfamily.ts +236 -37
  6. package/src/backend/cpp.ts +114 -9
  7. package/src/backend/pascal.ts +39 -0
  8. package/src/cdecl/constant.ts +59 -0
  9. package/src/cdecl/lex.ts +209 -0
  10. package/src/cdecl/parse.ts +975 -0
  11. package/src/cdecl/spell.ts +69 -0
  12. package/src/codegen-flags.ts +38 -6
  13. package/src/compiler-diagnostics.ts +194 -0
  14. package/src/contracts.ts +615 -79
  15. package/src/declare.ts +75 -5
  16. package/src/frontend/asmdata.ts +19 -5
  17. package/src/frontend/call-plan.ts +686 -0
  18. package/src/frontend/device-pins.ts +277 -0
  19. package/src/frontend/errors.ts +5 -0
  20. package/src/frontend/flags-edge.ts +101 -0
  21. package/src/frontend/fpu.ts +167 -0
  22. package/src/frontend/frame-objects.ts +2131 -0
  23. package/src/frontend/high-half.ts +2 -1
  24. package/src/frontend/local-object.ts +490 -0
  25. package/src/frontend/mips.ts +363 -40
  26. package/src/frontend/opaque.ts +97 -18
  27. package/src/frontend/ppc.ts +520 -129
  28. package/src/frontend/reloc-symbol.ts +94 -13
  29. package/src/frontend/splat.ts +108 -5
  30. package/src/frontend/ssa.ts +669 -76
  31. package/src/frontend/stackargs.ts +120 -39
  32. package/src/frontend/thumb.ts +2764 -2018
  33. package/src/ir/alias.ts +29 -0
  34. package/src/ir/core.ts +101 -3
  35. package/src/ir/discipline.ts +248 -0
  36. package/src/ir/float-bits.ts +47 -0
  37. package/src/ir/opcodes.ts +84 -112
  38. package/src/ir/parse.ts +1 -0
  39. package/src/ir/types.ts +118 -2
  40. package/src/ir/verify.ts +150 -3
  41. package/src/l3/address.ts +5 -0
  42. package/src/l3/argcopy.ts +38 -6
  43. package/src/l3/ast.ts +186 -14
  44. package/src/l3/coalesce.ts +21 -13
  45. package/src/l3/dce.ts +32 -10
  46. package/src/l3/gates.ts +9 -3
  47. package/src/l3/hoist.ts +8 -7
  48. package/src/l3/initfirst.ts +27 -9
  49. package/src/l3/intcell.ts +97 -0
  50. package/src/l3/mentions.ts +10 -4
  51. package/src/l3/narrowdecl.ts +218 -0
  52. package/src/l3/nearbase.ts +10 -5
  53. package/src/l3/parkfirst.ts +8 -5
  54. package/src/l3/pollguard.ts +3 -2
  55. package/src/l3/ptrcell.ts +357 -0
  56. package/src/l3/regspell.ts +17 -3
  57. package/src/l3/reindex.ts +10 -7
  58. package/src/l3/symbol-refs.ts +104 -23
  59. package/src/l3/tailmerge.ts +6 -19
  60. package/src/l3/typing.ts +137 -25
  61. package/src/l3/unreduce.ts +4 -4
  62. package/src/l3/volstore.ts +96 -7
  63. package/src/mangle.ts +11 -1
  64. package/src/pattern/engine.ts +19 -16
  65. package/src/pipeline.ts +57 -12
  66. package/src/proto-context.ts +788 -0
  67. package/src/proto.ts +689 -50
  68. package/src/raise/arrays.ts +4 -3
  69. package/src/raise/declared-volatile.ts +131 -0
  70. package/src/raise/divpow2.ts +2 -2
  71. package/src/raise/errors.ts +17 -0
  72. package/src/raise/floathelpers.ts +243 -0
  73. package/src/raise/globalshape.ts +11 -5
  74. package/src/raise/gvn.ts +26 -5
  75. package/src/raise/narrow.ts +29 -2
  76. package/src/raise/narrowlocal.ts +11 -6
  77. package/src/raise/offsetnames.ts +510 -0
  78. package/src/raise/pairparams.ts +81 -0
  79. package/src/raise/paramwidth.ts +7 -0
  80. package/src/raise/pre-recovery.ts +78 -4
  81. package/src/raise/recover.ts +14 -10
  82. package/src/raise/retsink.ts +1 -1
  83. package/src/raise/shortcircuit.ts +41 -19
  84. package/src/raise/softdiv.ts +21 -23
  85. package/src/raise/struct-arrays.ts +5 -4
  86. package/src/raise/structs.ts +311 -98
  87. package/src/raise/truncload.ts +366 -0
  88. package/src/raise/widehelpers.ts +106 -0
  89. package/src/rank-declare.ts +30 -5
  90. package/src/rank-variations.ts +36 -8
  91. package/src/rank.ts +276 -80
  92. package/src/runtime-helpers.ts +287 -0
  93. package/src/stillborn.ts +289 -0
  94. package/src/structure/analysis.ts +1285 -448
  95. package/src/structure/hazards.ts +633 -58
  96. package/src/structure/local-statics.ts +381 -0
  97. package/src/structure/namecoalesce.ts +1 -1
  98. package/src/structure/redundant-test.ts +99 -0
  99. package/src/structure/structure.ts +1772 -395
  100. package/src/structure/switch-recover.ts +159 -78
  101. package/src/symbols.ts +38 -3
  102. package/src/target.ts +662 -59
  103. package/src/trace.ts +44 -24
  104. package/src/variation-definitions.ts +119 -1
  105. package/src/variation-tokens.ts +20 -6
@@ -0,0 +1,69 @@
1
+ // asmlift — the PRINTER of what the parser reads: a declared type in asmlift's type vocabulary
2
+ // (`ParamType`): `u8 *`, `void (*)(s32 channel)`, `f32 (*)[3]`, and every run of tokens a spelling
3
+ // holds, as written.
4
+ //
5
+ // The derivations come from the parser (`parse.ts`), listed from the declarator's name outward, so
6
+ // the printer rebuilds the abstract declarator from the inside: a pointer is written ahead of what it
7
+ // binds, an array or a function after it, and a pointer that an array or a function binds next is
8
+ // grouped in parentheses.
9
+ //
10
+ // A RUN OF TOKENS IS PRINTED AS WRITTEN, one space wherever the source separates two of them: a type's
11
+ // name (`A::B`, `TVec3<const u8 *>`), a member pointer's class, a function's parameter list and an
12
+ // array's extent alike. A function pointer's own parameters are part of its spelling, not types this
13
+ // reads.
14
+ import type { Tokens } from './lex';
15
+ import type { DeclaredType, Range } from './parse';
16
+
17
+ /** The type `t` spells: its qualifiers, its base and its abstract declarator, `const u8 *`. */
18
+ export function spellType(t: DeclaredType, tokens: Tokens): string {
19
+ const base = t.qualifiers.length === 0 ? t.spelling : [...t.qualifiers, t.spelling].filter((w) => w !== '').join(' ');
20
+ let declarator = '';
21
+ // whether the last derivation was written ahead: an array or a function after it groups it in parentheses
22
+ let prefixed = false;
23
+ // whether the declarator is only the extents and lists written straight after the absent name
24
+ let suffixes = true;
25
+ for (const d of t.derivations) {
26
+ if (d.kind === 'pointer' || d.kind === 'reference') {
27
+ const own =
28
+ d.kind === 'reference' ? '&' : [`${d.member !== undefined ? `${d.member}::` : ''}*`, ...d.qualifiers].join(' ');
29
+ // a pointer is a word kept apart from what it binds, `u8 * *`, unless that is the name's own suffix
30
+ declarator = declarator === '' || suffixes ? own + declarator : `${own} ${declarator}`;
31
+ prefixed = true;
32
+ suffixes = false;
33
+ } else {
34
+ const grouped = prefixed ? `(${declarator})` : declarator;
35
+ declarator =
36
+ d.kind === 'array'
37
+ ? `${grouped}[${d.size === undefined ? '' : written(tokens, d.size)}]`
38
+ : `${grouped}(${written(tokens, d.list)})`;
39
+ prefixed = false;
40
+ }
41
+ }
42
+ return declarator === '' ? base : `${base} ${declarator}`;
43
+ }
44
+
45
+ /** A parameter's type as C adjusts it: an array nearest the name is a pointer to what it holds, and a
46
+ * function nearest the name a pointer to it. `f32 m[2][3]` is `f32 (*)[3]`, and `int fn(int)` is
47
+ * `int (*)(int)`. */
48
+ export function asParameter(t: DeclaredType): DeclaredType {
49
+ const [first, ...rest] = t.derivations;
50
+ if (first?.kind === 'array') {
51
+ return { ...t, derivations: [{ kind: 'pointer', qualifiers: [] }, ...rest] };
52
+ }
53
+ if (first?.kind === 'function') {
54
+ return { ...t, derivations: [{ kind: 'pointer', qualifiers: [] }, ...t.derivations] };
55
+ }
56
+ return t;
57
+ }
58
+
59
+ /** The tokens of `r` as written, one space wherever the source separates two of them. */
60
+ export function written(tokens: Tokens, r: Range): string {
61
+ let out = '';
62
+ for (let k = r.from; k < r.to; k++) {
63
+ if (k > r.from && tokens.start(k) > tokens.end(k - 1)) {
64
+ out += ' ';
65
+ }
66
+ out += tokens.text(k);
67
+ }
68
+ return out;
69
+ }
@@ -117,6 +117,9 @@ interface OptionSpec {
117
117
  /** the value the compiler acts on, from the spelled one (the argument, else the pattern's last
118
118
  * capture group, else empty) */
119
119
  value?: (spelled: string, m: Match) => string;
120
+ /** an inert word that makes the compiler STOP REPORTING after a number of errors; a run that
121
+ * reads a rejection compiles without it (cli flags.ts) */
122
+ limitsErrors?: true;
120
123
  }
121
124
 
122
125
  const toggle = (_: string, m: Match) => (m[1] ? 'off' : 'on');
@@ -140,6 +143,12 @@ const gccFamily: OptionSpec[] = [
140
143
  // -Os sets optimize_size beside level 2, so it is its own level: agbcc hoist.c differs at `-O2` and
141
144
  // `-Os`, where the address load moves above the branch.
142
145
  { match: '-Os', slot: 'O', value: () => 's' },
146
+ // Error limits, ahead of the `-W` diagnostics: gcc stops at the first error under
147
+ // `-Wfatal-errors` and at N under `-fmax-errors=N`; clang takes both and spells its own
148
+ // `-ferror-limit=N`, and under `-Wfatal-errors` prints the first error as `fatal error:` with no
149
+ // trailer, so what it printed never says whether it was all.
150
+ { match: '-Wfatal-errors', limitsErrors: true },
151
+ { match: /^-f(?:max-errors|error-limit)=\d+$/, limitsErrors: true },
143
152
  ...cppAndDiagnostics,
144
153
  // -B tells the driver where its own programs are: which compiler runs is the toolchain, not a flag
145
154
  { match: /^-B(.+)$/ },
@@ -192,7 +201,10 @@ const TABLES: Record<FlagFamily, readonly OptionSpec[]> = {
192
201
  // both, and `mwccLevel` reads what a sequence of them compiles at.
193
202
  { match: /^-O((?:[0-4](?:,[ps])?|[ps])?)$/, slot: 'O' },
194
203
  { match: /^-[ID](.+)$/ },
195
- { match: /^-(d|D|i|I|ir|maxerrors|w|msgstyle)$/, takesArg: true },
204
+ { match: /^-(d|D|i|I|ir|w|msgstyle)$/, takesArg: true },
205
+ // Under `-maxerrors 1` mwcc 2.3.3 and 2.4.2 end EVERY rejection with `User break, cancelled...`,
206
+ // a file with exactly one error included, so what they printed never says whether it was all.
207
+ { match: '-maxerrors', takesArg: true, limitsErrors: true },
196
208
  { match: /^-(nodefaults|nosyspath|nostdinc|stderr|c|multibyte|requireprotos)$/ },
197
209
  // `-dialect | -lang keyword` is one option under two names (mwcceppc -help), and all four
198
210
  // spellings compile the same source to the same object in the container.
@@ -307,6 +319,9 @@ export interface CodegenProfile {
307
319
  overriddenAt: readonly number[];
308
320
  /** argv indices of every inert word and its argument */
309
321
  inertAt: readonly number[];
322
+ /** argv indices of every error-limit word and its argument, a subset of `inertAt`: what a compiler
323
+ * cut short printed is a prefix of its verdict (compiler-diagnostics.ts `verdictMessages`) */
324
+ errorLimitAt: readonly number[];
310
325
  /** argv indices of every word that is neither an option nor an option's argument, such as the file
311
326
  * a compile command names or `-` for standard input; each is also unclassified */
312
327
  operandAt: readonly number[];
@@ -421,6 +436,7 @@ export function parseFlags(family: FlagFamily, argv: readonly string[]): Codegen
421
436
  const lastAt = new Map<string, { at: number[]; spelled: string }>();
422
437
  const overriddenAt: number[] = [];
423
438
  const inertAt: number[] = [];
439
+ const errorLimitAt: number[] = [];
424
440
  const overrides: LevelReading['overrides'] = [];
425
441
  const mwccLevelWords: { at: number; word: string }[] = [];
426
442
  const spans: number[][] = [];
@@ -482,6 +498,9 @@ export function parseFlags(family: FlagFamily, argv: readonly string[]): Codegen
482
498
  spans.push(at);
483
499
  if (hit.spec.slot === undefined) {
484
500
  inertAt.push(...at);
501
+ if (hit.spec.limitsErrors) {
502
+ errorLimitAt.push(...at);
503
+ }
485
504
  continue;
486
505
  }
487
506
  const slot = typeof hit.spec.slot === 'string' ? hit.spec.slot : hit.spec.slot(hit.m);
@@ -557,6 +576,7 @@ export function parseFlags(family: FlagFamily, argv: readonly string[]): Codegen
557
576
  unclassified,
558
577
  overriddenAt: overriddenAt.sort((a, b) => a - b),
559
578
  inertAt,
579
+ errorLimitAt,
560
580
  operandAt: unknown.filter((k) => isOperand(argv[k])),
561
581
  spans: grouped,
562
582
  overrides: overrides.sort((a, b) => a.at - b.at).map((o) => o.line),
@@ -617,14 +637,26 @@ export function storedFlags(family: FlagFamily, argv: readonly string[]): string
617
637
  * option, and only mwcc has a C++ front end, so every other family answers `c` unless its unit is
618
638
  * named for C++ — which none is, and where one is, real.ts refuses the row by name. */
619
639
  export function unitLanguage(unit: string, cflags: readonly string[]): 'c' | 'c++' {
620
- const stated = parseFlags('mwcc', cflags).slots.lang;
640
+ return dialectOf(parseFlags('mwcc', cflags).slots.lang, unit);
641
+ }
642
+
643
+ /** A `-lang` word CodeWarrior does not accept. */
644
+ export class UnreadableDialectError extends Error {}
645
+
646
+ /** The dialect a `-lang` word states — the one reading of it, for `unitLanguage` and for the target
647
+ * `targetFor` resolves at a flag set. With no word, the unit's extension when there is a unit, C
648
+ * otherwise. `objc` is C to this reading: its functions are C's. A word CodeWarrior itself refuses
649
+ * is refused. */
650
+ export function dialectOf(stated: string | undefined, unit?: string): 'c' | 'c++' {
621
651
  if (stated === undefined) {
622
- return /\.(cc|cp|cpp|cxx)$/i.test(unit) ? 'c++' : 'c';
652
+ return unit !== undefined && /\.(cc|cp|cpp|cxx)$/i.test(unit) ? 'c++' : 'c';
623
653
  }
624
- if (stated !== 'c' && stated !== 'c++' && stated !== 'ec++') {
625
- throw new Error(`${unit}: CodeWarrior dialect '${stated}' is not one of c, c++, ec++`);
654
+ if (stated !== 'c' && stated !== 'c++' && stated !== 'ec++' && stated !== 'objc') {
655
+ throw new UnreadableDialectError(
656
+ `${unit === undefined ? '' : `${unit}: `}CodeWarrior dialect '${stated}' is not one of c, c++, ec++, objc`,
657
+ );
626
658
  }
627
- return stated === 'c' ? 'c' : 'c++';
659
+ return stated === 'c++' || stated === 'ec++' ? 'c++' : 'c';
628
660
  }
629
661
 
630
662
  /** The optimisation level the compiler acts on (`-O2`, `-O4,p`), or null when the flags name none and
@@ -0,0 +1,194 @@
1
+ // asmlift — reading a compiler's diagnostic text: which lines are errors, and when two failed
2
+ // compiles failed THE SAME WAY. Pure string logic (core is browser-pure): the ranking drivers, the
3
+ // benchmark's compile modules and the CLI read a compiler's output through here, so "an error"
4
+ // means one thing on all of them.
5
+
6
+ /** A scorer's refusal that IS the compiler's verdict: it ran to completion, exited nonzero and
7
+ * said why. Everything else a scorer throws — a timeout, a killed process, a missing binary, a
8
+ * differ that could not find the symbol — is this machine or this harness having a bad minute,
9
+ * and says nothing about the candidate.
10
+ *
11
+ * A CLASS because the distinction cannot be read back off a message: a compiler killed mid-run
12
+ * has already printed half its diagnostics, and that text has exactly the shape of a rejection.
13
+ * The seam that saw the spawn result is the only place that knows, so it says so in the type.
14
+ *
15
+ * `diagnostic` is the compiler's WHOLE output. `message` is what the seam publishes, which may be
16
+ * a bounded selection of it; a comparison made over a bounded selection compares the bound. */
17
+ export class CompilerRejection extends Error {
18
+ readonly diagnostic: string;
19
+ constructor(message: string, diagnostic: string = message, options?: ErrorOptions) {
20
+ super(message, options);
21
+ this.name = 'CompilerRejection';
22
+ this.diagnostic = diagnostic;
23
+ }
24
+ }
25
+
26
+ /** The FIRST class word a line carries, with the colon that makes it a tag: `warning:`, `note:`,
27
+ * `error:`, `fatal error:`, IDO's `Fatal:` and its numbered `Warning 712:`. The first, because a
28
+ * message can quote a tag of its own — `expected ';' before 'note' token; note: …` is an error.
29
+ * A line is ADVISORY when its tag is a warning or a note: the compiler may still accept the text. */
30
+ const TAG = /\b(?:fatal(?:\s+error)?|error|warning|note)\b(?:\s+\d+)?\s*:/i;
31
+ const isAdvisory = (line: string): boolean => {
32
+ const tag = TAG.exec(line);
33
+ return tag !== null && /^(?:warning|note)/i.test(tag[0]);
34
+ };
35
+ /** An mwcc caret line (`# Error: ^`). It carries no message: the line(s) after it do, and
36
+ * together they are one diagnostic. */
37
+ const MWCC_CARET = /#\s*(Error|Warning):[\s^~]*$/i;
38
+ /** A line of an mwcc diagnostic's MESSAGE: `#`, then prose. Not the caret, not a source excerpt
39
+ * (`# 12: x = y;`), not the banner (`### mwcceppc.exe Compiler:`, `# File: …`, `# ----`).
40
+ * mwcc wraps a message at the terminal's width, so a message is every such line up to the first
41
+ * that is not one: a conversion error's source type ends one line and its target type `'int *'`
42
+ * opens the next, and the first line alone equals the same conversion to `'int'`. */
43
+ const MWCC_MESSAGE = /^\s*#(?!#)\s*(?!(?:Error|Warning)\s*:|\d+:|File:|-{3,})\S/i;
44
+
45
+ /** `lines` with every ERROR ahead of every warning and note, each class in its original order.
46
+ *
47
+ * A compiler prints diagnostics in source order, and a candidate that casts its way through a
48
+ * project's types can print dozens of warnings above the one error that refused it. Whatever
49
+ * bounds the list downstream then keeps warnings only, and the reader is shown a cause that is
50
+ * not why the compile failed. An mwcc message travels with its caret line, whichever class that
51
+ * is. */
52
+ export function errorsFirst(lines: readonly string[]): string[] {
53
+ const errors: string[] = [];
54
+ const advisories: string[] = [];
55
+ /** the class of the mwcc diagnostic whose message lines are still arriving */
56
+ let open: boolean | undefined;
57
+ for (const l of lines) {
58
+ let advisory: boolean;
59
+ if (open !== undefined && MWCC_MESSAGE.test(l)) {
60
+ advisory = open;
61
+ } else {
62
+ advisory = isAdvisory(l);
63
+ open = MWCC_CARET.test(l) ? advisory : undefined;
64
+ }
65
+ (advisory ? advisories : errors).push(l);
66
+ }
67
+ return [...errors, ...advisories];
68
+ }
69
+
70
+ /** `file:line:` or `file:line:column:` opening a diagnostic, the gcc family's spelling (clang's
71
+ * too). Behind an optional wrapper, because a seam's message puts its own words ahead of the
72
+ * first line (`agbcc failed: c.c:12: …`). */
73
+ const LOCATED = /(?:^|\s)[^\s:]+:\d+:(?:\d+:)?\s*(.*)$/;
74
+ /** The word a located error may carry ahead of its message (gcc 3+, clang); pre-3.0 gcc has none. */
75
+ const ERROR_TAG = /^(?:fatal\s+)?error\s*:\s*/i;
76
+ /** An error spelled with the word and no `file:line:`: `cc1: error: …`, `ld: fatal error: …`, and
77
+ * IDO's `cfe: Error: c.c, line 12: …`, whose own position is dropped with the rest. */
78
+ const TAGGED = /\b(?:fatal(?:\s+error)?|error)\s*:\s*(?:\S+, line \d+:\s*)?(.*)$/i;
79
+ /** Lines that belong to a diagnostic without being one: where the OTHER declaration was, and
80
+ * pre-3.0 gcc's two-line footnote to its first `undeclared`. None of them says what is wrong with
81
+ * the statement, and a `previous declaration` line points into the context rather than at the
82
+ * candidate. */
83
+ const CONTINUATION =
84
+ /^(?:previous (?:implicit )?(?:declaration|definition)\b|this is the location of\b|\(Each undeclared identifier\b|for each function it appears in\b|.*\bpreviously (?:declared|defined) here$)/i;
85
+ /** A position spelled INSIDE a message rather than ahead of it: IDO's `redeclaration of 'a';
86
+ * previous declaration at line 2 in file '/tmp/x/cand.c'`, whose file is a scratch directory that
87
+ * differs per compile. */
88
+ const POSITION_IN_MESSAGE = /\s*\bat line \d+ in file '[^']*'/g;
89
+
90
+ /** The compiler's ERROR lines, each reduced to its message: no file, no line, no column, no
91
+ * `error:` tag. Warnings, notes, `In function` banners, `previous declaration` lines and source
92
+ * excerpts are not errors and are not here. Empty when the text holds nothing this recognises —
93
+ * which is an answer the caller must treat as "unknown", never as "no errors". */
94
+ export function errorMessages(diagnostic: string): string[] {
95
+ const lines = diagnostic
96
+ .split('\n')
97
+ .map((l) => l.trim())
98
+ .filter(Boolean);
99
+ const out: string[] = [];
100
+ for (let i = 0; i < lines.length; i++) {
101
+ const caret = MWCC_CARET.exec(lines[i]);
102
+ if (caret !== null) {
103
+ const parts: string[] = [];
104
+ while (i + 1 < lines.length && MWCC_MESSAGE.test(lines[i + 1])) {
105
+ parts.push(lines[++i].replace(/^#\s*/, ''));
106
+ }
107
+ if (caret[1].toLowerCase() === 'error' && parts.length > 0) {
108
+ out.push(parts.join(' '));
109
+ }
110
+ continue;
111
+ }
112
+ if (isAdvisory(lines[i])) {
113
+ continue;
114
+ }
115
+ const located = LOCATED.exec(lines[i]);
116
+ const message = located === null ? TAGGED.exec(lines[i])?.[1] : located[1].replace(ERROR_TAG, '');
117
+ if (message !== undefined && message !== '' && !CONTINUATION.test(message)) {
118
+ out.push(message.replace(POSITION_IN_MESSAGE, ''));
119
+ }
120
+ }
121
+ return out;
122
+ }
123
+
124
+ /** A compiler that STOPPED REPORTING before it was done: IDO after 30 errors, clang at its
125
+ * `-ferror-limit`, gcc under `-fmax-errors` or `-Wfatal-errors`, mwcc under `-maxerrors`. What it
126
+ * printed is a prefix of its verdict, and two prefixes equal each other whatever follows them.
127
+ * clang under `-Wfatal-errors` prints no trailer at all, so the flag side (codegen-flags.ts
128
+ * `limitsErrors`) is what keeps that one out of a ranked compile. */
129
+ const TRUNCATED =
130
+ /Too many errors\.\.\. goodbye|too many errors emitted|compilation terminated due to -(?:fmax-errors|Wfatal-errors)|^User break, cancelled/im;
131
+
132
+ /** The error messages of a diagnostic that can be READ AS A VERDICT: `errorMessages`, or null when
133
+ * it recognises none, and null when the compiler stopped reporting — what an unfinished
134
+ * diagnostic printed is a prefix of its verdict, and two prefixes agree whatever follows them. */
135
+ export function verdictMessages(diagnostic: string): string[] | null {
136
+ if (TRUNCATED.test(diagnostic)) {
137
+ return null;
138
+ }
139
+ const messages = errorMessages(diagnostic);
140
+ return messages.length === 0 ? null : messages;
141
+ }
142
+
143
+ /** One compile ATTEMPT inside a diagnostic that holds several: what the harness called the
144
+ * attempt, and what the compiler printed for it. */
145
+ export interface Attempt {
146
+ label: string;
147
+ diagnostic: string;
148
+ }
149
+
150
+ /** The line that opens an attempt. Owned here because it is read here: a harness that spelled its
151
+ * own could not be split back, and every one of its rejections would read as a single attempt. */
152
+ const ATTEMPT_HEADER = /^--- (.+) ---$/;
153
+
154
+ /** ONE REJECTION, SEVERAL COMPILES. A harness that tries a candidate in more than one world before
155
+ * it gives up — the benchmark's real tier climbs a ladder of contexts, in two dialects — throws one
156
+ * rejection for all of them, and whoever compares two rejections has to ask what EACH attempt
157
+ * said: a world no candidate can compile in prints the same sentence for every candidate, and read
158
+ * as one text that sentence would say the whole fan fails alike. So the attempts travel under a
159
+ * header line per attempt, in the order they were tried, and `attemptsOf` splits them back.
160
+ *
161
+ * The header is no error line to `errorMessages`, so the joined text reads as the whole of what
162
+ * every attempt printed, which is what a store of rejections keeps and a printer prints. A label
163
+ * holds no line break; a compiler line that happens to read as a header would split its attempt
164
+ * in two, alike in every candidate that printed it. */
165
+ export function joinAttempts(attempts: readonly Attempt[]): string {
166
+ return attempts.map((a) => `--- ${a.label} ---\n${a.diagnostic}`).join('\n');
167
+ }
168
+
169
+ /** The attempts `joinAttempts` joined, in order. A diagnostic with no header is ONE attempt,
170
+ * labelled `''`: a single compile is the ordinary case, not a degenerate one. Text ahead of the
171
+ * first header is an attempt of its own when it holds anything, because dropping it would drop
172
+ * what it said. */
173
+ export function attemptsOf(diagnostic: string): Attempt[] {
174
+ const out: Attempt[] = [];
175
+ let label: string | null = null;
176
+ let lines: string[] = [];
177
+ const close = () => {
178
+ if (label !== null || lines.some((l) => l.trim() !== '')) {
179
+ out.push({ label: label ?? '', diagnostic: lines.join('\n') });
180
+ }
181
+ };
182
+ for (const line of diagnostic.split('\n')) {
183
+ const header = ATTEMPT_HEADER.exec(line);
184
+ if (header === null) {
185
+ lines.push(line);
186
+ continue;
187
+ }
188
+ close();
189
+ label = header[1];
190
+ lines = [];
191
+ }
192
+ close();
193
+ return out.length > 0 ? out : [{ label: '', diagnostic }];
194
+ }