gofront 0.0.1 → 0.0.2

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/src/codegen.js CHANGED
@@ -210,10 +210,6 @@ export class CodeGen {
210
210
 
211
211
  // ── Type declarations ────────────────────────────────────────
212
212
 
213
- genTypeDecl(decl) {
214
- this.genTypeDeclWithMethods(decl, []);
215
- }
216
-
217
213
  genTypeDeclWithMethods(decl, methodDecls) {
218
214
  if (decl.type.kind === "StructType") {
219
215
  this.genStruct(decl.name, decl.type, methodDecls);
@@ -339,22 +335,36 @@ export class CodeGen {
339
335
  this.namedReturnVars = prev;
340
336
  }
341
337
 
342
- // Emit a function body, wrapping in try/finally for defer if needed.
338
+ // Emit a function body, wrapping in try/catch/finally for defer if needed.
343
339
  _genBody(body) {
344
340
  if (!this._hasDefer(body)) {
345
- this.genBlock(body, true);
341
+ this.genBlock(body);
346
342
  return;
347
343
  }
348
344
  this.line("const __defers = [];");
345
+ this.line("let __panic = null;");
349
346
  this.line("try {");
350
- this.indented(() => this.genBlock(body, true));
347
+ this.indented(() => this.genBlock(body));
348
+ this.line("} catch (__err) {");
349
+ this.indented(() => this.line("__panic = __err;"));
351
350
  this.line("} finally {");
352
351
  this.indented(() => {
353
352
  this.line(
354
353
  "for (let __i = __defers.length - 1; __i >= 0; __i--) __defers[__i]();",
355
354
  );
355
+ this.line("if (__panic !== null) throw __panic;");
356
356
  });
357
357
  this.line("}");
358
+ // If a recover() cleared __panic, execution reaches here.
359
+ // Return named return vars so deferred mutations are visible to the caller.
360
+ if (this.namedReturnVars?.length > 0) {
361
+ const vars = this.namedReturnVars;
362
+ this.line(
363
+ vars.length === 1
364
+ ? `return ${vars[0]};`
365
+ : `return [${vars.join(", ")}];`,
366
+ );
367
+ }
358
368
  }
359
369
 
360
370
  // Recursively check if a block (or nested blocks) contains any DeferStmt.
@@ -410,7 +420,7 @@ export class CodeGen {
410
420
 
411
421
  // ── Statements ───────────────────────────────────────────────
412
422
 
413
- genBlock(block, _skipBraces = false) {
423
+ genBlock(block) {
414
424
  for (const stmt of block.stmts) this.genStmt(stmt);
415
425
  }
416
426
 
@@ -426,7 +436,7 @@ export class CodeGen {
426
436
  this.genConstDecl(stmt);
427
437
  break;
428
438
  case "TypeDecl":
429
- this.genTypeDecl(stmt);
439
+ this.genTypeDeclWithMethods(stmt, []);
430
440
  break;
431
441
 
432
442
  case "DefineStmt": {
@@ -550,12 +560,24 @@ export class CodeGen {
550
560
  this.genSwitch(stmt);
551
561
  break;
552
562
 
563
+ case "TypeSwitchStmt":
564
+ this.genTypeSwitch(stmt);
565
+ break;
566
+
553
567
  case "DeferStmt":
554
568
  this.line(`__defers.push(() => { ${this.genExpr(stmt.call)}; });`);
555
569
  break;
556
570
 
571
+ case "LabeledStmt":
572
+ this.line(`${stmt.label}:`);
573
+ this.genStmt(stmt.body);
574
+ break;
575
+
557
576
  case "BranchStmt":
558
- if (stmt.keyword !== "fallthrough") this.line(`${stmt.keyword};`);
577
+ if (stmt.keyword !== "fallthrough")
578
+ this.line(
579
+ stmt.label ? `${stmt.keyword} ${stmt.label};` : `${stmt.keyword};`,
580
+ );
559
581
  // fallthrough: omit — JS switch falls through naturally without a break
560
582
  break;
561
583
 
@@ -603,6 +625,15 @@ export class CodeGen {
603
625
  return;
604
626
  }
605
627
 
628
+ // for range N { } — no variable, just repeat N times
629
+ if (stmt.cond?.kind === "RangeExpr") {
630
+ const iteree = this.genExpr(stmt.cond.expr);
631
+ this.line(`for (let _$ = 0; _$ < ${iteree}; _$++) {`);
632
+ this.indented(() => this.genBlock(stmt.body));
633
+ this.line("}");
634
+ return;
635
+ }
636
+
606
637
  if (!stmt.init && !stmt.post) {
607
638
  if (!stmt.cond) {
608
639
  // infinite loop
@@ -639,6 +670,18 @@ export class CodeGen {
639
670
  const iterType = range.expr._type;
640
671
 
641
672
  let iterExpr;
673
+ if (
674
+ lhs.length <= 1 &&
675
+ iterType?.kind === "basic" &&
676
+ (iterType.name === "int" || iterType.name === "float64")
677
+ ) {
678
+ // integer range (Go 1.22): for i := range n → C-style for loop
679
+ const v = lhs[0] === "_" ? "_$" : lhs[0];
680
+ this.line(`for (let ${v} = 0; ${v} < ${iteree}; ${v}++) {`);
681
+ this.indented(() => this.genBlock(stmt.body));
682
+ this.line("}");
683
+ return;
684
+ }
642
685
  if (
643
686
  iterType?.kind === "map" ||
644
687
  (iterType?.kind === "named" && iterType.underlying?.kind === "map")
@@ -697,44 +740,82 @@ export class CodeGen {
697
740
  }
698
741
 
699
742
  genSwitch(stmt) {
743
+ const genBody = () => {
744
+ const tag = stmt.tag ? this.genExpr(stmt.tag) : "true";
745
+ this.line(`switch (${tag}) {`);
746
+ this.indented(() => {
747
+ for (const c of stmt.cases) {
748
+ if (c.list) {
749
+ for (const e of c.list) this.line(`case ${this.genExpr(e)}:`);
750
+ } else {
751
+ this.line("default:");
752
+ }
753
+ // Wrap each case body in {} so `let` declarations don't bleed
754
+ // across sibling cases (JS switch shares one block scope).
755
+ this.line("{");
756
+ this.indented(() => {
757
+ for (const s of c.stmts) this.genStmt(s);
758
+ // Go doesn't fall through by default — add break unless last stmt is return/break
759
+ const last = c.stmts[c.stmts.length - 1];
760
+ if (
761
+ !last ||
762
+ (last.kind !== "ReturnStmt" && last.kind !== "BranchStmt")
763
+ ) {
764
+ this.line("break;");
765
+ }
766
+ });
767
+ this.line("}");
768
+ }
769
+ });
770
+ this.line("}");
771
+ };
772
+
700
773
  if (stmt.init) {
701
774
  this.line("{");
702
- this.indent++;
703
- this.genStmt(stmt.init);
775
+ this.indented(() => {
776
+ this.genStmt(stmt.init);
777
+ genBody();
778
+ });
779
+ this.line("}");
780
+ } else {
781
+ genBody();
704
782
  }
783
+ }
705
784
 
706
- const tag = stmt.tag ? this.genExpr(stmt.tag) : "true";
707
- this.line(`switch (${tag}) {`);
785
+ genTypeSwitch(stmt) {
786
+ this.line("{");
708
787
  this.indented(() => {
788
+ const val = this.genExpr(stmt.expr);
789
+ this.line(`const __tsw = ${val};`);
790
+ if (stmt.assign) this.line(`let ${stmt.assign} = __tsw;`);
791
+
792
+ let first = true;
793
+ let hasDefault = false;
709
794
  for (const c of stmt.cases) {
710
- if (c.list) {
711
- for (const e of c.list) this.line(`case ${this.genExpr(e)}:`);
712
- } else {
713
- this.line("default:");
795
+ if (!c.types) {
796
+ // default case — emit last
797
+ hasDefault = c;
798
+ continue;
714
799
  }
715
- // Wrap each case body in {} so `let` declarations don't bleed
716
- // across sibling cases (JS switch shares one block scope).
717
- this.line("{");
800
+ const cond = c.types
801
+ .map((t) => this._typeCheckExpr(t, "__tsw"))
802
+ .join(" || ");
803
+ this.line(`${first ? "if" : "else if"} (${cond}) {`);
718
804
  this.indented(() => {
719
805
  for (const s of c.stmts) this.genStmt(s);
720
- // Go doesn't fall through by default — add break unless last stmt is return/break
721
- const last = c.stmts[c.stmts.length - 1];
722
- if (
723
- !last ||
724
- (last.kind !== "ReturnStmt" && last.kind !== "BranchStmt")
725
- ) {
726
- this.line("break;");
727
- }
806
+ });
807
+ this.line("}");
808
+ first = false;
809
+ }
810
+ if (hasDefault) {
811
+ this.line(first ? "{" : "else {");
812
+ this.indented(() => {
813
+ for (const s of hasDefault.stmts) this.genStmt(s);
728
814
  });
729
815
  this.line("}");
730
816
  }
731
817
  });
732
818
  this.line("}");
733
-
734
- if (stmt.init) {
735
- this.indent--;
736
- this.line("}");
737
- }
738
819
  }
739
820
 
740
821
  // ── Expressions ──────────────────────────────────────────────
@@ -766,7 +847,10 @@ export class CodeGen {
766
847
  ) {
767
848
  return `Math.trunc(${l} / ${r})`;
768
849
  }
769
- return `${l} ${expr.op} ${r}`;
850
+ // Go == and != are strict — map to JS === / !==
851
+ const op =
852
+ expr.op === "==" ? "===" : expr.op === "!=" ? "!==" : expr.op;
853
+ return `${l} ${op} ${r}`;
770
854
  }
771
855
 
772
856
  case "CallExpr":
@@ -818,7 +902,27 @@ export class CodeGen {
818
902
 
819
903
  case "TypeConversion": {
820
904
  const inner = this.genExpr(expr.expr);
821
- const target = expr.targetType?.name;
905
+ const t = expr.targetType;
906
+
907
+ // Slice type conversions
908
+ if (t?.kind === "SliceType") {
909
+ const elem = t.elem?.name;
910
+ // []byte(s) → UTF-8 byte array
911
+ if (elem === "byte" || elem === "uint8") {
912
+ return `Array.from(new TextEncoder().encode(${inner}))`;
913
+ }
914
+ // []rune(s) → Unicode code point array (only when source is a string)
915
+ if (elem === "rune" || elem === "int32" || elem === "int") {
916
+ const srcType = expr.expr._type;
917
+ if (srcType?.kind === "basic" && srcType?.name === "string") {
918
+ return `Array.from(${inner}, __c => __c.codePointAt(0))`;
919
+ }
920
+ return `Array.from(${inner})`;
921
+ }
922
+ return `Array.from(${inner})`;
923
+ }
924
+
925
+ const target = t?.name;
822
926
  switch (target) {
823
927
  case "string":
824
928
  return `String(${inner})`;
@@ -893,11 +997,33 @@ export class CodeGen {
893
997
  const arg = this.genExpr(expr.args[0]);
894
998
  return `(() => { throw new Error(${arg}); })()`;
895
999
  }
1000
+ case "recover":
1001
+ return `(typeof __panic !== "undefined" && __panic !== null ? (() => { const __r = __panic.message ?? String(__panic); __panic = null; return __r; })() : null)`;
896
1002
  case "error": {
897
1003
  // errors are plain strings; nil (null) means no error
898
1004
  const arg = this.genExpr(expr.args[0]);
899
1005
  return arg;
900
1006
  }
1007
+ case "min": {
1008
+ const args = expr.args.map((a) => this.genExpr(a)).join(", ");
1009
+ return `Math.min(${args})`;
1010
+ }
1011
+ case "max": {
1012
+ const args = expr.args.map((a) => this.genExpr(a)).join(", ");
1013
+ return `Math.max(${args})`;
1014
+ }
1015
+ case "clear": {
1016
+ const arg = expr.args[0];
1017
+ const t = arg?._type;
1018
+ const js = this.genExpr(arg);
1019
+ if (
1020
+ t?.kind === "map" ||
1021
+ (t?.kind === "named" && t.underlying?.kind === "map")
1022
+ ) {
1023
+ return `((__m) => { for (const __k in __m) delete __m[__k]; })(${js})`;
1024
+ }
1025
+ return `(${js}).length = 0`;
1026
+ }
901
1027
  }
902
1028
  }
903
1029
 
@@ -936,14 +1062,20 @@ export class CodeGen {
936
1062
  }
937
1063
  }
938
1064
 
939
- const fn = this.genExpr(expr.func);
940
- const args = expr.args.map((a) => this.genExpr(a)).join(", ");
1065
+ const rawFn = this.genExpr(expr.func);
1066
+ // Wrap function literals in parens so `function(){}()` → `(function(){})()`
1067
+ const fn = expr.func.kind === "FuncLit" ? `(${rawFn})` : rawFn;
1068
+ const args = expr.args
1069
+ .map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genExpr(a)))
1070
+ .join(", ");
941
1071
  return `${fn}(${args})`;
942
1072
  }
943
1073
 
944
1074
  genAppend(expr) {
945
1075
  const slice = this.genExpr(expr.args[0]);
946
- const elems = expr.args.slice(1).map((a) => this.genExpr(a));
1076
+ const elems = expr.args
1077
+ .slice(1)
1078
+ .map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genExpr(a)));
947
1079
  if (elems.length === 0) return slice;
948
1080
  this._usesAppend = true;
949
1081
  return `__append(${slice}, ${elems.join(", ")})`;
@@ -973,10 +1105,7 @@ export class CodeGen {
973
1105
  }
974
1106
  return `new Array(${n}).fill(${zero})`;
975
1107
  }
976
- if (typeNode.kind === "MapType" || resolvedKind === "map") {
977
- return "{}";
978
- }
979
- // Fallback
1108
+ // map or fallback
980
1109
  return "{}";
981
1110
  }
982
1111
 
@@ -1079,6 +1208,17 @@ export class CodeGen {
1079
1208
  _zeroForBasicName(name) {
1080
1209
  switch (name) {
1081
1210
  case "int":
1211
+ case "uint":
1212
+ case "int8":
1213
+ case "int16":
1214
+ case "int32":
1215
+ case "int64":
1216
+ case "uint8":
1217
+ case "uint16":
1218
+ case "uint32":
1219
+ case "uint64":
1220
+ case "uintptr":
1221
+ case "float32":
1082
1222
  case "float64":
1083
1223
  case "byte":
1084
1224
  case "rune":
@@ -1141,6 +1281,17 @@ export class CodeGen {
1141
1281
  if (typeNode.kind === "TypeName") {
1142
1282
  switch (typeNode.name) {
1143
1283
  case "int":
1284
+ case "uint":
1285
+ case "int8":
1286
+ case "int16":
1287
+ case "int32":
1288
+ case "int64":
1289
+ case "uint8":
1290
+ case "uint16":
1291
+ case "uint32":
1292
+ case "uint64":
1293
+ case "uintptr":
1294
+ case "float32":
1144
1295
  case "float64":
1145
1296
  case "byte":
1146
1297
  case "rune":
@@ -1149,6 +1300,10 @@ export class CodeGen {
1149
1300
  return `typeof ${val} === "string"`;
1150
1301
  case "bool":
1151
1302
  return `typeof ${val} === "boolean"`;
1303
+ case "nil":
1304
+ return `${val} === null`;
1305
+ case "error":
1306
+ return `typeof ${val} === "string"`;
1152
1307
  default:
1153
1308
  if (this.structNames.has(typeNode.name))
1154
1309
  return `${val} instanceof ${typeNode.name}`;
package/src/compiler.js CHANGED
@@ -86,8 +86,9 @@ export function compileFiles(files, options = {}) {
86
86
 
87
87
  // js: prefix — local .d.ts files
88
88
  for (const imp of allImports) {
89
- for (const path of imp.paths) {
89
+ for (const { path, alias } of imp.imports) {
90
90
  if (!path.startsWith("js:")) continue;
91
+ if (alias === "_") continue; // side-effect import — no type definitions
91
92
  const dtsPath = join(fromDir, path.slice(3));
92
93
  try {
93
94
  const { types, values } = parseDts(readFileSync(dtsPath, "utf8"));
@@ -98,8 +99,12 @@ export function compileFiles(files, options = {}) {
98
99
  }
99
100
  }
100
101
 
101
- // npm packages
102
- const resolved = resolveAll(allImports, dummyFromFile, parseDts);
102
+ // npm packages — exclude side-effect imports (alias "_") from type resolution
103
+ const allImportsNoSideEffect = allImports.map((imp) => ({
104
+ ...imp,
105
+ imports: imp.imports.filter(({ alias }) => alias !== "_"),
106
+ }));
107
+ const resolved = resolveAll(allImportsNoSideEffect, dummyFromFile, parseDts);
103
108
  for (const [path, info] of resolved) {
104
109
  if (!info) continue;
105
110
  checker.addDefinitions(info.types, info.values);
@@ -109,7 +114,7 @@ export function compileFiles(files, options = {}) {
109
114
  // local GoFront packages (./subdir)
110
115
  const seenLocalPaths = new Set();
111
116
  for (const imp of allImports) {
112
- for (const path of imp.paths) {
117
+ for (const { path, alias } of imp.imports) {
113
118
  if (!isLocalPath(path) || seenLocalPaths.has(path)) continue;
114
119
  seenLocalPaths.add(path);
115
120
 
@@ -124,9 +129,11 @@ export function compileFiles(files, options = {}) {
124
129
  // Recursively compile dependency
125
130
  const dep = compileDir(depDir);
126
131
  preambles.push(dep.js);
127
- bundledPackages.add(dep.pkgName);
132
+ if (alias === "_") continue; // side-effect import — bundle code but don't expose namespace
133
+ const nameUsed = alias ?? dep.pkgName;
134
+ bundledPackages.add(nameUsed);
128
135
  checker.addPackageNamespace(
129
- dep.pkgName,
136
+ nameUsed,
130
137
  dep.exportedSymbols,
131
138
  dep.exportedTypes,
132
139
  );
package/src/dts-parser.js CHANGED
@@ -404,13 +404,11 @@ export class DtsParser {
404
404
  if (this.eof() || this.ch() === "}") return;
405
405
 
406
406
  // Skip modifiers (export, declare, static, readonly, etc.)
407
- let _exported = false;
408
407
  while (true) {
409
408
  this.skip();
410
409
  let found = false;
411
410
  for (const mod of SKIP_MODIFIERS) {
412
411
  if (this.matchKw(mod)) {
413
- if (mod === "export") _exported = true;
414
412
  found = true;
415
413
  break;
416
414
  }
@@ -604,13 +602,11 @@ export class DtsParser {
604
602
  if (this.eof() || this.ch() === "}") break;
605
603
 
606
604
  // Collect modifiers
607
- let _isExport = false;
608
605
  while (true) {
609
606
  this.skip();
610
607
  let found = false;
611
608
  for (const mod of SKIP_MODIFIERS) {
612
609
  if (this.matchKw(mod)) {
613
- if (mod === "export") _isExport = true;
614
610
  found = true;
615
611
  break;
616
612
  }
package/src/index.js CHANGED
@@ -148,16 +148,14 @@ function runCompile() {
148
148
  throw new Error(`cannot read '${inputArg}': ${e.message}`);
149
149
  }
150
150
 
151
- let tokens;
152
- tokens = new Lexer(source, basename(inputPath)).tokenize();
151
+ const tokens = new Lexer(source, basename(inputPath)).tokenize();
153
152
 
154
153
  if (dumpTokens) {
155
154
  for (const tok of tokens) console.log(tok.toString());
156
155
  process.exit(0);
157
156
  }
158
157
 
159
- let ast;
160
- ast = new Parser(tokens, basename(inputPath), source).parse();
158
+ const ast = new Parser(tokens, basename(inputPath), source).parse();
161
159
  ast._source = source;
162
160
 
163
161
  if (dumpAst) {
@@ -170,7 +168,7 @@ function runCompile() {
170
168
  const jsImports = new Map();
171
169
 
172
170
  for (const imp of ast.imports) {
173
- for (const path of imp.paths) {
171
+ for (const { path } of imp.imports) {
174
172
  if (!path.startsWith("js:")) continue;
175
173
  const dtsPath = join(fromDir, path.slice(3));
176
174
  try {
@@ -193,7 +191,7 @@ function runCompile() {
193
191
  const preambles = [];
194
192
 
195
193
  for (const imp of ast.imports) {
196
- for (const path of imp.paths) {
194
+ for (const { path, alias } of imp.imports) {
197
195
  if (!isLocalPath(path)) continue;
198
196
  const depDir = resolveGwDir(path, inputPath);
199
197
  if (!depDir) {
@@ -204,9 +202,10 @@ function runCompile() {
204
202
  }
205
203
  const dep = compileDir(depDir);
206
204
  preambles.push(dep.js);
207
- bundledPackages.add(dep.pkgName);
205
+ const nameUsed = alias ?? dep.pkgName;
206
+ bundledPackages.add(nameUsed);
208
207
  checker.addPackageNamespace(
209
- dep.pkgName,
208
+ nameUsed,
210
209
  dep.exportedSymbols,
211
210
  dep.exportedTypes,
212
211
  );
@@ -228,15 +227,7 @@ function runCompile() {
228
227
  }
229
228
 
230
229
  const output = preambles.length > 0 ? `${preambles.join("\n")}\n${js}` : js;
231
- return { js: output, _cg: cg };
232
- }
233
-
234
- function _writeOutput(js) {
235
- if (outputFile) {
236
- writeFileSync(outputFile, `${js}\n`);
237
- } else {
238
- console.log(js);
239
- }
230
+ return { js: output };
240
231
  }
241
232
 
242
233
  // ── Minify helper ────────────────────────────────────────────
package/src/lexer.js CHANGED
@@ -58,6 +58,11 @@ export const T = {
58
58
  STAR_ASSIGN: "*=",
59
59
  SLASH_ASSIGN: "/=",
60
60
  PERCENT_ASSIGN: "%=",
61
+ AMP_ASSIGN: "&=",
62
+ PIPE_ASSIGN: "|=",
63
+ CARET_ASSIGN: "^=",
64
+ LSHIFT_ASSIGN: "<<=",
65
+ RSHIFT_ASSIGN: ">>=",
61
66
  INC: "++",
62
67
  DEC: "--",
63
68
  AMP: "&",
@@ -267,7 +272,7 @@ export class Lexer {
267
272
  }
268
273
  }
269
274
  if (this.pos >= this.src.length)
270
- throw new LexError("Unterminated string", l, c, this.filename);
275
+ throw new LexError("Unterminated string", l, c, this.filename, this.src);
271
276
  this.advance(); // closing "
272
277
  return s;
273
278
  }
@@ -281,6 +286,60 @@ export class Lexer {
281
286
  return s;
282
287
  }
283
288
 
289
+ readRuneLiteral() {
290
+ const l = this.line,
291
+ c = this.col;
292
+ this.advance(); // opening '
293
+ let code;
294
+ if (this.peek() === "\\") {
295
+ this.advance();
296
+ const esc = this.advance();
297
+ switch (esc) {
298
+ case "n":
299
+ code = 10;
300
+ break;
301
+ case "t":
302
+ code = 9;
303
+ break;
304
+ case "r":
305
+ code = 13;
306
+ break;
307
+ case "'":
308
+ code = 39;
309
+ break;
310
+ case "\\":
311
+ code = 92;
312
+ break;
313
+ case "0":
314
+ code = 0;
315
+ break;
316
+ default:
317
+ throw new LexError(
318
+ `Unknown escape in rune literal: \\${esc}`,
319
+ l,
320
+ c,
321
+ this.filename,
322
+ this.src,
323
+ );
324
+ }
325
+ } else if (this.peek() !== "'") {
326
+ code = this.src.codePointAt(this.pos);
327
+ this.advance();
328
+ } else {
329
+ throw new LexError("Empty rune literal", l, c, this.filename, this.src);
330
+ }
331
+ if (this.peek() !== "'")
332
+ throw new LexError(
333
+ "Rune literal must contain exactly one character",
334
+ l,
335
+ c,
336
+ this.filename,
337
+ this.src,
338
+ );
339
+ this.advance(); // closing '
340
+ return String(code);
341
+ }
342
+
284
343
  readNumber() {
285
344
  let n = "";
286
345
  let isFloat = false;
@@ -347,6 +406,10 @@ export class Lexer {
347
406
  this.push(T.STRING, this.readRawString(), l, c);
348
407
  continue;
349
408
  }
409
+ if (ch === "'") {
410
+ this.push(T.INT, this.readRuneLiteral(), l, c);
411
+ continue;
412
+ }
350
413
 
351
414
  // Numbers
352
415
  if (/[0-9]/.test(ch)) {
@@ -396,25 +459,32 @@ export class Lexer {
396
459
  else this.push(T.NOT, "!", l, c);
397
460
  break;
398
461
  case "<":
399
- if (this.match("<")) this.push(T.LSHIFT, "<<", l, c);
400
- else if (this.match("=")) this.push(T.LTE, "<=", l, c);
462
+ if (this.match("<")) {
463
+ if (this.match("=")) this.push(T.LSHIFT_ASSIGN, "<<=", l, c);
464
+ else this.push(T.LSHIFT, "<<", l, c);
465
+ } else if (this.match("=")) this.push(T.LTE, "<=", l, c);
401
466
  else this.push(T.LT, "<", l, c);
402
467
  break;
403
468
  case ">":
404
- if (this.match(">")) this.push(T.RSHIFT, ">>", l, c);
405
- else if (this.match("=")) this.push(T.GTE, ">=", l, c);
469
+ if (this.match(">")) {
470
+ if (this.match("=")) this.push(T.RSHIFT_ASSIGN, ">>=", l, c);
471
+ else this.push(T.RSHIFT, ">>", l, c);
472
+ } else if (this.match("=")) this.push(T.GTE, ">=", l, c);
406
473
  else this.push(T.GT, ">", l, c);
407
474
  break;
408
475
  case "&":
409
476
  if (this.match("&")) this.push(T.AND, "&&", l, c);
477
+ else if (this.match("=")) this.push(T.AMP_ASSIGN, "&=", l, c);
410
478
  else this.push(T.AMP, "&", l, c);
411
479
  break;
412
480
  case "|":
413
481
  if (this.match("|")) this.push(T.OR, "||", l, c);
482
+ else if (this.match("=")) this.push(T.PIPE_ASSIGN, "|=", l, c);
414
483
  else this.push(T.PIPE, "|", l, c);
415
484
  break;
416
485
  case "^":
417
- this.push(T.CARET, "^", l, c);
486
+ if (this.match("=")) this.push(T.CARET_ASSIGN, "^=", l, c);
487
+ else this.push(T.CARET, "^", l, c);
418
488
  break;
419
489
  case ":":
420
490
  if (this.match("=")) this.push(T.DEFINE, ":=", l, c);