@jalvin/compiler 1.0.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.
- package/dist/ast.d.ts +720 -0
- package/dist/ast.d.ts.map +1 -0
- package/dist/ast.js +58 -0
- package/dist/ast.js.map +1 -0
- package/dist/codegen.d.ts +106 -0
- package/dist/codegen.d.ts.map +1 -0
- package/dist/codegen.js +1624 -0
- package/dist/codegen.js.map +1 -0
- package/dist/diagnostics.d.ts +61 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +104 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/dist/lexer.d.ts +157 -0
- package/dist/lexer.d.ts.map +1 -0
- package/dist/lexer.js +677 -0
- package/dist/lexer.js.map +1 -0
- package/dist/parser.d.ts +114 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +2180 -0
- package/dist/parser.js.map +1 -0
- package/dist/typechecker.d.ts +205 -0
- package/dist/typechecker.d.ts.map +1 -0
- package/dist/typechecker.js +2196 -0
- package/dist/typechecker.js.map +1 -0
- package/package.json +35 -0
package/dist/lexer.js
ADDED
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3
|
+
// Jalvin Lexer
|
|
4
|
+
//
|
|
5
|
+
// Tokenises a .jalvin source file. Key behaviours:
|
|
6
|
+
// • Automatic semicolon insertion (like Go) after certain tokens
|
|
7
|
+
// when a newline is encountered.
|
|
8
|
+
// • String templates: `"Hello $name"` and `"Hello ${expr}"`.
|
|
9
|
+
// • Triple-quoted raw strings: `"""..."""`.
|
|
10
|
+
// • Coroutine keywords: launch, async, suspend, await.
|
|
11
|
+
// • `component` keyword for UI declarations.
|
|
12
|
+
// • `Bibi` is just an identifier — treated normally by the lexer.
|
|
13
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.Lexer = void 0;
|
|
16
|
+
exports.lex = lex;
|
|
17
|
+
const diagnostics_js_1 = require("./diagnostics.js");
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Keyword map
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
const KEYWORDS = {
|
|
22
|
+
fun: "KwFun" /* TokenKind.KwFun */,
|
|
23
|
+
component: "KwComponent" /* TokenKind.KwComponent */,
|
|
24
|
+
val: "KwVal" /* TokenKind.KwVal */,
|
|
25
|
+
var: "KwVar" /* TokenKind.KwVar */,
|
|
26
|
+
class: "KwClass" /* TokenKind.KwClass */,
|
|
27
|
+
data: "KwData" /* TokenKind.KwData */,
|
|
28
|
+
sealed: "KwSealed" /* TokenKind.KwSealed */,
|
|
29
|
+
object: "KwObject" /* TokenKind.KwObject */,
|
|
30
|
+
interface: "KwInterface" /* TokenKind.KwInterface */,
|
|
31
|
+
enum: "KwEnum" /* TokenKind.KwEnum */,
|
|
32
|
+
when: "KwWhen" /* TokenKind.KwWhen */,
|
|
33
|
+
if: "KwIf" /* TokenKind.KwIf */,
|
|
34
|
+
else: "KwElse" /* TokenKind.KwElse */,
|
|
35
|
+
for: "KwFor" /* TokenKind.KwFor */,
|
|
36
|
+
while: "KwWhile" /* TokenKind.KwWhile */,
|
|
37
|
+
do: "KwDo" /* TokenKind.KwDo */,
|
|
38
|
+
return: "KwReturn" /* TokenKind.KwReturn */,
|
|
39
|
+
break: "KwBreak" /* TokenKind.KwBreak */,
|
|
40
|
+
continue: "KwContinue" /* TokenKind.KwContinue */,
|
|
41
|
+
import: "KwImport" /* TokenKind.KwImport */,
|
|
42
|
+
package: "KwPackage" /* TokenKind.KwPackage */,
|
|
43
|
+
is: "KwIs" /* TokenKind.KwIs */,
|
|
44
|
+
as: "KwAs" /* TokenKind.KwAs */,
|
|
45
|
+
in: "KwIn" /* TokenKind.KwIn */,
|
|
46
|
+
by: "KwBy" /* TokenKind.KwBy */,
|
|
47
|
+
override: "KwOverride" /* TokenKind.KwOverride */,
|
|
48
|
+
open: "KwOpen" /* TokenKind.KwOpen */,
|
|
49
|
+
abstract: "KwAbstract" /* TokenKind.KwAbstract */,
|
|
50
|
+
companion: "KwCompanion" /* TokenKind.KwCompanion */,
|
|
51
|
+
init: "KwInit" /* TokenKind.KwInit */,
|
|
52
|
+
constructor: "KwConstructor" /* TokenKind.KwConstructor */,
|
|
53
|
+
this: "KwThis" /* TokenKind.KwThis */,
|
|
54
|
+
super: "KwSuper" /* TokenKind.KwSuper */,
|
|
55
|
+
launch: "KwLaunch" /* TokenKind.KwLaunch */,
|
|
56
|
+
async: "KwAsync" /* TokenKind.KwAsync */,
|
|
57
|
+
suspend: "KwSuspend" /* TokenKind.KwSuspend */,
|
|
58
|
+
await: "KwAwait" /* TokenKind.KwAwait */,
|
|
59
|
+
throw: "KwThrow" /* TokenKind.KwThrow */,
|
|
60
|
+
try: "KwTry" /* TokenKind.KwTry */,
|
|
61
|
+
catch: "KwCatch" /* TokenKind.KwCatch */,
|
|
62
|
+
finally: "KwFinally" /* TokenKind.KwFinally */,
|
|
63
|
+
typealias: "KwTypealias" /* TokenKind.KwTypealias */,
|
|
64
|
+
operator: "KwOperator" /* TokenKind.KwOperator */,
|
|
65
|
+
infix: "KwInfix" /* TokenKind.KwInfix */,
|
|
66
|
+
inline: "KwInline" /* TokenKind.KwInline */,
|
|
67
|
+
reified: "KwReified" /* TokenKind.KwReified */,
|
|
68
|
+
external: "KwExternal" /* TokenKind.KwExternal */,
|
|
69
|
+
internal: "KwInternal" /* TokenKind.KwInternal */,
|
|
70
|
+
private: "KwPrivate" /* TokenKind.KwPrivate */,
|
|
71
|
+
protected: "KwProtected" /* TokenKind.KwProtected */,
|
|
72
|
+
public: "KwPublic" /* TokenKind.KwPublic */,
|
|
73
|
+
tailrec: "KwTailrec" /* TokenKind.KwTailrec */,
|
|
74
|
+
const: "KwConst" /* TokenKind.KwConst */,
|
|
75
|
+
lateinit: "KwLateinit" /* TokenKind.KwLateinit */,
|
|
76
|
+
final: "KwFinal" /* TokenKind.KwFinal */,
|
|
77
|
+
true: "BooleanLiteral" /* TokenKind.BooleanLiteral */,
|
|
78
|
+
false: "BooleanLiteral" /* TokenKind.BooleanLiteral */,
|
|
79
|
+
null: "NullLiteral" /* TokenKind.NullLiteral */,
|
|
80
|
+
};
|
|
81
|
+
/** Tokens after which a newline triggers automatic semicolon insertion */
|
|
82
|
+
const ASI_SET = new Set([
|
|
83
|
+
"Identifier" /* TokenKind.Identifier */,
|
|
84
|
+
"IntLiteral" /* TokenKind.IntLiteral */,
|
|
85
|
+
"LongLiteral" /* TokenKind.LongLiteral */,
|
|
86
|
+
"FloatLiteral" /* TokenKind.FloatLiteral */,
|
|
87
|
+
"DoubleLiteral" /* TokenKind.DoubleLiteral */,
|
|
88
|
+
"BooleanLiteral" /* TokenKind.BooleanLiteral */,
|
|
89
|
+
"NullLiteral" /* TokenKind.NullLiteral */,
|
|
90
|
+
"StringLiteral" /* TokenKind.StringLiteral */,
|
|
91
|
+
"RawStringLiteral" /* TokenKind.RawStringLiteral */,
|
|
92
|
+
"StringTemplateEnd" /* TokenKind.StringTemplateEnd */,
|
|
93
|
+
"RParen" /* TokenKind.RParen */,
|
|
94
|
+
"RBrace" /* TokenKind.RBrace */,
|
|
95
|
+
"RBracket" /* TokenKind.RBracket */,
|
|
96
|
+
"KwReturn" /* TokenKind.KwReturn */,
|
|
97
|
+
"KwBreak" /* TokenKind.KwBreak */,
|
|
98
|
+
"KwContinue" /* TokenKind.KwContinue */,
|
|
99
|
+
"KwThis" /* TokenKind.KwThis */,
|
|
100
|
+
"KwSuper" /* TokenKind.KwSuper */,
|
|
101
|
+
"BangBang" /* TokenKind.BangBang */,
|
|
102
|
+
]);
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Lexer
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
class Lexer {
|
|
107
|
+
pos = 0;
|
|
108
|
+
line = 0;
|
|
109
|
+
col = 0;
|
|
110
|
+
src;
|
|
111
|
+
file;
|
|
112
|
+
diag;
|
|
113
|
+
constructor(src, file, diag) {
|
|
114
|
+
this.src = src;
|
|
115
|
+
this.file = file;
|
|
116
|
+
this.diag = diag;
|
|
117
|
+
}
|
|
118
|
+
tokenize() {
|
|
119
|
+
const tokens = [];
|
|
120
|
+
let prevKind = null;
|
|
121
|
+
while (this.pos < this.src.length) {
|
|
122
|
+
const ch = this.src[this.pos];
|
|
123
|
+
// ── Whitespace ───────────────────────────────────────────────────────
|
|
124
|
+
if (ch === " " || ch === "\t" || ch === "\r") {
|
|
125
|
+
this.advance();
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
// ── Newline → potential semicolon ────────────────────────────────────
|
|
129
|
+
if (ch === "\n") {
|
|
130
|
+
if (prevKind !== null && ASI_SET.has(prevKind)) {
|
|
131
|
+
const span = this.spanAt(this.pos, this.pos);
|
|
132
|
+
tokens.push({ kind: "Semicolon" /* TokenKind.Semicolon */, span, text: "\n" });
|
|
133
|
+
prevKind = "Semicolon" /* TokenKind.Semicolon */;
|
|
134
|
+
}
|
|
135
|
+
this.advance();
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
// ── Comments ─────────────────────────────────────────────────────────
|
|
139
|
+
if (ch === "/" && this.peek(1) === "/") {
|
|
140
|
+
this.skipLineComment();
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (ch === "/" && this.peek(1) === "*") {
|
|
144
|
+
this.skipBlockComment();
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const tok = this.nextToken();
|
|
148
|
+
if (tok) {
|
|
149
|
+
tokens.push(tok);
|
|
150
|
+
prevKind = tok.kind;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// EOF
|
|
154
|
+
tokens.push({
|
|
155
|
+
kind: "EOF" /* TokenKind.EOF */,
|
|
156
|
+
span: this.spanAt(this.pos, this.pos),
|
|
157
|
+
text: "",
|
|
158
|
+
});
|
|
159
|
+
return tokens;
|
|
160
|
+
}
|
|
161
|
+
// ── Token dispatch ─────────────────────────────────────────────────────────
|
|
162
|
+
nextToken() {
|
|
163
|
+
const start = this.pos;
|
|
164
|
+
const ch = this.src[start];
|
|
165
|
+
if (ch === undefined)
|
|
166
|
+
return null;
|
|
167
|
+
// Identifiers & keywords
|
|
168
|
+
if (isIdentStart(ch))
|
|
169
|
+
return this.lexIdentOrKeyword();
|
|
170
|
+
// Numbers
|
|
171
|
+
if (isDigit(ch))
|
|
172
|
+
return this.lexNumber();
|
|
173
|
+
// Strings
|
|
174
|
+
if (ch === '"') {
|
|
175
|
+
if (this.peek(1) === '"' && this.peek(2) === '"') {
|
|
176
|
+
return this.lexRawString();
|
|
177
|
+
}
|
|
178
|
+
return this.lexString();
|
|
179
|
+
}
|
|
180
|
+
// Operators & punctuation
|
|
181
|
+
return this.lexSymbol();
|
|
182
|
+
}
|
|
183
|
+
// ── Identifier / keyword ───────────────────────────────────────────────────
|
|
184
|
+
lexIdentOrKeyword() {
|
|
185
|
+
const start = this.pos;
|
|
186
|
+
const startLine = this.line;
|
|
187
|
+
const startCol = this.col;
|
|
188
|
+
while (this.pos < this.src.length && isIdentContinue(this.src[this.pos])) {
|
|
189
|
+
this.advance();
|
|
190
|
+
}
|
|
191
|
+
const text = this.src.slice(start, this.pos);
|
|
192
|
+
const span = this.makeSpan(start, startLine, startCol);
|
|
193
|
+
if (text === "_") {
|
|
194
|
+
return { kind: "Underscore" /* TokenKind.Underscore */, span, text };
|
|
195
|
+
}
|
|
196
|
+
const kwKind = KEYWORDS[text];
|
|
197
|
+
if (kwKind !== undefined) {
|
|
198
|
+
switch (kwKind) {
|
|
199
|
+
case "BooleanLiteral" /* TokenKind.BooleanLiteral */:
|
|
200
|
+
return { kind: kwKind, span, text, value: text === "true" };
|
|
201
|
+
case "NullLiteral" /* TokenKind.NullLiteral */:
|
|
202
|
+
return { kind: kwKind, span, text, value: null };
|
|
203
|
+
default:
|
|
204
|
+
return { kind: kwKind, span, text };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return { kind: "Identifier" /* TokenKind.Identifier */, span, text };
|
|
208
|
+
}
|
|
209
|
+
// ── Number literals ────────────────────────────────────────────────────────
|
|
210
|
+
lexNumber() {
|
|
211
|
+
const start = this.pos;
|
|
212
|
+
const startLine = this.line;
|
|
213
|
+
const startCol = this.col;
|
|
214
|
+
let isHex = false;
|
|
215
|
+
let isBinary = false;
|
|
216
|
+
if (this.src[this.pos] === "0") {
|
|
217
|
+
const next = this.peek(1);
|
|
218
|
+
if (next === "x" || next === "X") {
|
|
219
|
+
this.advance();
|
|
220
|
+
this.advance();
|
|
221
|
+
isHex = true;
|
|
222
|
+
}
|
|
223
|
+
else if (next === "b" || next === "B") {
|
|
224
|
+
this.advance();
|
|
225
|
+
this.advance();
|
|
226
|
+
isBinary = true;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (isHex) {
|
|
230
|
+
while (this.pos < this.src.length && isHexDigit(this.src[this.pos])) {
|
|
231
|
+
this.advance();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
else if (isBinary) {
|
|
235
|
+
while (this.pos < this.src.length && (this.src[this.pos] === "0" || this.src[this.pos] === "1")) {
|
|
236
|
+
this.advance();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
while (this.pos < this.src.length && (isDigit(this.src[this.pos]) || this.src[this.pos] === "_")) {
|
|
241
|
+
this.advance();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
let isFloat = false;
|
|
245
|
+
let isDouble = false;
|
|
246
|
+
if (!isHex && !isBinary) {
|
|
247
|
+
if (this.src[this.pos] === "." && isDigit(this.peek(1) ?? "")) {
|
|
248
|
+
isDouble = true;
|
|
249
|
+
this.advance(); // consume '.'
|
|
250
|
+
while (this.pos < this.src.length && (isDigit(this.src[this.pos]) || this.src[this.pos] === "_")) {
|
|
251
|
+
this.advance();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Exponent
|
|
255
|
+
if (this.src[this.pos] === "e" || this.src[this.pos] === "E") {
|
|
256
|
+
isDouble = true;
|
|
257
|
+
this.advance();
|
|
258
|
+
if (this.src[this.pos] === "+" || this.src[this.pos] === "-")
|
|
259
|
+
this.advance();
|
|
260
|
+
while (this.pos < this.src.length && isDigit(this.src[this.pos]))
|
|
261
|
+
this.advance();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
let suffix = "";
|
|
265
|
+
const sc = this.src[this.pos];
|
|
266
|
+
if (sc === "L" || sc === "l") {
|
|
267
|
+
suffix = "L";
|
|
268
|
+
this.advance();
|
|
269
|
+
}
|
|
270
|
+
else if (sc === "f" || sc === "F") {
|
|
271
|
+
suffix = "f";
|
|
272
|
+
isFloat = true;
|
|
273
|
+
this.advance();
|
|
274
|
+
}
|
|
275
|
+
else if (sc === "d" || sc === "D") {
|
|
276
|
+
suffix = "d";
|
|
277
|
+
isDouble = true;
|
|
278
|
+
this.advance();
|
|
279
|
+
}
|
|
280
|
+
const text = this.src.slice(start, this.pos);
|
|
281
|
+
const span = this.makeSpan(start, startLine, startCol);
|
|
282
|
+
const clean = text.replace(/_/g, "").replace(/[LlfFdD]$/, "");
|
|
283
|
+
if (suffix === "L") {
|
|
284
|
+
let val;
|
|
285
|
+
try {
|
|
286
|
+
val = BigInt(clean);
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
this.diag.error(span, diagnostics_js_1.E_INVALID_NUMBER_LITERAL, `Invalid long literal: ${text}`);
|
|
290
|
+
val = 0n;
|
|
291
|
+
}
|
|
292
|
+
return { kind: "LongLiteral" /* TokenKind.LongLiteral */, span, text, value: val };
|
|
293
|
+
}
|
|
294
|
+
if (isFloat) {
|
|
295
|
+
return { kind: "FloatLiteral" /* TokenKind.FloatLiteral */, span, text, value: parseFloat(clean) };
|
|
296
|
+
}
|
|
297
|
+
if (isDouble || text.includes(".") || text.toLowerCase().includes("e")) {
|
|
298
|
+
return { kind: "DoubleLiteral" /* TokenKind.DoubleLiteral */, span, text, value: parseFloat(clean) };
|
|
299
|
+
}
|
|
300
|
+
const intVal = isHex
|
|
301
|
+
? parseInt(clean.slice(2), 16)
|
|
302
|
+
: isBinary
|
|
303
|
+
? parseInt(clean.slice(2), 2)
|
|
304
|
+
: parseInt(clean, 10);
|
|
305
|
+
if (isNaN(intVal)) {
|
|
306
|
+
this.diag.error(span, diagnostics_js_1.E_INVALID_NUMBER_LITERAL, `Invalid integer literal: ${text}`);
|
|
307
|
+
return { kind: "IntLiteral" /* TokenKind.IntLiteral */, span, text, value: 0 };
|
|
308
|
+
}
|
|
309
|
+
return { kind: "IntLiteral" /* TokenKind.IntLiteral */, span, text, value: intVal };
|
|
310
|
+
}
|
|
311
|
+
// ── String literals ────────────────────────────────────────────────────────
|
|
312
|
+
lexString() {
|
|
313
|
+
const start = this.pos;
|
|
314
|
+
const startLine = this.line;
|
|
315
|
+
const startCol = this.col;
|
|
316
|
+
this.advance(); // consume opening "
|
|
317
|
+
let value = "";
|
|
318
|
+
let isTemplate = false;
|
|
319
|
+
let closed = false;
|
|
320
|
+
const templateParts = [];
|
|
321
|
+
while (this.pos < this.src.length) {
|
|
322
|
+
const ch = this.src[this.pos];
|
|
323
|
+
if (ch === "\"") {
|
|
324
|
+
this.advance();
|
|
325
|
+
closed = true;
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
if (ch === "\n") {
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
if (ch === "$") {
|
|
332
|
+
if (this.peek(1) === "{") {
|
|
333
|
+
isTemplate = true;
|
|
334
|
+
templateParts.push(value);
|
|
335
|
+
value = "";
|
|
336
|
+
// We just return the string fragment; the parser handles interpolation
|
|
337
|
+
// by re-lexing nested expressions. For simplicity we encode templates
|
|
338
|
+
// into a single StringTemplateExpr token with the raw text.
|
|
339
|
+
// Consume ${ block
|
|
340
|
+
this.advance();
|
|
341
|
+
this.advance();
|
|
342
|
+
let depth = 1;
|
|
343
|
+
let exprText = "";
|
|
344
|
+
while (this.pos < this.src.length && depth > 0) {
|
|
345
|
+
const ec = this.src[this.pos];
|
|
346
|
+
if (ec === "{")
|
|
347
|
+
depth++;
|
|
348
|
+
else if (ec === "}") {
|
|
349
|
+
depth--;
|
|
350
|
+
if (depth === 0) {
|
|
351
|
+
this.advance();
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
exprText += ec;
|
|
356
|
+
this.advance();
|
|
357
|
+
}
|
|
358
|
+
templateParts.push(`\${${exprText}}`);
|
|
359
|
+
}
|
|
360
|
+
else if (isIdentStart(this.peek(1) ?? "")) {
|
|
361
|
+
isTemplate = true;
|
|
362
|
+
templateParts.push(value);
|
|
363
|
+
value = "";
|
|
364
|
+
this.advance(); // $
|
|
365
|
+
const identStart = this.pos;
|
|
366
|
+
while (this.pos < this.src.length && isIdentContinue(this.src[this.pos]))
|
|
367
|
+
this.advance();
|
|
368
|
+
const ident = this.src.slice(identStart, this.pos);
|
|
369
|
+
templateParts.push(`\${${ident}}`);
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
value += ch;
|
|
373
|
+
this.advance();
|
|
374
|
+
}
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (ch === "\\") {
|
|
378
|
+
const span = this.makeSpan(this.pos, this.line, this.col);
|
|
379
|
+
value += this.parseEscape(span);
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
value += ch;
|
|
383
|
+
this.advance();
|
|
384
|
+
}
|
|
385
|
+
if (!closed) {
|
|
386
|
+
const span = this.makeSpan(start, startLine, startCol);
|
|
387
|
+
this.diag.error(span, diagnostics_js_1.E_UNTERMINATED_STRING, "Unterminated string literal");
|
|
388
|
+
}
|
|
389
|
+
const text = this.src.slice(start, this.pos);
|
|
390
|
+
const span = this.makeSpan(start, startLine, startCol);
|
|
391
|
+
if (isTemplate) {
|
|
392
|
+
const raw = templateParts.join("") + value;
|
|
393
|
+
return { kind: "StringLiteral" /* TokenKind.StringLiteral */, span, text, value: raw };
|
|
394
|
+
}
|
|
395
|
+
return { kind: "StringLiteral" /* TokenKind.StringLiteral */, span, text, value };
|
|
396
|
+
}
|
|
397
|
+
lexRawString() {
|
|
398
|
+
const start = this.pos;
|
|
399
|
+
const startLine = this.line;
|
|
400
|
+
const startCol = this.col;
|
|
401
|
+
this.advance();
|
|
402
|
+
this.advance();
|
|
403
|
+
this.advance(); // consume """
|
|
404
|
+
let value = "";
|
|
405
|
+
let closed = false;
|
|
406
|
+
while (this.pos < this.src.length) {
|
|
407
|
+
if (this.src[this.pos] === '"' && this.peek(1) === '"' && this.peek(2) === '"') {
|
|
408
|
+
this.advance();
|
|
409
|
+
this.advance();
|
|
410
|
+
this.advance();
|
|
411
|
+
closed = true;
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
const c = this.src[this.pos];
|
|
415
|
+
if (c === "\n")
|
|
416
|
+
this.line++; // no col tracking in raw strings
|
|
417
|
+
value += c;
|
|
418
|
+
this.advance();
|
|
419
|
+
}
|
|
420
|
+
if (!closed) {
|
|
421
|
+
const span = this.makeSpan(start, startLine, startCol);
|
|
422
|
+
this.diag.error(span, diagnostics_js_1.E_UNTERMINATED_STRING, "Unterminated triple-quoted string");
|
|
423
|
+
}
|
|
424
|
+
const text = this.src.slice(start, this.pos);
|
|
425
|
+
const span = this.makeSpan(start, startLine, startCol);
|
|
426
|
+
return { kind: "RawStringLiteral" /* TokenKind.RawStringLiteral */, span, text, value };
|
|
427
|
+
}
|
|
428
|
+
parseEscape(span) {
|
|
429
|
+
this.advance(); // consume backslash
|
|
430
|
+
const ec = this.src[this.pos];
|
|
431
|
+
this.advance();
|
|
432
|
+
switch (ec) {
|
|
433
|
+
case "n": return "\n";
|
|
434
|
+
case "t": return "\t";
|
|
435
|
+
case "r": return "\r";
|
|
436
|
+
case "\\": return "\\";
|
|
437
|
+
case '"': return '"';
|
|
438
|
+
case "'": return "'";
|
|
439
|
+
case "0": return "\0";
|
|
440
|
+
case "$": return "$";
|
|
441
|
+
case "u": {
|
|
442
|
+
if (this.src[this.pos] === "{") {
|
|
443
|
+
this.advance(); // {
|
|
444
|
+
let hex = "";
|
|
445
|
+
while (this.pos < this.src.length && this.src[this.pos] !== "}") {
|
|
446
|
+
hex += this.src[this.pos];
|
|
447
|
+
this.advance();
|
|
448
|
+
}
|
|
449
|
+
this.advance(); // }
|
|
450
|
+
const cp = parseInt(hex, 16);
|
|
451
|
+
if (isNaN(cp)) {
|
|
452
|
+
this.diag.error(span, diagnostics_js_1.E_INVALID_ESCAPE, `Invalid unicode escape: \\u{${hex}}`);
|
|
453
|
+
return "";
|
|
454
|
+
}
|
|
455
|
+
return String.fromCodePoint(cp);
|
|
456
|
+
}
|
|
457
|
+
// \uXXXX
|
|
458
|
+
let hex = "";
|
|
459
|
+
for (let i = 0; i < 4; i++) {
|
|
460
|
+
hex += this.src[this.pos] ?? "";
|
|
461
|
+
this.advance();
|
|
462
|
+
}
|
|
463
|
+
const cp = parseInt(hex, 16);
|
|
464
|
+
if (isNaN(cp)) {
|
|
465
|
+
this.diag.error(span, diagnostics_js_1.E_INVALID_ESCAPE, `Invalid unicode escape: \\u${hex}`);
|
|
466
|
+
return "";
|
|
467
|
+
}
|
|
468
|
+
return String.fromCharCode(cp);
|
|
469
|
+
}
|
|
470
|
+
default:
|
|
471
|
+
this.diag.error(span, diagnostics_js_1.E_INVALID_ESCAPE, `Unknown escape sequence: \\${ec ?? ""}`);
|
|
472
|
+
return ec ?? "";
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
// ── Symbols & operators ────────────────────────────────────────────────────
|
|
476
|
+
lexSymbol() {
|
|
477
|
+
const start = this.pos;
|
|
478
|
+
const startLine = this.line;
|
|
479
|
+
const startCol = this.col;
|
|
480
|
+
const ch = this.src[this.pos];
|
|
481
|
+
const next = this.peek(1);
|
|
482
|
+
const next2 = this.peek(2);
|
|
483
|
+
const mk = (kind, len) => {
|
|
484
|
+
const text = this.src.slice(start, start + len);
|
|
485
|
+
for (let i = 0; i < len; i++)
|
|
486
|
+
this.advance();
|
|
487
|
+
return { kind, span: this.makeSpan(start, startLine, startCol), text };
|
|
488
|
+
};
|
|
489
|
+
switch (ch) {
|
|
490
|
+
case "+":
|
|
491
|
+
if (next === "+")
|
|
492
|
+
return mk("PlusPlus" /* TokenKind.PlusPlus */, 2);
|
|
493
|
+
if (next === "=")
|
|
494
|
+
return mk("PlusEq" /* TokenKind.PlusEq */, 2);
|
|
495
|
+
return mk("Plus" /* TokenKind.Plus */, 1);
|
|
496
|
+
case "-":
|
|
497
|
+
if (next === "-")
|
|
498
|
+
return mk("MinusMinus" /* TokenKind.MinusMinus */, 2);
|
|
499
|
+
if (next === "=")
|
|
500
|
+
return mk("MinusEq" /* TokenKind.MinusEq */, 2);
|
|
501
|
+
if (next === ">")
|
|
502
|
+
return mk("Arrow" /* TokenKind.Arrow */, 2);
|
|
503
|
+
return mk("Minus" /* TokenKind.Minus */, 1);
|
|
504
|
+
case "*":
|
|
505
|
+
if (next === "=")
|
|
506
|
+
return mk("StarEq" /* TokenKind.StarEq */, 2);
|
|
507
|
+
return mk("Star" /* TokenKind.Star */, 1);
|
|
508
|
+
case "/":
|
|
509
|
+
if (next === "=")
|
|
510
|
+
return mk("SlashEq" /* TokenKind.SlashEq */, 2);
|
|
511
|
+
return mk("Slash" /* TokenKind.Slash */, 1);
|
|
512
|
+
case "%":
|
|
513
|
+
if (next === "=")
|
|
514
|
+
return mk("PercentEq" /* TokenKind.PercentEq */, 2);
|
|
515
|
+
return mk("Percent" /* TokenKind.Percent */, 1);
|
|
516
|
+
case "=":
|
|
517
|
+
if (next === "=" && next2 === "=")
|
|
518
|
+
return mk("EqEqEq" /* TokenKind.EqEqEq */, 3);
|
|
519
|
+
if (next === "=")
|
|
520
|
+
return mk("EqEq" /* TokenKind.EqEq */, 2);
|
|
521
|
+
if (next === ">")
|
|
522
|
+
return mk("FatArrow" /* TokenKind.FatArrow */, 2);
|
|
523
|
+
return mk("Eq" /* TokenKind.Eq */, 1);
|
|
524
|
+
case "!":
|
|
525
|
+
if (next === "=" && next2 === "=")
|
|
526
|
+
return mk("BangEqEq" /* TokenKind.BangEqEq */, 3);
|
|
527
|
+
if (next === "=")
|
|
528
|
+
return mk("BangEq" /* TokenKind.BangEq */, 2);
|
|
529
|
+
if (next === "!")
|
|
530
|
+
return mk("BangBang" /* TokenKind.BangBang */, 2);
|
|
531
|
+
return mk("Bang" /* TokenKind.Bang */, 1);
|
|
532
|
+
case "<":
|
|
533
|
+
if (next === "=")
|
|
534
|
+
return mk("LtEq" /* TokenKind.LtEq */, 2);
|
|
535
|
+
return mk("Lt" /* TokenKind.Lt */, 1);
|
|
536
|
+
case ">":
|
|
537
|
+
if (next === "=")
|
|
538
|
+
return mk("GtEq" /* TokenKind.GtEq */, 2);
|
|
539
|
+
return mk("Gt" /* TokenKind.Gt */, 1);
|
|
540
|
+
case "&":
|
|
541
|
+
if (next === "&")
|
|
542
|
+
return mk("AmpAmp" /* TokenKind.AmpAmp */, 2);
|
|
543
|
+
return mk("Amp" /* TokenKind.Amp */, 1);
|
|
544
|
+
case "|":
|
|
545
|
+
if (next === "|")
|
|
546
|
+
return mk("PipePipe" /* TokenKind.PipePipe */, 2);
|
|
547
|
+
return mk("Pipe" /* TokenKind.Pipe */, 1);
|
|
548
|
+
case "?":
|
|
549
|
+
if (next === ":")
|
|
550
|
+
return mk("QuestionColon" /* TokenKind.QuestionColon */, 2);
|
|
551
|
+
if (next === ".")
|
|
552
|
+
return mk("QuestionDot" /* TokenKind.QuestionDot */, 2);
|
|
553
|
+
return mk("Question" /* TokenKind.Question */, 1);
|
|
554
|
+
case ".":
|
|
555
|
+
if (next === "." && next2 === "<")
|
|
556
|
+
return mk("DotDotLt" /* TokenKind.DotDotLt */, 3);
|
|
557
|
+
if (next === ".")
|
|
558
|
+
return mk("DotDot" /* TokenKind.DotDot */, 2);
|
|
559
|
+
return mk("Dot" /* TokenKind.Dot */, 1);
|
|
560
|
+
case ":":
|
|
561
|
+
if (next === ":")
|
|
562
|
+
return mk("ColonColon" /* TokenKind.ColonColon */, 2);
|
|
563
|
+
return mk("Colon" /* TokenKind.Colon */, 1);
|
|
564
|
+
case "{": return mk("LBrace" /* TokenKind.LBrace */, 1);
|
|
565
|
+
case "}": return mk("RBrace" /* TokenKind.RBrace */, 1);
|
|
566
|
+
case "(": return mk("LParen" /* TokenKind.LParen */, 1);
|
|
567
|
+
case ")": return mk("RParen" /* TokenKind.RParen */, 1);
|
|
568
|
+
case "[": return mk("LBracket" /* TokenKind.LBracket */, 1);
|
|
569
|
+
case "]": return mk("RBracket" /* TokenKind.RBracket */, 1);
|
|
570
|
+
case ",": return mk("Comma" /* TokenKind.Comma */, 1);
|
|
571
|
+
case ";": return mk("Semicolon" /* TokenKind.Semicolon */, 1);
|
|
572
|
+
case "@": return mk("At" /* TokenKind.At */, 1);
|
|
573
|
+
case "#": return mk("Hash" /* TokenKind.Hash */, 1);
|
|
574
|
+
case "^": return mk("Caret" /* TokenKind.Caret */, 1);
|
|
575
|
+
case "~": return mk("Tilde" /* TokenKind.Tilde */, 1);
|
|
576
|
+
default: {
|
|
577
|
+
const span = this.makeSpan(start, startLine, startCol);
|
|
578
|
+
this.diag.error(span, diagnostics_js_1.E_UNEXPECTED_CHAR, `Unexpected character: '${ch}'`);
|
|
579
|
+
this.advance();
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
// ── Comment skipping ───────────────────────────────────────────────────────
|
|
585
|
+
skipLineComment() {
|
|
586
|
+
while (this.pos < this.src.length && this.src[this.pos] !== "\n") {
|
|
587
|
+
this.advance();
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
skipBlockComment() {
|
|
591
|
+
const start = this.pos;
|
|
592
|
+
const startLine = this.line;
|
|
593
|
+
const startCol = this.col;
|
|
594
|
+
this.advance();
|
|
595
|
+
this.advance(); // /*
|
|
596
|
+
let depth = 1;
|
|
597
|
+
while (this.pos < this.src.length) {
|
|
598
|
+
const c = this.src[this.pos];
|
|
599
|
+
if (c === "/" && this.peek(1) === "*") {
|
|
600
|
+
depth++;
|
|
601
|
+
this.advance();
|
|
602
|
+
this.advance();
|
|
603
|
+
continue;
|
|
604
|
+
}
|
|
605
|
+
if (c === "*" && this.peek(1) === "/") {
|
|
606
|
+
depth--;
|
|
607
|
+
this.advance();
|
|
608
|
+
this.advance();
|
|
609
|
+
if (depth === 0)
|
|
610
|
+
return;
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
this.advance();
|
|
614
|
+
}
|
|
615
|
+
const span = this.makeSpan(start, startLine, startCol);
|
|
616
|
+
this.diag.error(span, diagnostics_js_1.E_UNTERMINATED_BLOCK_COMMENT, "Unterminated block comment");
|
|
617
|
+
}
|
|
618
|
+
// ── Utilities ──────────────────────────────────────────────────────────────
|
|
619
|
+
advance() {
|
|
620
|
+
if (this.src[this.pos] === "\n") {
|
|
621
|
+
this.line++;
|
|
622
|
+
this.col = 0;
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
this.col++;
|
|
626
|
+
}
|
|
627
|
+
this.pos++;
|
|
628
|
+
}
|
|
629
|
+
peek(offset) {
|
|
630
|
+
return this.src[this.pos + offset];
|
|
631
|
+
}
|
|
632
|
+
spanAt(startPos, endPos) {
|
|
633
|
+
return {
|
|
634
|
+
file: this.file,
|
|
635
|
+
startLine: this.line,
|
|
636
|
+
startCol: this.col,
|
|
637
|
+
endLine: this.line,
|
|
638
|
+
endCol: this.col,
|
|
639
|
+
startOffset: startPos,
|
|
640
|
+
endOffset: endPos,
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
makeSpan(startOffset, startLine, startCol) {
|
|
644
|
+
return {
|
|
645
|
+
file: this.file,
|
|
646
|
+
startLine,
|
|
647
|
+
startCol,
|
|
648
|
+
endLine: this.line,
|
|
649
|
+
endCol: this.col,
|
|
650
|
+
startOffset,
|
|
651
|
+
endOffset: this.pos,
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
exports.Lexer = Lexer;
|
|
656
|
+
// ---------------------------------------------------------------------------
|
|
657
|
+
// Character classification helpers
|
|
658
|
+
// ---------------------------------------------------------------------------
|
|
659
|
+
function isIdentStart(ch) {
|
|
660
|
+
return /[a-zA-Z_]/.test(ch);
|
|
661
|
+
}
|
|
662
|
+
function isIdentContinue(ch) {
|
|
663
|
+
return /[a-zA-Z0-9_]/.test(ch);
|
|
664
|
+
}
|
|
665
|
+
function isDigit(ch) {
|
|
666
|
+
return ch >= "0" && ch <= "9";
|
|
667
|
+
}
|
|
668
|
+
function isHexDigit(ch) {
|
|
669
|
+
return (ch >= "0" && ch <= "9") || (ch >= "a" && ch <= "f") || (ch >= "A" && ch <= "F");
|
|
670
|
+
}
|
|
671
|
+
// ---------------------------------------------------------------------------
|
|
672
|
+
// Public helper: lex a source file
|
|
673
|
+
// ---------------------------------------------------------------------------
|
|
674
|
+
function lex(src, file, diag) {
|
|
675
|
+
return new Lexer(src, file, diag).tokenize();
|
|
676
|
+
}
|
|
677
|
+
//# sourceMappingURL=lexer.js.map
|