gofront 0.0.9 → 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 (50) hide show
  1. package/README.md +6 -6
  2. package/package.json +7 -4
  3. package/src/codegen/expressions.js +520 -1717
  4. package/src/codegen/index.js +576 -0
  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 +80 -74
  30. package/src/dts-parser.js +355 -371
  31. package/src/index.js +13 -49
  32. package/src/lexer.js +186 -272
  33. package/src/minifier.js +305 -318
  34. package/src/parser/expressions.js +51 -34
  35. package/src/{parser.js → parser/index.js} +4 -4
  36. package/src/parser/statements.js +24 -34
  37. package/src/parser/types.js +31 -29
  38. package/src/templ-parser.js +2 -2
  39. package/src/tokens.js +109 -0
  40. package/src/typechecker/assignability.js +258 -0
  41. package/src/typechecker/expressions.js +559 -549
  42. package/src/typechecker/index.js +555 -0
  43. package/src/typechecker/resolve.js +421 -0
  44. package/src/typechecker/statements.js +267 -280
  45. package/src/typechecker/stdlib/core.js +592 -0
  46. package/src/typechecker/stdlib/extended.js +332 -0
  47. package/src/typechecker/stdlib.js +9 -1060
  48. package/src/typechecker/termination.js +83 -0
  49. package/src/codegen.js +0 -882
  50. package/src/typechecker.js +0 -1299
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
  }
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) {
@@ -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;
327
+ }
328
+ return { n, isFloat: false };
329
+ }
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;
439
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
+ }
440
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
  }