gofront 0.0.8 → 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.
Files changed (51) hide show
  1. package/README.md +88 -12
  2. package/package.json +8 -4
  3. package/src/codegen/expressions.js +520 -1717
  4. package/src/{codegen.js → codegen/index.js} +155 -184
  5. package/src/codegen/runtime.js +151 -0
  6. package/src/codegen/statements.js +305 -296
  7. package/src/codegen/stdlib/builder.js +36 -0
  8. package/src/codegen/stdlib/bytes.js +62 -0
  9. package/src/codegen/stdlib/errors.js +17 -0
  10. package/src/codegen/stdlib/fmt.js +101 -0
  11. package/src/codegen/stdlib/gom.js +152 -0
  12. package/src/codegen/stdlib/html.js +17 -0
  13. package/src/codegen/stdlib/index.js +95 -0
  14. package/src/codegen/stdlib/io.js +33 -0
  15. package/src/codegen/stdlib/maps.js +42 -0
  16. package/src/codegen/stdlib/math.js +53 -0
  17. package/src/codegen/stdlib/os.js +10 -0
  18. package/src/codegen/stdlib/path.js +31 -0
  19. package/src/codegen/stdlib/rand.js +24 -0
  20. package/src/codegen/stdlib/regexp.js +51 -0
  21. package/src/codegen/stdlib/slices.js +48 -0
  22. package/src/codegen/stdlib/sort.js +28 -0
  23. package/src/codegen/stdlib/strconv.js +30 -0
  24. package/src/codegen/stdlib/strings.js +83 -0
  25. package/src/codegen/stdlib/time.js +54 -0
  26. package/src/codegen/stdlib/unicode.js +22 -0
  27. package/src/codegen/stdlib/utf8.js +23 -0
  28. package/src/codegen/templ.js +281 -0
  29. package/src/compiler.js +102 -62
  30. package/src/dts-parser.js +355 -371
  31. package/src/index.js +22 -54
  32. package/src/lexer.js +200 -286
  33. package/src/minifier.js +305 -318
  34. package/src/parser/expressions.js +54 -36
  35. package/src/{parser.js → parser/index.js} +12 -5
  36. package/src/parser/statements.js +29 -35
  37. package/src/parser/types.js +31 -29
  38. package/src/templ-lexer.js +737 -0
  39. package/src/templ-parser.js +235 -0
  40. package/src/tokens.js +109 -0
  41. package/src/typechecker/assignability.js +258 -0
  42. package/src/typechecker/expressions.js +559 -549
  43. package/src/typechecker/index.js +555 -0
  44. package/src/typechecker/resolve.js +421 -0
  45. package/src/typechecker/statements.js +267 -280
  46. package/src/typechecker/stdlib/core.js +592 -0
  47. package/src/typechecker/stdlib/extended.js +332 -0
  48. package/src/typechecker/stdlib.js +9 -1060
  49. package/src/typechecker/termination.js +83 -0
  50. package/src/typechecker/types.js +6 -3
  51. package/src/typechecker.js +0 -1271
package/src/index.js CHANGED
@@ -27,15 +27,13 @@ const _require = createRequire(import.meta.url);
27
27
  const { version } = _require("../package.json");
28
28
 
29
29
  import { basename, dirname, join, relative, resolve } from "node:path";
30
- import { CodeGen } from "./codegen.js";
31
- import { compileDir } from "./compiler.js";
30
+ import { CodeGen } from "./codegen/index.js";
31
+ import { compileDir, resolveImports } from "./compiler.js";
32
32
  import { createDevServer, liveReloadClient } from "./dev-server.js";
33
- import { parseDts } from "./dts-parser.js";
34
33
  import { Lexer } from "./lexer.js";
35
34
  import { minify } from "./minifier.js";
36
- import { Parser } from "./parser.js";
37
- import { isLocalPath, resolveAll, resolveGwDir } from "./resolver.js";
38
- import { TypeChecker } from "./typechecker.js";
35
+ import { Parser } from "./parser/index.js";
36
+ import { TypeChecker } from "./typechecker/index.js";
39
37
 
