@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/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