xbintsc 0.1.3 → 0.1.8
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.
- package/README.md +14 -13
- package/dist/src/ast/common.d.ts +33 -0
- package/dist/src/ast/common.js +7 -0
- package/dist/src/ast/common.js.map +1 -0
- package/dist/src/ast/declarations.d.ts +110 -0
- package/dist/src/ast/declarations.js +5 -0
- package/dist/src/ast/declarations.js.map +1 -0
- package/dist/src/ast/expressions.d.ts +219 -0
- package/dist/src/ast/expressions.js +6 -0
- package/dist/src/ast/expressions.js.map +1 -0
- package/dist/src/ast/kinds.d.ts +160 -0
- package/dist/src/ast/kinds.js +171 -0
- package/dist/src/ast/kinds.js.map +1 -0
- package/dist/src/ast/modules.d.ts +61 -0
- package/dist/src/ast/modules.js +5 -0
- package/dist/src/ast/modules.js.map +1 -0
- package/dist/src/ast/nodes.d.ts +11 -839
- package/dist/src/ast/nodes.js +11 -237
- package/dist/src/ast/nodes.js.map +1 -1
- package/dist/src/ast/operators.d.ts +71 -0
- package/dist/src/ast/operators.js +77 -0
- package/dist/src/ast/operators.js.map +1 -0
- package/dist/src/ast/statements.d.ts +120 -0
- package/dist/src/ast/statements.js +5 -0
- package/dist/src/ast/statements.js.map +1 -0
- package/dist/src/ast/types.d.ts +123 -0
- package/dist/src/ast/types.js +6 -0
- package/dist/src/ast/types.js.map +1 -0
- package/dist/src/codegen/generator/calls.d.ts +22 -0
- package/dist/src/codegen/generator/calls.js +301 -0
- package/dist/src/codegen/generator/calls.js.map +1 -0
- package/dist/src/codegen/generator/context.d.ts +45 -0
- package/dist/src/codegen/generator/context.js +128 -0
- package/dist/src/codegen/generator/context.js.map +1 -0
- package/dist/src/codegen/generator/expressions.d.ts +22 -0
- package/dist/src/codegen/generator/expressions.js +383 -0
- package/dist/src/codegen/generator/expressions.js.map +1 -0
- package/dist/src/codegen/generator/generator.d.ts +22 -0
- package/dist/src/codegen/generator/generator.js +21 -0
- package/dist/src/codegen/generator/generator.js.map +1 -0
- package/dist/src/codegen/generator/module.d.ts +15 -0
- package/dist/src/codegen/generator/module.js +114 -0
- package/dist/src/codegen/generator/module.js.map +1 -0
- package/dist/src/codegen/generator/state.d.ts +40 -0
- package/dist/src/codegen/generator/state.js +84 -0
- package/dist/src/codegen/generator/state.js.map +1 -0
- package/dist/src/codegen/generator/statements.d.ts +22 -0
- package/dist/src/codegen/generator/statements.js +425 -0
- package/dist/src/codegen/generator/statements.js.map +1 -0
- package/dist/src/codegen/generator/tables.d.ts +19 -0
- package/dist/src/codegen/generator/tables.js +153 -0
- package/dist/src/codegen/generator/tables.js.map +1 -0
- package/dist/src/codegen/llvm.d.ts +4 -13
- package/dist/src/codegen/llvm.js +4 -1159
- package/dist/src/codegen/llvm.js.map +1 -1
- package/dist/src/driver/compiler.js +13 -4
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/lexer/char.d.ts +78 -0
- package/dist/src/lexer/char.js +106 -0
- package/dist/src/lexer/char.js.map +1 -0
- package/dist/src/lexer/scanner.js +1 -98
- package/dist/src/lexer/scanner.js.map +1 -1
- package/dist/src/parser/context.d.ts +58 -0
- package/dist/src/parser/context.js +174 -0
- package/dist/src/parser/context.js.map +1 -0
- package/dist/src/parser/declarations.d.ts +26 -0
- package/dist/src/parser/declarations.js +461 -0
- package/dist/src/parser/declarations.js.map +1 -0
- package/dist/src/parser/expressions.d.ts +27 -0
- package/dist/src/parser/expressions.js +437 -0
- package/dist/src/parser/expressions.js.map +1 -0
- package/dist/src/parser/helpers.d.ts +7 -0
- package/dist/src/parser/helpers.js +36 -0
- package/dist/src/parser/helpers.js.map +1 -0
- package/dist/src/parser/literals.d.ts +16 -0
- package/dist/src/parser/literals.js +193 -0
- package/dist/src/parser/literals.js.map +1 -0
- package/dist/src/parser/modules.d.ts +12 -0
- package/dist/src/parser/modules.js +175 -0
- package/dist/src/parser/modules.js.map +1 -0
- package/dist/src/parser/operators.d.ts +12 -0
- package/dist/src/parser/operators.js +172 -0
- package/dist/src/parser/operators.js.map +1 -0
- package/dist/src/parser/parser.d.ts +17 -122
- package/dist/src/parser/parser.js +18 -2307
- package/dist/src/parser/parser.js.map +1 -1
- package/dist/src/parser/speculation.d.ts +6 -0
- package/dist/src/parser/speculation.js +10 -0
- package/dist/src/parser/speculation.js.map +1 -0
- package/dist/src/parser/statements.d.ts +28 -0
- package/dist/src/parser/statements.js +402 -0
- package/dist/src/parser/statements.js.map +1 -0
- package/dist/src/parser/types.d.ts +21 -0
- package/dist/src/parser/types.js +338 -0
- package/dist/src/parser/types.js.map +1 -0
- package/dist/tests/e2e/compile.test.js +122 -2
- package/dist/tests/e2e/compile.test.js.map +1 -1
- package/dist/tests/parser/parser.test.js +9 -1
- package/dist/tests/parser/parser.test.js.map +1 -1
- package/package.json +1 -1
- package/runtime/rt.h +32 -0
- package/runtime/rt_internal.h +106 -0
- package/runtime/xt_alloc.c +38 -0
- package/runtime/xt_builtins.c +192 -0
- package/runtime/xt_containers.c +225 -0
- package/runtime/xt_io.c +133 -0
- package/runtime/xt_stdlib.c +502 -0
- package/runtime/xt_values.c +291 -0
- package/scripts/build-runtime.mjs +8 -1
- package/src/ast/common.ts +210 -0
- package/src/ast/declarations.ts +130 -0
- package/src/ast/expressions.ts +275 -0
- package/src/ast/kinds.ts +176 -0
- package/src/ast/modules.ts +73 -0
- package/src/ast/nodes.ts +11 -1101
- package/src/ast/operators.ts +76 -0
- package/src/ast/statements.ts +148 -0
- package/src/ast/types.ts +173 -0
- package/src/codegen/generator/calls.ts +355 -0
- package/src/codegen/generator/context.ts +156 -0
- package/src/codegen/generator/expressions.ts +431 -0
- package/src/codegen/generator/generator.ts +35 -0
- package/src/codegen/generator/module.ts +139 -0
- package/src/codegen/generator/state.ts +126 -0
- package/src/codegen/generator/statements.ts +474 -0
- package/src/codegen/generator/tables.ts +171 -0
- package/src/codegen/llvm.ts +4 -1305
- package/src/driver/compiler.ts +17 -5
- package/src/lexer/char.ts +116 -0
- package/src/lexer/scanner.ts +9 -109
- package/src/parser/context.ts +196 -0
- package/src/parser/declarations.ts +502 -0
- package/src/parser/expressions.ts +465 -0
- package/src/parser/helpers.ts +42 -0
- package/src/parser/literals.ts +207 -0
- package/src/parser/modules.ts +194 -0
- package/src/parser/operators.ts +181 -0
- package/src/parser/parser.ts +35 -2369
- package/src/parser/speculation.ts +9 -0
- package/src/parser/statements.ts +457 -0
- package/src/parser/types.ts +365 -0
- package/runtime/xt_runtime.c +0 -682
package/src/driver/compiler.ts
CHANGED
|
@@ -139,7 +139,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
139
139
|
const runtimeDir = findRuntimeDir();
|
|
140
140
|
mkdirSync(cacheDir, { recursive: true });
|
|
141
141
|
|
|
142
|
-
const {
|
|
142
|
+
const { runtimeObjects, extensionObjects } = ensureRuntimeObjects(runner, clang, runtimeDir, cacheDir, registry);
|
|
143
143
|
|
|
144
144
|
const objectPath = emit === "obj" ? outputPath : resolve(outDir, baseName + ".o");
|
|
145
145
|
compileIr(runner, { clang, irPath, objectPath, optimize });
|
|
@@ -152,7 +152,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
152
152
|
|
|
153
153
|
link(runner, {
|
|
154
154
|
clang,
|
|
155
|
-
objectPaths: [objectPath,
|
|
155
|
+
objectPaths: [objectPath, ...runtimeObjects, ...extensionObjects],
|
|
156
156
|
outputPath,
|
|
157
157
|
linkerFlags: [...(process.platform === "win32" ? [] : ["-lm"]), ...registry.linkerFlags()],
|
|
158
158
|
optimize,
|
|
@@ -164,10 +164,20 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
interface RuntimeObjects {
|
|
167
|
-
readonly
|
|
167
|
+
readonly runtimeObjects: readonly string[];
|
|
168
168
|
readonly extensionObjects: readonly string[];
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
/** Core runtime translation units (each compiled and cached independently). */
|
|
172
|
+
const RUNTIME_SOURCES = [
|
|
173
|
+
"xt_alloc.c",
|
|
174
|
+
"xt_values.c",
|
|
175
|
+
"xt_containers.c",
|
|
176
|
+
"xt_stdlib.c",
|
|
177
|
+
"xt_builtins.c",
|
|
178
|
+
"xt_io.c",
|
|
179
|
+
] as const;
|
|
180
|
+
|
|
171
181
|
/**
|
|
172
182
|
* Compile the core runtime and every extension source, reusing cached object
|
|
173
183
|
* files keyed on the C source hash. Returns the object paths to link.
|
|
@@ -189,9 +199,11 @@ function ensureRuntimeObjects(
|
|
|
189
199
|
return objectPath;
|
|
190
200
|
};
|
|
191
201
|
|
|
192
|
-
const
|
|
202
|
+
const runtimeObjects = RUNTIME_SOURCES.map((source) =>
|
|
203
|
+
compileOne(join(runtimeDir, source), basename(source, ".c")),
|
|
204
|
+
);
|
|
193
205
|
const extensionObjects = registry
|
|
194
206
|
.runtimeSources()
|
|
195
207
|
.map((sourcePath, index) => compileOne(sourcePath, `ext_${index}_${basename(sourcePath, ".c")}`));
|
|
196
|
-
return {
|
|
208
|
+
return { runtimeObjects, extensionObjects };
|
|
197
209
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Character classification used by the scanner.
|
|
3
|
+
*
|
|
4
|
+
* Keeping the code-point constants and the `is*` predicates in their own module
|
|
5
|
+
* keeps the scanner focused on tokenization and makes the low-level rules easy
|
|
6
|
+
* to test in isolation.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const enum Char {
|
|
10
|
+
Null = 0,
|
|
11
|
+
Tab = 9,
|
|
12
|
+
LineFeed = 10,
|
|
13
|
+
VerticalTab = 11,
|
|
14
|
+
FormFeed = 12,
|
|
15
|
+
CarriageReturn = 13,
|
|
16
|
+
Space = 32,
|
|
17
|
+
Exclamation = 33,
|
|
18
|
+
DoubleQuote = 34,
|
|
19
|
+
Hash = 35,
|
|
20
|
+
Dollar = 36,
|
|
21
|
+
Percent = 37,
|
|
22
|
+
Ampersand = 38,
|
|
23
|
+
SingleQuote = 39,
|
|
24
|
+
OpenParen = 40,
|
|
25
|
+
CloseParen = 41,
|
|
26
|
+
Asterisk = 42,
|
|
27
|
+
Plus = 43,
|
|
28
|
+
Comma = 44,
|
|
29
|
+
Minus = 45,
|
|
30
|
+
Dot = 46,
|
|
31
|
+
Slash = 47,
|
|
32
|
+
Zero = 48,
|
|
33
|
+
Nine = 57,
|
|
34
|
+
Colon = 58,
|
|
35
|
+
Semicolon = 59,
|
|
36
|
+
LessThan = 60,
|
|
37
|
+
Equals = 61,
|
|
38
|
+
GreaterThan = 62,
|
|
39
|
+
Question = 63,
|
|
40
|
+
At = 64,
|
|
41
|
+
UpperA = 65,
|
|
42
|
+
UpperB = 66,
|
|
43
|
+
UpperE = 69,
|
|
44
|
+
UpperF = 70,
|
|
45
|
+
UpperO = 79,
|
|
46
|
+
UpperX = 88,
|
|
47
|
+
UpperZ = 90,
|
|
48
|
+
OpenBracket = 91,
|
|
49
|
+
Backslash = 92,
|
|
50
|
+
CloseBracket = 93,
|
|
51
|
+
Caret = 94,
|
|
52
|
+
Underscore = 95,
|
|
53
|
+
Backtick = 96,
|
|
54
|
+
LowerA = 97,
|
|
55
|
+
LowerB = 98,
|
|
56
|
+
LowerE = 101,
|
|
57
|
+
LowerF = 102,
|
|
58
|
+
LowerN = 110,
|
|
59
|
+
LowerO = 111,
|
|
60
|
+
LowerR = 114,
|
|
61
|
+
LowerT = 116,
|
|
62
|
+
LowerU = 117,
|
|
63
|
+
LowerX = 120,
|
|
64
|
+
LowerZ = 122,
|
|
65
|
+
OpenBrace = 123,
|
|
66
|
+
Bar = 124,
|
|
67
|
+
CloseBrace = 125,
|
|
68
|
+
Tilde = 126,
|
|
69
|
+
NonBreakingSpace = 0xa0,
|
|
70
|
+
LineSeparator = 0x2028,
|
|
71
|
+
ParagraphSeparator = 0x2029,
|
|
72
|
+
ByteOrderMark = 0xfeff,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function isLineBreak(ch: number): boolean {
|
|
76
|
+
return ch === Char.LineFeed || ch === Char.CarriageReturn || ch === Char.LineSeparator || ch === Char.ParagraphSeparator;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const WHITESPACE_UNICODE = new Set<number>([
|
|
80
|
+
0x1680, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200a, 0x202f, 0x205f,
|
|
81
|
+
0x3000,
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
export function isWhiteSpace(ch: number): boolean {
|
|
85
|
+
return (
|
|
86
|
+
ch === Char.Space ||
|
|
87
|
+
ch === Char.Tab ||
|
|
88
|
+
ch === Char.VerticalTab ||
|
|
89
|
+
ch === Char.FormFeed ||
|
|
90
|
+
ch === Char.NonBreakingSpace ||
|
|
91
|
+
ch === Char.ByteOrderMark ||
|
|
92
|
+
(ch > 0x1680 && WHITESPACE_UNICODE.has(ch))
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function isDigit(ch: number): boolean {
|
|
97
|
+
return ch >= Char.Zero && ch <= Char.Nine;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function isHexDigit(ch: number): boolean {
|
|
101
|
+
return isDigit(ch) || (ch >= Char.LowerA && ch <= Char.LowerF) || (ch >= Char.UpperA && ch <= Char.UpperF);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function isIdentifierStart(ch: number): boolean {
|
|
105
|
+
return (
|
|
106
|
+
(ch >= Char.LowerA && ch <= Char.LowerZ) ||
|
|
107
|
+
(ch >= Char.UpperA && ch <= Char.UpperZ) ||
|
|
108
|
+
ch === Char.Dollar ||
|
|
109
|
+
ch === Char.Underscore ||
|
|
110
|
+
ch > 0x7f
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function isIdentifierPart(ch: number): boolean {
|
|
115
|
+
return isIdentifierStart(ch) || isDigit(ch) || ch === Char.Backslash || ch > 0x7f;
|
|
116
|
+
}
|
package/src/lexer/scanner.ts
CHANGED
|
@@ -1,115 +1,15 @@
|
|
|
1
1
|
import { DiagnosticBag, DiagnosticCode } from "../diagnostics/diagnostic.js";
|
|
2
2
|
import type { SourceFile } from "../diagnostics/source.js";
|
|
3
3
|
import { KEYWORDS, Token, TokenKind } from "./token.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
Exclamation = 33,
|
|
14
|
-
DoubleQuote = 34,
|
|
15
|
-
Hash = 35,
|
|
16
|
-
Dollar = 36,
|
|
17
|
-
Percent = 37,
|
|
18
|
-
Ampersand = 38,
|
|
19
|
-
SingleQuote = 39,
|
|
20
|
-
OpenParen = 40,
|
|
21
|
-
CloseParen = 41,
|
|
22
|
-
Asterisk = 42,
|
|
23
|
-
Plus = 43,
|
|
24
|
-
Comma = 44,
|
|
25
|
-
Minus = 45,
|
|
26
|
-
Dot = 46,
|
|
27
|
-
Slash = 47,
|
|
28
|
-
Zero = 48,
|
|
29
|
-
Nine = 57,
|
|
30
|
-
Colon = 58,
|
|
31
|
-
Semicolon = 59,
|
|
32
|
-
LessThan = 60,
|
|
33
|
-
Equals = 61,
|
|
34
|
-
GreaterThan = 62,
|
|
35
|
-
Question = 63,
|
|
36
|
-
At = 64,
|
|
37
|
-
UpperA = 65,
|
|
38
|
-
UpperB = 66,
|
|
39
|
-
UpperE = 69,
|
|
40
|
-
UpperF = 70,
|
|
41
|
-
UpperO = 79,
|
|
42
|
-
UpperX = 88,
|
|
43
|
-
UpperZ = 90,
|
|
44
|
-
OpenBracket = 91,
|
|
45
|
-
Backslash = 92,
|
|
46
|
-
CloseBracket = 93,
|
|
47
|
-
Caret = 94,
|
|
48
|
-
Underscore = 95,
|
|
49
|
-
Backtick = 96,
|
|
50
|
-
LowerA = 97,
|
|
51
|
-
LowerB = 98,
|
|
52
|
-
LowerE = 101,
|
|
53
|
-
LowerF = 102,
|
|
54
|
-
LowerN = 110,
|
|
55
|
-
LowerO = 111,
|
|
56
|
-
LowerR = 114,
|
|
57
|
-
LowerT = 116,
|
|
58
|
-
LowerU = 117,
|
|
59
|
-
LowerX = 120,
|
|
60
|
-
LowerZ = 122,
|
|
61
|
-
OpenBrace = 123,
|
|
62
|
-
Bar = 124,
|
|
63
|
-
CloseBrace = 125,
|
|
64
|
-
Tilde = 126,
|
|
65
|
-
NonBreakingSpace = 0xa0,
|
|
66
|
-
LineSeparator = 0x2028,
|
|
67
|
-
ParagraphSeparator = 0x2029,
|
|
68
|
-
ByteOrderMark = 0xfeff,
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function isLineBreak(ch: number): boolean {
|
|
72
|
-
return ch === Char.LineFeed || ch === Char.CarriageReturn || ch === Char.LineSeparator || ch === Char.ParagraphSeparator;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function isWhiteSpace(ch: number): boolean {
|
|
76
|
-
return (
|
|
77
|
-
ch === Char.Space ||
|
|
78
|
-
ch === Char.Tab ||
|
|
79
|
-
ch === Char.VerticalTab ||
|
|
80
|
-
ch === Char.FormFeed ||
|
|
81
|
-
ch === Char.NonBreakingSpace ||
|
|
82
|
-
ch === Char.ByteOrderMark ||
|
|
83
|
-
(ch > 0x1680 && WHITESPACE_UNICODE.has(ch))
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const WHITESPACE_UNICODE = new Set<number>([
|
|
88
|
-
0x1680, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200a, 0x202f, 0x205f,
|
|
89
|
-
0x3000,
|
|
90
|
-
]);
|
|
91
|
-
|
|
92
|
-
function isDigit(ch: number): boolean {
|
|
93
|
-
return ch >= Char.Zero && ch <= Char.Nine;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function isHexDigit(ch: number): boolean {
|
|
97
|
-
return isDigit(ch) || (ch >= Char.LowerA && ch <= Char.LowerF) || (ch >= Char.UpperA && ch <= Char.UpperF);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function isIdentifierStart(ch: number): boolean {
|
|
101
|
-
return (
|
|
102
|
-
(ch >= Char.LowerA && ch <= Char.LowerZ) ||
|
|
103
|
-
(ch >= Char.UpperA && ch <= Char.UpperZ) ||
|
|
104
|
-
ch === Char.Dollar ||
|
|
105
|
-
ch === Char.Underscore ||
|
|
106
|
-
ch > 0x7f
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function isIdentifierPart(ch: number): boolean {
|
|
111
|
-
return isIdentifierStart(ch) || isDigit(ch) || ch === Char.Backslash || ch > 0x7f;
|
|
112
|
-
}
|
|
4
|
+
import {
|
|
5
|
+
Char,
|
|
6
|
+
isDigit,
|
|
7
|
+
isHexDigit,
|
|
8
|
+
isIdentifierPart,
|
|
9
|
+
isIdentifierStart,
|
|
10
|
+
isLineBreak,
|
|
11
|
+
isWhiteSpace,
|
|
12
|
+
} from "./char.js";
|
|
113
13
|
|
|
114
14
|
/**
|
|
115
15
|
* Hand-written scanner for the TypeScript language. It produces a flat token
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared state and low-level token plumbing for the parser.
|
|
3
|
+
*
|
|
4
|
+
* The parser's method groups (statements, expressions, types, modules) are
|
|
5
|
+
* defined as separate modules and installed onto `Parser.prototype`. This class
|
|
6
|
+
* holds the state they all operate on plus the token accessors every group
|
|
7
|
+
* needs.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { DiagnosticBag, DiagnosticCode } from "../diagnostics/diagnostic.js";
|
|
11
|
+
import type { SourceFile } from "../diagnostics/source.js";
|
|
12
|
+
import { Scanner } from "../lexer/scanner.js";
|
|
13
|
+
import { Token, TokenKind } from "../lexer/token.js";
|
|
14
|
+
import { SyntaxKind } from "../ast/kinds.js";
|
|
15
|
+
import type { Identifier, StringLiteral } from "../ast/nodes.js";
|
|
16
|
+
import { SpeculationError } from "./speculation.js";
|
|
17
|
+
|
|
18
|
+
export interface ParserState {
|
|
19
|
+
index: number;
|
|
20
|
+
tokens: Token[];
|
|
21
|
+
pos: number;
|
|
22
|
+
consumed: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class ParserContext {
|
|
26
|
+
readonly scanner: Scanner;
|
|
27
|
+
readonly diagnostics: DiagnosticBag;
|
|
28
|
+
readonly source: SourceFile;
|
|
29
|
+
tokens: Token[];
|
|
30
|
+
index = 0;
|
|
31
|
+
/** Non-zero while a speculative parse is running; errors abort the attempt. */
|
|
32
|
+
speculating = 0;
|
|
33
|
+
/** Monotonic count of tokens consumed; used to detect lack of progress. */
|
|
34
|
+
consumed = 0;
|
|
35
|
+
|
|
36
|
+
constructor(source: SourceFile, diagnostics: DiagnosticBag) {
|
|
37
|
+
this.source = source;
|
|
38
|
+
this.diagnostics = diagnostics;
|
|
39
|
+
this.scanner = new Scanner(source, diagnostics);
|
|
40
|
+
this.tokens = [this.scanner.nextToken()];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// -- token access ---------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
get token(): Token {
|
|
46
|
+
return this.tokens[this.index]!;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Compare the current token kind without triggering TypeScript's control-flow
|
|
51
|
+
* narrowing of `this.token.kind`, which would otherwise be invalidated by the
|
|
52
|
+
* calls to `nextToken()` that occur between checks.
|
|
53
|
+
*/
|
|
54
|
+
at(kind: TokenKind): boolean {
|
|
55
|
+
return this.tokens[this.index]!.kind === kind;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
atAhead(k: number, kind: TokenKind): boolean {
|
|
59
|
+
return this.lookAhead(k).kind === kind;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
lookAhead(k: number): Token {
|
|
63
|
+
while (this.tokens.length <= this.index + k) {
|
|
64
|
+
this.tokens.push(this.scanner.nextToken());
|
|
65
|
+
}
|
|
66
|
+
return this.tokens[this.index + k]!;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
nextToken(): Token {
|
|
70
|
+
this.consumed++;
|
|
71
|
+
const current = this.token;
|
|
72
|
+
if (this.index + 1 < this.tokens.length) {
|
|
73
|
+
this.index++;
|
|
74
|
+
} else {
|
|
75
|
+
this.tokens = [this.scanner.nextToken()];
|
|
76
|
+
this.index = 0;
|
|
77
|
+
}
|
|
78
|
+
return current;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
parseExpected(kind: TokenKind, message?: string): Token {
|
|
82
|
+
if (this.token.kind === kind) return this.nextToken();
|
|
83
|
+
this.error(
|
|
84
|
+
DiagnosticCode.ExpectedToken,
|
|
85
|
+
message ?? `Expected '${kind}' but found '${this.token.text || this.token.kind}'`,
|
|
86
|
+
this.token,
|
|
87
|
+
);
|
|
88
|
+
return this.token;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
error(code: DiagnosticCode, message: string, token: Token = this.token): void {
|
|
92
|
+
this.diagnostics.error(code, message, { start: token.start, end: token.end }, this.source.fileName);
|
|
93
|
+
if (this.speculating > 0) throw new SpeculationError();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Run `fn` speculatively. Any diagnostic raised while it runs (which aborts
|
|
98
|
+
* the attempt via `SpeculationError`) is rolled back along with the scanner
|
|
99
|
+
* position, so the parser can try a different production cleanly.
|
|
100
|
+
*/
|
|
101
|
+
tryParse<T>(fn: () => T): T | undefined {
|
|
102
|
+
const mark = this.diagnostics.mark();
|
|
103
|
+
const state = this.saveState();
|
|
104
|
+
this.speculating++;
|
|
105
|
+
let result: T | undefined;
|
|
106
|
+
let ok = false;
|
|
107
|
+
try {
|
|
108
|
+
result = fn();
|
|
109
|
+
ok = true;
|
|
110
|
+
} catch (error) {
|
|
111
|
+
if (!(error instanceof SpeculationError)) throw error;
|
|
112
|
+
} finally {
|
|
113
|
+
this.speculating--;
|
|
114
|
+
}
|
|
115
|
+
if (!ok) {
|
|
116
|
+
this.diagnostics.reset(mark);
|
|
117
|
+
this.restoreState(state);
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
isIdentifierLike(token: Token): boolean {
|
|
124
|
+
if (token.kind === TokenKind.Identifier) return true;
|
|
125
|
+
switch (token.kind) {
|
|
126
|
+
case TokenKind.AsKeyword:
|
|
127
|
+
case TokenKind.SatisfiesKeyword:
|
|
128
|
+
case TokenKind.FromKeyword:
|
|
129
|
+
case TokenKind.TypeKeyword:
|
|
130
|
+
case TokenKind.GetKeyword:
|
|
131
|
+
case TokenKind.SetKeyword:
|
|
132
|
+
case TokenKind.AsyncKeyword:
|
|
133
|
+
return true;
|
|
134
|
+
default:
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
parseIdentifier(allowKeywords = false): Identifier {
|
|
140
|
+
const token = this.token;
|
|
141
|
+
if (token.kind === TokenKind.Identifier || (allowKeywords && this.isIdentifierLike(token))) {
|
|
142
|
+
this.nextToken();
|
|
143
|
+
return { kind: SyntaxKind.Identifier, text: token.text, start: token.start, end: token.end };
|
|
144
|
+
}
|
|
145
|
+
this.error(DiagnosticCode.ExpectedIdentifier, `Expected identifier but found '${token.text || token.kind}'`);
|
|
146
|
+
if (token.kind !== TokenKind.EndOfFile) this.nextToken();
|
|
147
|
+
return { kind: SyntaxKind.Identifier, text: token.text || "missing", start: token.start, end: token.end };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
parseIdentifierName(): Identifier {
|
|
151
|
+
return this.parseIdentifier(/* allowKeywords */ true);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
parseSemicolon(): void {
|
|
155
|
+
if (this.at(TokenKind.Semicolon)) {
|
|
156
|
+
this.nextToken();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (this.canInsertSemicolon()) return;
|
|
160
|
+
this.error(DiagnosticCode.ExpectedToken, "Expected ';'", this.token);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
canInsertSemicolon(): boolean {
|
|
164
|
+
return this.at(TokenKind.EndOfFile) || this.at(TokenKind.CloseBrace) || this.token.precededByLineBreak;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
parseDelimitedList<T>(close: TokenKind, parseElement: () => T): T[] {
|
|
168
|
+
const items: T[] = [];
|
|
169
|
+
while (this.token.kind !== close && !this.at(TokenKind.EndOfFile)) {
|
|
170
|
+
const before = this.consumed;
|
|
171
|
+
items.push(parseElement());
|
|
172
|
+
if (this.at(TokenKind.Comma)) this.nextToken();
|
|
173
|
+
else break;
|
|
174
|
+
if (this.consumed === before && this.token.kind !== close) this.nextToken();
|
|
175
|
+
}
|
|
176
|
+
this.parseExpected(close);
|
|
177
|
+
return items;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
stringLiteralFromToken(token: Token): StringLiteral {
|
|
181
|
+
return { kind: SyntaxKind.StringLiteral, text: token.text, raw: token.text, value: String(token.value ?? ""), start: token.start, end: token.end };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// -- state save / restore (for speculative parsing) -----------------------
|
|
185
|
+
|
|
186
|
+
saveState(): ParserState {
|
|
187
|
+
return { index: this.index, tokens: this.tokens.slice(), pos: this.scanner.position, consumed: this.consumed };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
restoreState(state: ParserState): void {
|
|
191
|
+
this.index = state.index;
|
|
192
|
+
this.tokens = state.tokens;
|
|
193
|
+
this.scanner.seek(state.pos);
|
|
194
|
+
this.consumed = state.consumed;
|
|
195
|
+
}
|
|
196
|
+
}
|