40
38
  // ── Parse CLI args ───────────────────────────────────────────
41
39
 
@@ -173,55 +171,21 @@ function runCompile() {
173
171
  }
174
172
 
175
173
  const checker = new TypeChecker();
176
- const fromDir = dirname(inputPath);
177
174
  const jsImports = new Map();
178
-
179
- for (const imp of ast.imports) {
180
- for (const { path } of imp.imports) {
181
- if (!path.startsWith("js:")) continue;
182
- const dtsPath = join(fromDir, path.slice(3));
183
- try {
184
- const { types, values } = parseDts(readFileSync(dtsPath, "utf8"));
185
- checker.addDefinitions(types, values);
186
- } catch (e) {
187
- throw new Error(`cannot read '${dtsPath}': ${e.message}`);
188
- }
189
- }
190
- }
191
-
192
- const resolved = resolveAll(ast.imports, inputPath, parseDts);
193
- for (const [path, info] of resolved) {
194
- if (!info) continue;
195
- checker.addDefinitions(info.types, info.values);
196
- jsImports.set(path, [...info.values.keys()]);
197
- }
198
-
199
175
  const bundledPackages = new Set();
200
176
  const preambles = [];
201
177
 
202
- for (const imp of ast.imports) {
203
- for (const { path, alias } of imp.imports) {
204
- if (!isLocalPath(path)) continue;
205
- const depDir = resolveGwDir(path, inputPath);
206
- if (!depDir) {
207
- console.error(
208
- `gofront: warning: cannot find local package '${path}' relative to ${fromDir}`,
209
- );
210
- continue;
211
- }
212
- const dep = compileDir(depDir);
213
- preambles.push(dep.js);
214
- const nameUsed = alias ?? dep.pkgName;
215
- bundledPackages.add(nameUsed);
216
- checker.addPackageNamespace(
217
- nameUsed,
218
- dep.exportedSymbols,
219
- dep.exportedTypes,
220
- );
221
- }
222
- }
178
+ resolveImports(
179
+ [ast],
180
+ inputPath,
181
+ checker,
182
+ jsImports,
183
+ bundledPackages,
184
+ preambles,
185
+ );
223
186
 
224
187
  const errors = checker.check(ast);
188
+ checker.reportUnusedImports();
225
189
  if (errors.length > 0) {
226
190
  throw new Error(errors.map((e) => e.message).join("\n"));
227
191
  }
@@ -311,7 +275,7 @@ if (serveMode) {
311
275
  devServer = createDevServer(serveDir, servePort);
312
276
  }
313
277
 
