gofront 0.0.4 → 0.0.5

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.
@@ -1,5 +1,7 @@
1
1
  // CodeGen statement methods — installed as a mixin on CodeGen.prototype.
2
2
 
3
+ import { isComplex } from "../typechecker/types.js";
4
+
3
5
  export const statementGenMethods = {
4
6
  genBlock(block) {
5
7
  for (const stmt of block.stmts) this.genStmt(stmt);
@@ -45,10 +47,16 @@ export const statementGenMethods = {
45
47
  const redecls = stmt.lhs.map((e) => !!e._redecl);
46
48
  const anyRedecl = redecls.some((r) => r);
47
49
  if (lhs.length === 1) {
50
+ const isBoxed = this._boxedVars.has(lhs[0]);
51
+ const val = isBoxed && !redecls[0] ? `{ value: ${rhs[0]} }` : rhs[0];
48
52
  if (redecls[0]) {
49
- this.line(`${lhs[0]} = ${rhs[0]};`);
53
+ if (isBoxed) {
54
+ this.line(`${lhs[0]}.value = ${rhs[0]};`);
55
+ } else {
56
+ this.line(`${lhs[0]} = ${val};`);
57
+ }
50
58
  } else {
51
- this.line(`let ${lhs[0]} = ${rhs[0]};`);
59
+ this.line(`let ${lhs[0]} = ${val};`);
52
60
  }
53
61
  } else if (!anyRedecl) {
54
62
  // All new: let [a, b] = ...
@@ -75,6 +83,38 @@ export const statementGenMethods = {
75
83
  }
76
84
 
77
85
  case "AssignStmt": {
86
+ // Complex compound assignment: z += w → z = z + w (with complex codegen)
87
+ if (
88
+ stmt.op !== "=" &&
89
+ stmt.lhs.length === 1 &&
90
+ stmt.rhs.length === 1 &&
91
+ isComplex(stmt.lhs[0]._type)
92
+ ) {
93
+ const lhsStr = stmt.lhs[0].name ?? this.genExpr(stmt.lhs[0]);
94
+ const rhsExpr = this._genComplexOperand(stmt.rhs[0]);
95
+ const baseOp = stmt.op.slice(0, -1); // "+=" → "+"
96
+ let result;
97
+ switch (baseOp) {
98
+ case "+":
99
+ result = `{ re: ${lhsStr}.re + ${rhsExpr}.re, im: ${lhsStr}.im + ${rhsExpr}.im }`;
100
+ break;
101
+ case "-":
102
+ result = `{ re: ${lhsStr}.re - ${rhsExpr}.re, im: ${lhsStr}.im - ${rhsExpr}.im }`;
103
+ break;
104
+ case "*":
105
+ this._usesCmul = true;
106
+ result = `__cmul(${lhsStr}, ${rhsExpr})`;
107
+ break;
108
+ case "/":
109
+ this._usesCdiv = true;
110
+ result = `__cdiv(${lhsStr}, ${rhsExpr})`;
111
+ break;
112
+ default:
113
+ result = rhsExpr;
114
+ }
115
+ this.line(`${lhsStr} = ${result};`);
116
+ break;
117
+ }
78
118
  // comma-ok map index: v, ok = m["key"]
79
119
  if (stmt._commaOkMap) {
80
120
  const rhsNode = stmt.rhs[0];
@@ -113,7 +153,15 @@ export const statementGenMethods = {
113
153
  }
114
154
  const rhs = stmt.rhs.map((e) => this.genExpr(e));
115
155
  for (const e of stmt.lhs) if (e.kind === "IndexExpr") e._lvalue = true;
116
- const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
156
+ const lhs = stmt.lhs.map((e) => {
157
+ if (
158
+ e.kind === "Ident" &&
159
+ e.name !== "_" &&
160
+ this._boxedVars.has(e.name)
161
+ )
162
+ return `${e.name}.value`;
163
+ return e.name ?? this.genExpr(e);
164
+ });
117
165
  // Filter out blank identifier assignments entirely
118
166
  const pairs = lhs.map((l, i) => ({ l, r: rhs[i] ?? rhs[0] }));
119
167
  const active = pairs.filter((p) => p.l !== "_");
@@ -151,6 +199,21 @@ export const statementGenMethods = {
151
199
  break;
152
200
 
153
201
  case "ReturnStmt": {
202
+ if (this._inIteratorBody) {
203
+ if (stmt.values.length === 0 && !this.namedReturnVars?.length) {
204
+ this.line(`${this._iterReturnFlag} = true; return false;`);
205
+ } else {
206
+ const vals =
207
+ stmt.values.length > 0
208
+ ? stmt.values.map((v) => this.genExpr(v))
209
+ : (this.namedReturnVars ?? []);
210
+ const stored = vals.length === 1 ? vals[0] : `[${vals.join(", ")}]`;
211
+ this.line(
212
+ `${this._iterReturnFlag} = true; ${this._iterReturnVar} = ${stored}; return false;`,
213
+ );
214
+ }
215
+ break;
216
+ }
154
217
  if (stmt.values.length === 0) {
155
218
  // bare return — emit named return vars if present
156
219
  if (this.namedReturnVars?.length > 0) {
@@ -210,6 +273,16 @@ export const statementGenMethods = {
210
273
  break;
211
274
 
212
275
  case "BranchStmt":
276
+ if (this._inIteratorBody && !stmt.label) {
277
+ if (stmt.keyword === "break") {
278
+ this.line(`${this._iterBreakFlag} = true; return false;`);
279
+ break;
280
+ }
281
+ if (stmt.keyword === "continue") {
282
+ this.line("return true;");
283
+ break;
284
+ }
285
+ }
213
286
  if (stmt.keyword !== "fallthrough")
214
287
  this.line(
215
288
  stmt.label ? `${stmt.keyword} ${stmt.label};` : `${stmt.keyword};`,
@@ -255,14 +328,22 @@ export const statementGenMethods = {
255
328
  },
256
329
 
257
330
  genFor(stmt) {
258
- // Detect range: init is DefineStmt with rhs[0] being a RangeExpr
331
+ // Detect range: init is DefineStmt/AssignStmt with rhs[0] being a RangeExpr
259
332
  if (this.isRangeFor(stmt)) {
260
- this.genRangeFor(stmt);
333
+ if (stmt.init.rhs[0]._isIterator) {
334
+ this.genIteratorFor(stmt);
335
+ } else {
336
+ this.genRangeFor(stmt);
337
+ }
261
338
  return;
262
339
  }
263
340
 
264
- // for range N { } — no variable, just repeat N times
341
+ // for range expr — no variable; could be int range or 0-param iterator
265
342
  if (stmt.cond?.kind === "RangeExpr") {
343
+ if (stmt.cond._isIterator) {
344
+ this.genIteratorForCond(stmt);
345
+ return;
346
+ }
266
347
  const iteree = this.genExpr(stmt.cond.expr);
267
348
  this.line(`for (let _$ = 0; _$ < ${iteree}; _$++) {`);
268
349
  this.indented(() => this.genBlock(stmt.body));
@@ -361,6 +442,112 @@ export const statementGenMethods = {
361
442
  this.line("}");
362
443
  },
363
444
 
445
+ genIteratorFor(stmt) {
446
+ const range = stmt.init.rhs[0];
447
+ const lhs = stmt.init.lhs.map((e) => e.name ?? this.genExpr(e));
448
+ let iteree = this.genExpr(range.expr);
449
+ const yieldParams = range._yieldParams;
450
+
451
+ // Wrap bare function expressions to avoid "Function statements require a name"
452
+ if (range.expr.kind === "FuncLit") {
453
+ iteree = `(${iteree})`;
454
+ }
455
+
456
+ const d = this._iterDepth++;
457
+ const breakFlag = `__broke${d}`;
458
+ const retFlag = `__returned${d}`;
459
+ const retVar = `__retVal${d}`;
460
+
461
+ // Yield callback param names (blank vars get _$N to avoid JS syntax error)
462
+ const cbParams = lhs.map((n, i) => (n === "_" ? `_$${i}` : n));
463
+
464
+ this.line("{");
465
+ this.indented(() => {
466
+ this.line(`let ${breakFlag} = false;`);
467
+ this.line(`let ${retFlag} = false;`);
468
+ this.line(`let ${retVar};`);
469
+
470
+ const params = yieldParams.length === 0 ? "" : cbParams.join(", ");
471
+ this.line(`${iteree}(function(${params}) {`);
472
+ this.indented(() => {
473
+ this.line(`if (${breakFlag}) return false;`);
474
+ const prev = {
475
+ in: this._inIteratorBody,
476
+ break: this._iterBreakFlag,
477
+ ret: this._iterReturnFlag,
478
+ retVar: this._iterReturnVar,
479
+ };
480
+ this._inIteratorBody = true;
481
+ this._iterBreakFlag = breakFlag;
482
+ this._iterReturnFlag = retFlag;
483
+ this._iterReturnVar = retVar;
484
+
485
+ this.genBlock(stmt.body);
486
+
487
+ this._inIteratorBody = prev.in;
488
+ this._iterBreakFlag = prev.break;
489
+ this._iterReturnFlag = prev.ret;
490
+ this._iterReturnVar = prev.retVar;
491
+
492
+ this.line("return true;");
493
+ });
494
+ this.line("});");
495
+ this.line(`if (${retFlag}) return ${retVar};`);
496
+ });
497
+ this.line("}");
498
+ this._iterDepth--;
499
+ },
500
+
501
+ // 0-param iterator: for range iter { body }
502
+ genIteratorForCond(stmt) {
503
+ const range = stmt.cond;
504
+ let iteree = this.genExpr(range.expr);
505
+
506
+ if (range.expr.kind === "FuncLit") {
507
+ iteree = `(${iteree})`;
508
+ }
509
+
510
+ const d = this._iterDepth++;
511
+ const breakFlag = `__broke${d}`;
512
+ const retFlag = `__returned${d}`;
513
+ const retVar = `__retVal${d}`;
514
+
515
+ this.line("{");
516
+ this.indented(() => {
517
+ this.line(`let ${breakFlag} = false;`);
518
+ this.line(`let ${retFlag} = false;`);
519
+ this.line(`let ${retVar};`);
520
+
521
+ this.line(`${iteree}(function() {`);
522
+ this.indented(() => {
523
+ this.line(`if (${breakFlag}) return false;`);
524
+ const prev = {
525
+ in: this._inIteratorBody,
526
+ break: this._iterBreakFlag,
527
+ ret: this._iterReturnFlag,
528
+ retVar: this._iterReturnVar,
529
+ };
530
+ this._inIteratorBody = true;
531
+ this._iterBreakFlag = breakFlag;
532
+ this._iterReturnFlag = retFlag;
533
+ this._iterReturnVar = retVar;
534
+
535
+ this.genBlock(stmt.body);
536
+
537
+ this._inIteratorBody = prev.in;
538
+ this._iterBreakFlag = prev.break;
539
+ this._iterReturnFlag = prev.ret;
540
+ this._iterReturnVar = prev.retVar;
541
+
542
+ this.line("return true;");
543
+ });
544
+ this.line("});");
545
+ this.line(`if (${retFlag}) return ${retVar};`);
546
+ });
547
+ this.line("}");
548
+ this._iterDepth--;
549
+ },
550
+
364
551
  // Inline a statement as a for-init/post string (no semicolon)
365
552
  stmtInline(stmt) {
366
553
  switch (stmt.kind) {
package/src/codegen.js CHANGED
@@ -20,6 +20,7 @@
20
20
  import { expressionGenMethods } from "./codegen/expressions.js";
21
21
  import { buildSourceMap } from "./codegen/source-map.js";
22
22
  import { statementGenMethods } from "./codegen/statements.js";
23
+ import { isComplex, isNumeric } from "./typechecker/types.js";
23
24
 
24
25
  export class CodeGen {
25
26
  // jsImports: Map<importPath, string[]> — npm package imports to emit at top of file
@@ -38,12 +39,23 @@ export class CodeGen {
38
39
  this.bundledPackages = bundledPackages;
39
40
  this.namedReturnVars = null; // names of current function's named return vars
40
41
  this._srcMappings = []; // { genLine, srcLine } for source map
42
+ this._boxedVars = new Set(); // address-taken scalar variables that need boxing
41
43
  // Runtime helper usage tracking — only emit helpers that are actually used
42
44
  this._usesLen = false;
43
45
  this._usesAppend = false;
44
46
  this._usesSliceGuard = false;
45
47
  this._usesSprintf = false;
46
48
  this._usesEqual = false;
49
+ this._usesCmul = false;
50
+ this._usesCdiv = false;
51
+ this._usesError = false;
52
+ this._usesErrorIs = false;
53
+ // Iterator (range-over-func) context
54
+ this._inIteratorBody = false;
55
+ this._iterDepth = 0;
56
+ this._iterBreakFlag = null;
57
+ this._iterReturnFlag = null;
58
+ this._iterReturnVar = null;
47
59
  }
48
60
 
49
61
  // ── Output helpers ───────────────────────────────────────────
@@ -151,10 +163,18 @@ export class CodeGen {
151
163
  helpers.push(
152
164
  'function __equal(a,b){if(a===b)return true;if(a===null||b===null)return false;if(Array.isArray(a)&&Array.isArray(b)){if(a.length!==b.length)return false;for(let i=0;i<a.length;i++)if(!__equal(a[i],b[i]))return false;return true;}if(typeof a==="object"&&typeof b==="object"){const ka=Object.keys(a),kb=Object.keys(b);if(ka.length!==kb.length)return false;for(const k of ka)if(!__equal(a[k],b[k]))return false;return true;}return false;}',
153
165
  );
166
+ if (this._usesCmul)
167
+ helpers.push(
168
+ "function __cmul(a, b) { return { re: a.re * b.re - a.im * b.im, im: a.re * b.im + a.im * b.re }; }",
169
+ );
170
+ if (this._usesCdiv)
171
+ helpers.push(
172
+ "function __cdiv(a, b) { const d = b.re * b.re + b.im * b.im; return { re: (a.re * b.re + a.im * b.im) / d, im: (a.im * b.re - a.re * b.im) / d }; }",
173
+ );
154
174
  if (this._usesSprintf)
155
175
  helpers.push(
156
176
  [
157
- "function __sprintf(f,...a){let i=0;return f.replace(/%([#+\\- 0]*)([0-9]*)\\.?([0-9]*)[sdvftxXqobeEgG%]/g,(m)=>{",
177
+ "function __sprintf(f,...a){let i=0;return f.replace(/%([#+\\- 0]*)([0-9]*)\\.?([0-9]*)[sdvftxXqobeEgGw%]/g,(m)=>{",
158
178
  "if(m==='%%')return'%';const fl=m.slice(1,-1),verb=m.slice(-1),v=a[i++];",
159
179
  "const pad=(s,w,z)=>{w=parseInt(w)||0;if(!w)return s;const p=(z?'0':' ').repeat(Math.max(0,w-s.length));return fl.includes('-')?s+p:p+s;};",
160
180
  "const [,flags,width,prec]=m.match(/^%([#+\\- 0]*)([0-9]*)\\.?([0-9]*)/)||[];",
@@ -162,7 +182,7 @@ export class CodeGen {
162
182
  "switch(verb){",
163
183
  "case's':return pad(String(v==null?'<nil>':v),width,false);",
164
184
  "case'd':return pad(String(Math.trunc(Number(v))),width,zero);",
165
- "case'v':return pad(String(v==null?'<nil>':v),width,false);",
185
+ "case'v':{if(typeof v==='object'&&v!==null&&'re' in v&&'im' in v){const sign=v.im>=0?'+':'';return pad('('+v.re+sign+v.im+'i)',width,false);}return pad(String(v==null?'<nil>':v),width,false);}",
166
186
  "case'f':{const n=Number(v),p=prec!==''?parseInt(prec):6;return pad(n.toFixed(p),width,zero);}",
167
187
  "case't':return pad(String(!!v),width,false);",
168
188
  "case'x':return pad((Number(v)>>>0).toString(16),width,zero);",
@@ -172,10 +192,19 @@ export class CodeGen {
172
192
  "case'q':return pad('\"'+String(v==null?'':v).replace(/\\\\/g,'\\\\\\\\').replace(/\"/g,'\\\\\"')+'\"',width,false);",
173
193
  "case'e':case'E':{const n=Number(v),p=prec!==''?parseInt(prec):6;return pad(n.toExponential(p),width,zero);}",
174
194
  "case'g':case'G':{const n=Number(v);return pad(prec!==''?n.toPrecision(parseInt(prec)):String(n),width,zero);}",
195
+ "case'w':return pad(String(v==null?'<nil>':typeof v==='object'&&v.Error?v.Error():v),width,false);",
175
196
  "default:return m;}});",
176
197
  "}",
177
198
  ].join(""),
178
199
  );
200
+ if (this._usesError)
201
+ helpers.push(
202
+ "function __error(msg, cause) { return { Error() { return msg; }, toString() { return msg; }, _msg: msg, _cause: cause ?? null }; }",
203
+ );
204
+ if (this._usesErrorIs)
205
+ helpers.push(
206
+ 'function __errorIs(err, target) { while (err !== null && err !== undefined) { if (err === target) return true; if (typeof err === "object" && typeof target === "object" && err._msg !== undefined && target._msg !== undefined && err._msg === target._msg) return true; err = err?._cause ?? null; } return false; }',
207
+ );
179
208
 
180
209
  if (helpers.length > 0) this.out.unshift(...helpers, "");
181
210
 
@@ -278,12 +307,16 @@ export class CodeGen {
278
307
  const params = decl.params.map((p) => p.name).join(", ");
279
308
  const asyncPrefix = decl.async ? "async " : "";
280
309
  this.line(`${asyncPrefix}${decl.name}(${params}) {`);
310
+ const prevBoxed = this._boxedVars;
311
+ this._boxedVars = new Set();
312
+ this._scanAddressTaken(decl.body);
281
313
  this.indented(() => {
282
314
  if (decl.recvName && decl.recvName !== "_") {
283
315
  this.line(`const ${decl.recvName} = this;`);
284
316
  }
285
317
  this._withNamedReturns(decl, () => this._genBody(decl.body));
286
318
  });
319
+ this._boxedVars = prevBoxed;
287
320
  this.line("}");
288
321
  }
289
322
 
@@ -302,9 +335,13 @@ export class CodeGen {
302
335
  `${asyncPrefix}function ${name}(${params}) {`,
303
336
  srcLine ? srcLine - 1 : null,
304
337
  );
338
+ const prevBoxed = this._boxedVars;
339
+ this._boxedVars = new Set();
340
+ this._scanAddressTaken(decl.body);
305
341
  this.indented(() =>
306
342
  this._withNamedReturns(decl, () => this._genBody(decl.body)),
307
343
  );
344
+ this._boxedVars = prevBoxed;
308
345
  this.line("}");
309
346
  }
310
347
 
@@ -357,12 +394,61 @@ export class CodeGen {
357
394
  }
358
395
  }
359
396
 
397
+ // Scan AST node for _addressTaken idents on scalars and populate _boxedVars.
398
+ _scanAddressTaken(node) {
399
+ if (!node || typeof node !== "object") return;
400
+ if (Array.isArray(node)) {
401
+ for (const child of node) this._scanAddressTaken(child);
402
+ return;
403
+ }
404
+ // &x — the operand ident will have _addressTaken set by typechecker
405
+ if (node.kind === "Ident" && node._addressTaken) {
406
+ // Check if the type is a scalar (needs boxing) vs reference type (no boxing)
407
+ const t = node._type;
408
+ if (t && !this._isReferenceType(t)) {
409
+ this._boxedVars.add(node.name);
410
+ }
411
+ }
412
+ // Recurse into FuncLit too — closures may take address of outer vars
413
+ for (const key of Object.keys(node)) {
414
+ if (key.startsWith("_")) continue;
415
+ this._scanAddressTaken(node[key]);
416
+ }
417
+ }
418
+
419
+ _isReferenceType(t) {
420
+ if (!t) return false;
421
+ const base = t.kind === "named" ? t.underlying : t;
422
+ return (
423
+ base?.kind === "struct" ||
424
+ base?.kind === "slice" ||
425
+ base?.kind === "map" ||
426
+ base?.kind === "func" ||
427
+ base?.kind === "interface"
428
+ );
429
+ }
430
+
360
431
  // ── Variable / const declarations ────────────────────────────
361
432
 
362
433
  genVarDecl(decl) {
363
434
  for (const spec of decl.decls) {
364
435
  if (spec.value) {
365
- const vals = spec.value.map((v) => this.genExpr(v));
436
+ const vals = spec.value.map((v) => {
437
+ const js = this.genExpr(v);
438
+ // Wrap numeric values assigned to complex-typed vars
439
+ if (
440
+ spec.type?.name === "complex128" ||
441
+ spec.type?.name === "complex64"
442
+ ) {
443
+ if (
444
+ !isComplex(v._type) &&
445
+ (isNumeric(v._type) || v._type?.kind === "untyped")
446
+ ) {
447
+ return `{ re: ${js}, im: 0 }`;
448
+ }
449
+ }
450
+ return js;
451
+ });
366
452
  if (spec.names.length === 1) {
367
453
  this.line(`let ${spec.names[0]} = ${vals[0]};`);
368
454
  } else {
package/src/dts-parser.js CHANGED
@@ -74,7 +74,7 @@ export class DtsParser {
74
74
  this.pos += 2;
75
75
  while (!this.eof() && !(this.ch() === "*" && this.ch1() === "/"))
76
76
  this.pos++;
77
- this.pos += 2;
77
+ if (!this.eof()) this.pos += 2;
78
78
  continue;
79
79
  }
80
80
  break;
package/src/index.js CHANGED
@@ -27,11 +27,11 @@ const _require = createRequire(import.meta.url);
27
27
  const { version } = _require("../package.json");
28
28
 
29
29
  import { basename, dirname, join, resolve } from "node:path";
30
- import { minify } from "terser";
31
30
  import { CodeGen } from "./codegen.js";
32
31
  import { compileDir } from "./compiler.js";
33
32
  import { parseDts } from "./dts-parser.js";
34
33
  import { Lexer } from "./lexer.js";
34
+ import { minify } from "./minifier.js";
35
35
  import { Parser } from "./parser.js";
36
36
  import { isLocalPath, resolveAll, resolveGwDir } from "./resolver.js";
37
37
  import { TypeChecker } from "./typechecker.js";
@@ -56,7 +56,8 @@ Usage:
56
56
  gofront <input> --check Type-check only
57
57
  gofront <input> --watch Watch for changes and recompile
58
58
  gofront <input> --source-map Append inline source map to output
59
- gofront <input> --minify Minify output with terser
59
+ gofront <input> --minify Minify output
60
+ gofront <input> --minify --mangle Minify and mangle identifiers
60
61
  gofront <file.go> --ast Dump AST (debug)
61
62
  gofront <file.go> --tokens Dump tokens (debug)
62
63
  gofront init [dir] Scaffold a new GoFront project
@@ -121,6 +122,7 @@ const dumpTokens = args.includes("--tokens");
121
122
  const sourceMap = args.includes("--source-map");
122
123
  const watchMode = args.includes("--watch");
123
124
  const minifyOutput = args.includes("--minify");
125
+ const mangleOutput = args.includes("--mangle");
124
126
 
125
127
  // ── Determine input mode ─────────────────────────────────────
126
128
 
@@ -232,14 +234,13 @@ function runCompile() {
232
234
 
233
235
  // ── Minify helper ────────────────────────────────────────────
234
236
 
235
- async function maybeMinify(js) {
237
+ function maybeMinify(js) {
236
238
  if (!minifyOutput) return js;
237
- const result = await minify(js, {
238
- module: true,
239
- compress: true,
240
- mangle: true,
241
- });
242
- return result.code;
239
+ if (sourceMap) {
240
+ console.error("gofront: --source-map and --minify cannot be used together");
241
+ process.exit(1);
242
+ }
243
+ return minify(js, { mangle: mangleOutput });
243
244
  }
244
245
 
245
246
  // ── Single-shot mode ─────────────────────────────────────────
@@ -262,7 +263,7 @@ if (!watchMode) {
262
263
 
263
264
  let js;
264
265
  try {
265
- js = await maybeMinify(result.js);
266
+ js = maybeMinify(result.js);
266
267
  } catch (e) {
267
268
  console.error(`gofront: minify failed: ${e.message}`);
268
269
  process.exit(1);
@@ -290,11 +291,11 @@ function timestamp() {
290
291
  return new Date().toLocaleTimeString();
291
292
  }
292
293
 
293
- async function buildOnce() {
294
+ function buildOnce() {
294
295
  try {
295
296
  const startMs = performance.now();
296
297
  const result = runCompile();
297
- const js = await maybeMinify(result.js);
298
+ const js = maybeMinify(result.js);
298
299
  const elapsedMs = (performance.now() - startMs).toFixed(0);
299
300
  if (outputFile) {
300
301
  writeFileSync(outputFile, `${js}\n`);
package/src/lexer.js CHANGED
@@ -8,6 +8,8 @@ export const T = {
8
8
  STRING: "STRING",
9
9
  IDENT: "IDENT",
10
10
 
11
+ IMAG: "IMAG",
12
+
11
13
  // Keywords
12
14
  FUNC: "func",
13
15
  VAR: "var",
@@ -72,6 +74,7 @@ export const T = {
72
74
  PIPE: "|",
73
75
  CARET: "^",
74
76
  AND_NOT: "&^",
77
+ TILDE: "~",
75
78
  LSHIFT: "<<",
76
79
  RSHIFT: ">>",
77
80
  ELLIPSIS: "...",
@@ -97,6 +100,7 @@ const SEMI_TRIGGERS = new Set([
97
100
  T.IDENT,
98
101
  T.INT,
99
102
  T.FLOAT,
103
+ T.IMAG,
100
104
  T.STRING,
101
105
  T.RPAREN,
102
106
  T.RBRACE,
@@ -297,10 +301,20 @@ export class Lexer {
297
301
  }
298
302
 
299
303
  readRawString() {
304
+ const l = this.line,
305
+ c = this.col;
300
306
  this.advance(); // opening `
301
307
  let s = "";
302
308
  while (this.pos < this.src.length && this.peek() !== "`")
303
309
  s += this.advance();
310
+ if (this.pos >= this.src.length)
311
+ throw new LexError(
312
+ "Unterminated raw string",
313
+ l,
314
+ c,
315
+ this.filename,
316
+ this.src,
317
+ );
304
318
  this.advance(); // closing `
305
319
  return s;
306
320
  }
@@ -501,7 +515,13 @@ export class Lexer {
501
515
  // Numbers
502
516
  if (/[0-9]/.test(ch)) {
503
517
  const { n, isFloat } = this.readNumber();
504
- this.push(isFloat ? T.FLOAT : T.INT, n, l, c);
518
+ if (this.src[this.pos] === "i") {
519
+ this.pos++;
520
+ this.col++;
521
+ this.push(T.IMAG, n, l, c);
522
+ } else {
523
+ this.push(isFloat ? T.FLOAT : T.INT, n, l, c);
524
+ }
505
525
  continue;
506
526
  }
507
527
 
@@ -574,6 +594,9 @@ export class Lexer {
574
594
  if (this.match("=")) this.push(T.CARET_ASSIGN, "^=", l, c);
575
595
  else this.push(T.CARET, "^", l, c);
576
596
  break;
597
+ case "~":
598
+ this.push(T.TILDE, "~", l, c);
599
+ break;
577
600
  case ":":
578
601
  if (this.match("=")) this.push(T.DEFINE, ":=", l, c);
579
602
  else this.push(T.COLON, ":", l, c);