314
- function buildOnce() {
278
+ function buildOnce(changedFile = null) {
315
279
  try {
316
280
  const startMs = performance.now();
317
281
  const result = runCompile();
@@ -326,16 +290,19 @@ function buildOnce() {
326
290
  }
327
291
  }
328
292
  const elapsedMs = (performance.now() - startMs).toFixed(0);
293
+ const changeNote = changedFile ? ` — ${changedFile} changed` : "";
329
294
  if (outputFile) {
330
295
  writeFileSync(outputFile, `${js}\n`);
331
296
  console.error(
332
- `[${timestamp()}] gofront: OK — wrote ${outputFile} (${elapsedMs}ms)`,
297
+ `[${timestamp()}] gofront: OK — wrote ${outputFile} (${elapsedMs}ms${changeNote})`,
333
298
  );
334
299
  } else {
335
300
  // Clear screen then print
336
301
  process.stdout.write("\x1Bc");
337
302
  console.log(js);
338
- console.error(`[${timestamp()}] gofront: OK (${elapsedMs}ms)`);
303
+ console.error(
304
+ `[${timestamp()}] gofront: OK (${elapsedMs}ms${changeNote})`,
305
+ );
339
306
  }
340
307
  devServer?.notify();
341
308
  } catch (e) {
@@ -352,9 +319,10 @@ const watchTarget = isDir ? inputPath : dirname(inputPath);
352
319
 
353
320
  let debounce = null;
354
321
  watch(watchTarget, { recursive: true }, (_event, filename) => {
355
- if (filename && !filename.endsWith(".go")) return;
322
+ if (filename && !filename.endsWith(".go") && !filename.endsWith(".templ"))
323
+ return;
356
324
  clearTimeout(debounce);
357
- debounce = setTimeout(() => buildOnce(), 80);
325
+ debounce = setTimeout(() => buildOnce(filename), 80);
358
326
  });
359
327
 
360
328
  console.error(`[${timestamp()}] gofront: watching ${inputArg} ...`);
package/src/lexer.js CHANGED
@@ -1,99 +1,12 @@
1
1
  // Lexer: tokenizes GoFront source into a flat token stream.
2
2
  // Implements Go-style automatic semicolon insertion.
3
+ //
4
+ // `T` and `Token` live in ./tokens.js (a pure-data leaf module shared by
5
+ // lexer, parser, and codegen). They are re-exported here for backward compat.
3
6
 
4
- export const T = {
5
- // Literals
6
- INT: "INT",
7
- FLOAT: "FLOAT",
8
- STRING: "STRING",
9
- IDENT: "IDENT",
10
-
11
- IMAG: "IMAG",
12
-
13
- // Keywords
14
- FUNC: "func",
15
- VAR: "var",
16
- CONST: "const",
17
- TYPE: "type",
18
- STRUCT: "struct",
19
- INTERFACE: "interface",
20
- IF: "if",
21
- ELSE: "else",
22
- FOR: "for",
23
- RANGE: "range",
24
- RETURN: "return",
25
- PACKAGE: "package",
26
- IMPORT: "import",
27
- TRUE: "true",
28
- FALSE: "false",
29
- NIL: "nil",
30
- BREAK: "break",
31
- CONTINUE: "continue",
32
- SWITCH: "switch",
33
- CASE: "case",
34
- DEFAULT: "default",
35
- MAP: "map",
36
- NEW: "new",
37
- DEFER: "defer",
38
- ASYNC: "async",
39
- AWAIT: "await",
40
- GO: "go",
41
- CHAN: "chan",
42
- SELECT: "select",
43
-
44
- // Operators
45
- PLUS: "+",
46
- MINUS: "-",
47
- STAR: "*",
48
- SLASH: "/",
49
- PERCENT: "%",
50
- EQ: "==",
51
- NEQ: "!=",
52
- LT: "<",
53
- GT: ">",
54
- LTE: "<=",
55
- GTE: ">=",
56
- AND: "&&",
57
- OR: "||",
58
- NOT: "!",
59
- ASSIGN: "=",
60
- DEFINE: ":=",
61
- PLUS_ASSIGN: "+=",
62
- MINUS_ASSIGN: "-=",
63
- STAR_ASSIGN: "*=",
64
- SLASH_ASSIGN: "/=",
65
- PERCENT_ASSIGN: "%=",
66
- AMP_ASSIGN: "&=",
67
- PIPE_ASSIGN: "|=",
68
- CARET_ASSIGN: "^=",
69
- LSHIFT_ASSIGN: "<<=",
70
- RSHIFT_ASSIGN: ">>=",
71
- INC: "++",
72
- DEC: "--",
73
- AMP: "&",
74
- PIPE: "|",
75
- CARET: "^",
76
- AND_NOT: "&^",
77
- TILDE: "~",
78
- LSHIFT: "<<",
79
- RSHIFT: ">>",
80
- ELLIPSIS: "...",
81
- FALLTHROUGH: "fallthrough",
82
-
83
- // Delimiters
84
- LPAREN: "(",
85
- RPAREN: ")",
86
- LBRACE: "{",
87
- RBRACE: "}",
88
- LBRACKET: "[",
89
- RBRACKET: "]",
90
- COMMA: ",",
91
- DOT: ".",
92
- COLON: ":",
93
- SEMICOLON: ";",
94
-
95
- EOF: "EOF",
96
- };
7
+ import { T, Token } from "./tokens.js";
8
+
9
+ export { T, Token };
97
10
 
98
11
  // Tokens where a trailing newline inserts a semicolon (Go spec §Semicolons)
99
12
  const SEMI_TRIGGERS = new Set([
@@ -149,17 +62,29 @@ const KEYWORDS = new Set([
149
62
  "select",
150
63
  ]);
151
64
 
152
- export class Token {
153
- constructor(type, value, line, col) {
154
- this.type = type;
155
- this.value = value;
156
- this.line = line;
157
- this.col = col;
158
- }
159
- toString() {
160
- return `Token(${this.type}, ${JSON.stringify(this.value)}, ${this.line}:${this.col})`;
161
- }
162
- }
65
+ // Single-character punctuation: push a fixed token with no lookahead.
66
+ const PUNCT_TOKEN = {
67
+ "~": [T.TILDE, "~"],
68
+ "(": [T.LPAREN, "("],
69
+ ")": [T.RPAREN, ")"],
70
+ "{": [T.LBRACE, "{"],
71
+ "}": [T.RBRACE, "}"],
72
+ "[": [T.LBRACKET, "["],
73
+ "]": [T.RBRACKET, "]"],
74
+ ",": [T.COMMA, ","],
75
+ ";": [T.SEMICOLON, ";"],
76
+ };
77
+
78
+ // Operator + optional "=" compound form: [compoundType, compoundVal, baseType, baseVal]
79
+ const ASSIGN_OP = {
80
+ "*": [T.STAR_ASSIGN, "*=", T.STAR, "*"],
81
+ "/": [T.SLASH_ASSIGN, "/=", T.SLASH, "/"],
82
+ "%": [T.PERCENT_ASSIGN, "%=", T.PERCENT, "%"],
83
+ "=": [T.EQ, "==", T.ASSIGN, "="],
84
+ "!": [T.NEQ, "!=", T.NOT, "!"],
85
+ "^": [T.CARET_ASSIGN, "^=", T.CARET, "^"],
86
+ ":": [T.DEFINE, ":=", T.COLON, ":"],
87
+ };
163
88
 
164
89
  export class LexError extends Error {
165
90
  constructor(msg, line, col, filename, sourceCode) {
@@ -262,7 +187,7 @@ export class Lexer {
262
187
  const l = this.line,
263
188
  c = this.col;
264
189
  this.advance(); // opening "
265
- let s = "";
190
+ const parts = [];
266
191
  while (this.pos < this.src.length && this.peek() !== '"') {
267
192
  if (this.peek() === "\n") this.err("Unterminated string literal");
268
193
  if (this.peek() === "\\") {
@@ -270,43 +195,42 @@ export class Lexer {
270
195
  const esc = this.advance();
271
196
  switch (esc) {
272
197
  case "n":
273
- s += "\n";
198
+ parts.push("\n");
274
199
  break;
275
200
  case "t":
276
- s += "\t";
201
+ parts.push("\t");
277
202
  break;
278
203
  case "r":
279
- s += "\r";
204
+ parts.push("\r");
280
205
  break;
281
206
  case '"':
282
- s += '"';
207
+ parts.push('"');
283
208
  break;
284
209
  case "\\":
285
- s += "\\";
210
+ parts.push("\\");
286
211
  break;
287
212
  case "0":
288
- s += "\0";
213
+ parts.push("\0");
289
214
  break;
290
215
  default:
291
- s += `\\${esc}`;
216
+ parts.push(`\\${esc}`);
292
217
  }
293
218
  } else {
294
- s += this.advance();
219
+ parts.push(this.advance());
295
220
  }
296
221
  }
297
222
  if (this.pos >= this.src.length)
298
223
  throw new LexError("Unterminated string", l, c, this.filename, this.src);
299
224
  this.advance(); // closing "
300
- return s;
225
+ return parts.join("");
301
226
  }
302
227
 
303
228
  readRawString() {
304
229
  const l = this.line,
305
230
  c = this.col;
306
231
  this.advance(); // opening `
307
- let s = "";
308
- while (this.pos < this.src.length && this.peek() !== "`")
309
- s += this.advance();
232
+ const start = this.pos;
233
+ while (this.pos < this.src.length && this.peek() !== "`") this.advance();
310
234
  if (this.pos >= this.src.length)
311
235
  throw new LexError(
312
236
  "Unterminated raw string",
@@ -315,8 +239,9 @@ export class Lexer {
315
239
  this.filename,
316
240
  this.src,
317
241
  );
242
+ const raw = this.src.slice(start, this.pos);
318
243
  this.advance(); // closing `
319
- return s;
244
+ return raw;
320
245
  }
321
246
 
322
247
  readRuneLiteral() {
@@ -374,70 +299,83 @@ export class Lexer {
374
299
  }
375
300
 
376
301
  readNumber() {
377
- let n = "";
378
- let isFloat = false;
379
-
380
- // Check for prefix: 0b, 0o, 0x
381
302
  const first = this.advance();
382
- n += first;
303
+ const n = first;
383
304
  if (first === "0") {
384
305
  const p = this.peek();
385
- if (p === "b" || p === "B") {
386
- n += this.advance();
387
- while (this.pos < this.src.length && /[01_]/.test(this.peek())) {
388
- const ch = this.advance();
389
- if (ch !== "_") n += ch;
390
- }
391
- return { n, isFloat: false };
392
- }
393
- if (p === "o" || p === "O") {
394
- n += this.advance();
395
- while (this.pos < this.src.length && /[0-7_]/.test(this.peek())) {
396
- const ch = this.advance();
397
- if (ch !== "_") n += ch;
398
- }
399
- return { n, isFloat: false };
400
- }
401
- if (p === "x" || p === "X") {
402
- n += this.advance();
403
- while (this.pos < this.src.length && /[0-9a-fA-F_]/.test(this.peek())) {
404
- const ch = this.advance();
405
- if (ch !== "_") n += ch;
406
- }
407
- // Hex float: 0x1.Fp10 or 0xAp-2
408
- if (this.peek() === "." || this.peek() === "p" || this.peek() === "P") {
409
- let frac = "";
410
- if (this.peek() === ".") {
411
- this.advance(); // skip the dot (don't add to n)
412
- while (
413
- this.pos < this.src.length &&
414
- /[0-9a-fA-F_]/.test(this.peek())
415
- ) {
416
- const ch = this.advance();
417
- if (ch !== "_") frac += ch;
418
- }
419
- }
420
- if (this.peek() === "p" || this.peek() === "P") {
421
- this.advance(); // skip p/P
422
- let exp = "";
423
- if (this.peek() === "+" || this.peek() === "-")
424
- exp += this.advance();
425
- while (this.pos < this.src.length && /[0-9]/.test(this.peek()))
426
- exp += this.advance();
427
- // Evaluate: JS can't parse hex floats, so convert manually
428
- // n is "0x1" (integer hex part), frac is hex fractional digits
429
- let mantissa = Number(n);
430
- if (frac) {
431
- mantissa += Number.parseInt(frac, 16) / 16 ** frac.length;
432
- }
433
- const value = mantissa * 2 ** Number(exp);
434
- return { n: String(value), isFloat: true };
435
- }
436
- }
437
- return { n, isFloat: false };
438
- }
306
+ if (p === "b" || p === "B") return this._readBinaryNumber(n);
307
+ if (p === "o" || p === "O") return this._readOctalNumber(n);
308
+ if (p === "x" || p === "X") return this._readHexNumber(n);
309
+ }
310
+ return this._readDecimalNumber(n);
311
+ }
312
+
313
+ _readBinaryNumber(n) {
314
+ n += this.advance(); // consume b/B
315
+ while (this.pos < this.src.length && /[01_]/.test(this.peek())) {
316
+ const ch = this.advance();
317
+ if (ch !== "_") n += ch;
318
+ }
319
+ return { n, isFloat: false };
320
+ }
321
+
322
+ _readOctalNumber(n) {
323
+ n += this.advance(); // consume o/O
324
+ while (this.pos < this.src.length && /[0-7_]/.test(this.peek())) {
325
+ const ch = this.advance();
326
+ if (ch !== "_") n += ch;
439
327
  }
328
+ return { n, isFloat: false };
329
+ }
440
330
 
331
+ _readHexFrac() {
332
+ let frac = "";
333
+ this.advance(); // skip the dot
334
+ while (this.pos < this.src.length && /[0-9a-fA-F_]/.test(this.peek())) {
335
+ const ch = this.advance();
336
+ if (ch !== "_") frac += ch;
337
+ }
338
+ return frac;
339
+ }
340
+
341
+ _readHexFloatExp(n, frac) {
342
+ this.advance(); // skip p/P
343
+ let exp = "";
344
+ if (this.peek() === "+" || this.peek() === "-") exp += this.advance();
345
+ while (this.pos < this.src.length && /[0-9]/.test(this.peek()))
346
+ exp += this.advance();
347
+ let mantissa = Number(n);
348
+ if (frac) mantissa += Number.parseInt(frac, 16) / 16 ** frac.length;
349
+ return { n: String(mantissa * 2 ** Number(exp)), isFloat: true };
350
+ }
351
+
352
+ _readHexNumber(n) {
353
+ n += this.advance(); // consume x/X
354
+ while (this.pos < this.src.length && /[0-9a-fA-F_]/.test(this.peek())) {
355
+ const ch = this.advance();
356
+ if (ch !== "_") n += ch;
357
+ }
358
+ if (this.peek() === "." || this.peek() === "p" || this.peek() === "P") {
359
+ let frac = "";
360
+ if (this.peek() === ".") frac = this._readHexFrac();
361
+ if (this.peek() === "p" || this.peek() === "P")
362
+ return this._readHexFloatExp(n, frac);
363
+ }
364
+ return { n, isFloat: false };
365
+ }
366
+
367
+ _readDecimalExponent(n) {
368
+ n += this.advance(); // consume e/E
369
+ if (this.peek() === "+" || this.peek() === "-") n += this.advance();
370
+ while (this.pos < this.src.length && /[0-9_]/.test(this.peek())) {
371
+ const ch = this.advance();
372
+ if (ch !== "_") n += ch;
373
+ }
374
+ return n;
375
+ }
376
+
377
+ _readDecimalNumber(n) {
378
+ let isFloat = false;
441
379
  while (this.pos < this.src.length && /[0-9_]/.test(this.peek())) {
442
380
  const ch = this.advance();
443
381
  if (ch !== "_") n += ch;
@@ -452,12 +390,7 @@ export class Lexer {
452
390
  }
453
391
  if (this.peek() === "e" || this.peek() === "E") {
454
392
  isFloat = true;
455
- n += this.advance();
456
- if (this.peek() === "+" || this.peek() === "-") n += this.advance();
457
- while (this.pos < this.src.length && /[0-9_]/.test(this.peek())) {
458
- const ch = this.advance();
459
- if (ch !== "_") n += ch;
460
- }
393
+ n = this._readDecimalExponent(n);
461
394
  }
462
395
  return { n, isFloat };
463
396
  }
@@ -534,113 +467,94 @@ export class Lexer {
534
467
 
535
468
  // Operators & punctuation
536
469
  this.advance();
537
- switch (ch) {
538
- case "+":
539
- if (this.match("+")) this.push(T.INC, "++", l, c);
540
- else if (this.match("=")) this.push(T.PLUS_ASSIGN, "+=", l, c);
541
- else this.push(T.PLUS, "+", l, c);
542
- break;
543
- case "-":
544
- if (this.match("-")) this.push(T.DEC, "--", l, c);
545
- else if (this.match("=")) this.push(T.MINUS_ASSIGN, "-=", l, c);
546
- else this.push(T.MINUS, "-", l, c);
547
- break;
548
- case "*":
549
- if (this.match("=")) this.push(T.STAR_ASSIGN, "*=", l, c);
550
- else this.push(T.STAR, "*", l, c);
551
- break;
552
- case "/":
553
- if (this.match("=")) this.push(T.SLASH_ASSIGN, "/=", l, c);
554
- else this.push(T.SLASH, "/", l, c);
555
- break;
556
- case "%":
557
- if (this.match("=")) this.push(T.PERCENT_ASSIGN, "%=", l, c);
558
- else this.push(T.PERCENT, "%", l, c);
559
- break;
560
- case "=":
561
- if (this.match("=")) this.push(T.EQ, "==", l, c);
562
- else this.push(T.ASSIGN, "=", l, c);
563
- break;
564
- case "!":
565
- if (this.match("=")) this.push(T.NEQ, "!=", l, c);
566
- else this.push(T.NOT, "!", l, c);
567
- break;
568
- case "<":
569
- if (this.match("<")) {
570
- if (this.match("=")) this.push(T.LSHIFT_ASSIGN, "<<=", l, c);
571
- else this.push(T.LSHIFT, "<<", l, c);
572
- } else if (this.match("=")) this.push(T.LTE, "<=", l, c);
573
- else this.push(T.LT, "<", l, c);
574
- break;
575
- case ">":
576
- if (this.match(">")) {
577
- if (this.match("=")) this.push(T.RSHIFT_ASSIGN, ">>=", l, c);
578
- else this.push(T.RSHIFT, ">>", l, c);
579
- } else if (this.match("=")) this.push(T.GTE, ">=", l, c);
580
- else this.push(T.GT, ">", l, c);
581
- break;
582
- case "&":
583
- if (this.match("&")) this.push(T.AND, "&&", l, c);
584
- else if (this.match("^")) this.push(T.AND_NOT, "&^", l, c);
585
- else if (this.match("=")) this.push(T.AMP_ASSIGN, "&=", l, c);
586
- else this.push(T.AMP, "&", l, c);
587
- break;
588
- case "|":
589
- if (this.match("|")) this.push(T.OR, "||", l, c);
590
- else if (this.match("=")) this.push(T.PIPE_ASSIGN, "|=", l, c);
591
- else this.push(T.PIPE, "|", l, c);
592
- break;
593
- case "^":
594
- if (this.match("=")) this.push(T.CARET_ASSIGN, "^=", l, c);
595
- else this.push(T.CARET, "^", l, c);
596
- break;
597
- case "~":
598
- this.push(T.TILDE, "~", l, c);
599
- break;
600
- case ":":
601
- if (this.match("=")) this.push(T.DEFINE, ":=", l, c);
602
- else this.push(T.COLON, ":", l, c);
603
- break;
604
- case ".":
605
- if (this.peek() === "." && this.peek(1) === ".") {
606
- this.advance();
607
- this.advance();
608
- this.push(T.ELLIPSIS, "...", l, c);
609
- } else {
610
- this.push(T.DOT, ".", l, c);
611
- }
612
- break;
613
- case "(":
614
- this.push(T.LPAREN, "(", l, c);
615
- break;
616
- case ")":
617
- this.push(T.RPAREN, ")", l, c);
618
- break;
619
- case "{":
620
- this.push(T.LBRACE, "{", l, c);
621
- break;
622
- case "}":
623
- this.push(T.RBRACE, "}", l, c);
624
- break;
625
- case "[":
626
- this.push(T.LBRACKET, "[", l, c);
627
- break;
628
- case "]":
629
- this.push(T.RBRACKET, "]", l, c);
630
- break;
631
- case ",":
632
- this.push(T.COMMA, ",", l, c);
633
- break;
634
- case ";":
635
- this.push(T.SEMICOLON, ";", l, c);
636
- break;
637
- default:
638
- this.err(`Unexpected character: '${ch}'`);
639
- }
470
+ this._readOperator(ch, l, c);
640
471
  }
641
472
 
642
473
  if (this.shouldSemi()) this.push(T.SEMICOLON, ";", this.line, this.col);
643
474
  this.push(T.EOF, "", this.line, this.col);
644
475
  return this.tokens;
645
476
  }
477
+ _readPlusOp(l, c) {
478
+ if (this.match("+")) this.push(T.INC, "++", l, c);
479
+ else if (this.match("=")) this.push(T.PLUS_ASSIGN, "+=", l, c);
480
+ else this.push(T.PLUS, "+", l, c);
481
+ }
482
+
483
+ _readMinusOp(l, c) {
484
+ if (this.match("-")) this.push(T.DEC, "--", l, c);
485
+ else if (this.match("=")) this.push(T.MINUS_ASSIGN, "-=", l, c);
486
+ else this.push(T.MINUS, "-", l, c);
487
+ }
488
+
489
+ _readLessOp(l, c) {
490
+ if (this.match("<")) {
491
+ if (this.match("=")) this.push(T.LSHIFT_ASSIGN, "<<=", l, c);
492
+ else this.push(T.LSHIFT, "<<", l, c);
493
+ } else if (this.match("=")) this.push(T.LTE, "<=", l, c);
494
+ else this.push(T.LT, "<", l, c);
495
+ }
496
+
497
+ _readGreaterOp(l, c) {
498
+ if (this.match(">")) {
499
+ if (this.match("=")) this.push(T.RSHIFT_ASSIGN, ">>=", l, c);
500
+ else this.push(T.RSHIFT, ">>", l, c);
501
+ } else if (this.match("=")) this.push(T.GTE, ">=", l, c);
502
+ else this.push(T.GT, ">", l, c);
503
+ }
504
+
505
+ _readAmpOp(l, c) {
506
+ if (this.match("&")) this.push(T.AND, "&&", l, c);
507
+ else if (this.match("^")) this.push(T.AND_NOT, "&^", l, c);
508
+ else if (this.match("=")) this.push(T.AMP_ASSIGN, "&=", l, c);
509
+ else this.push(T.AMP, "&", l, c);
510
+ }
511
+
512
+ _readOperator(ch, l, c) {
513
+ const punct = PUNCT_TOKEN[ch];
514
+ if (punct) {
515
+ this.push(punct[0], punct[1], l, c);
516
+ return;
517
+ }
518
+
519
+ const assign = ASSIGN_OP[ch];
520
+ if (assign) {
521
+ if (this.match("=")) this.push(assign[0], assign[1], l, c);
522
+ else this.push(assign[2], assign[3], l, c);
523
+ return;
524
+ }
525
+
526
+ switch (ch) {
527
+ case "+":
528
+ this._readPlusOp(l, c);
529
+ break;
530
+ case "-":
531
+ this._readMinusOp(l, c);
532
+ break;
533
+ case "<":
534
+ this._readLessOp(l, c);
535
+ break;
536
+ case ">":
537
+ this._readGreaterOp(l, c);
538
+ break;
539
+ case "&":
540
+ this._readAmpOp(l, c);
541
+ break;
542
+ case "|":
543
+ if (this.match("|")) this.push(T.OR, "||", l, c);
544
+ else if (this.match("=")) this.push(T.PIPE_ASSIGN, "|=", l, c);
545
+ else this.push(T.PIPE, "|", l, c);
546
+ break;
547
+ case ".":
548
+ if (this.peek() === "." && this.peek(1) === ".") {
549
+ this.advance();
550
+ this.advance();
551
+ this.push(T.ELLIPSIS, "...", l, c);
552
+ } else {
553
+ this.push(T.DOT, ".", l, c);
554
+ }
555
+ break;
556
+ default:
557
+ this.err(`Unexpected character: '${ch}'`);
558
+ }
559
+ }
646
560
  }