gofront 0.0.7 → 0.0.8

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/README.md CHANGED
@@ -101,8 +101,8 @@ Walks the typed AST and emits clean, readable JavaScript. No intermediate repres
101
101
  — the codegen writes directly to an output buffer with indentation tracking.
102
102
 
103
103
  Runtime helpers (`__len`, `__append`, `__s`, `__sprintf`, `__cmul`, `__cdiv`, `__error`,
104
- `__errorIs`) are tree-shaken: only emitted when actually used. Optional inline source
105
- maps are supported via VLQ-encoded mappings.
104
+ `__errorIs`, `__timeFmt`, `__timeParse`, `__pathClean`) are tree-shaken: only emitted
105
+ when actually used. Optional inline source maps are supported via VLQ-encoded mappings.
106
106
 
107
107
  ---
108
108
 
@@ -464,21 +464,24 @@ signatures into GoFront's internal type representation.
464
464
 
465
465
  | Package | Functions |
466
466
  |---|---|
467
- | `fmt` | `Sprintf`, `Printf`, `Println`, `Print`, `Errorf`, `Fprintf`, `Fprintln`, `Fprint` — format verbs: `%v`, `%d`, `%s`, `%t`, `%x`, `%o`, `%b`, `%q`, `%e`, `%g`, `%w`, width/precision |
468
- | `strings` | `Contains`, `HasPrefix`, `HasSuffix`, `Index`, `LastIndex`, `Count`, `Repeat`, `Replace`, `ReplaceAll`, `ToUpper`, `ToLower`, `TrimSpace`, `Trim`, `TrimPrefix`, `TrimSuffix`, `TrimLeft`, `TrimRight`, `Split`, `Join`, `EqualFold`; **`Builder`** type (`WriteString`, `WriteByte`, `WriteRune`, `Write`, `String`, `Len`, `Reset`, `Grow`) |
469
- | `bytes` | `Contains`, `HasPrefix`, `HasSuffix`, `Index`, `Join`, `Split`, `Replace`, `ToUpper`, `ToLower`, `TrimSpace`, `Equal`, `Count`, `Repeat`; **`Buffer`** type (`WriteString`, `WriteByte`, `Write`, `String`, `Bytes`, `Len`, `Reset`) |
470
- | `strconv` | `Itoa`, `Atoi`, `FormatBool`, `FormatInt`, `FormatFloat`, `ParseFloat`, `ParseInt`, `ParseBool` |
471
- | `sort` | `Ints`, `Float64s`, `Strings`, `Slice`, `SliceStable`, `SliceIsSorted` |
472
- | `math` | `Abs`, `Floor`, `Ceil`, `Round`, `Sqrt`, `Cbrt`, `Pow`, `Log`, `Log2`, `Log10`, trig functions + `Pi`, `E`, `MaxFloat64`, `MaxInt`, `MinInt` |
467
+ | `fmt` | `Sprintf`, `Printf`, `Println`, `Print`, `Errorf`, `Fprintf`, `Fprintln`, `Fprint` — format verbs: `%v`, `%d`, `%s`, `%t`, `%x`, `%o`, `%b`, `%q`, `%e`, `%g`, `%w`, width/precision; **scanning**: `Sscan`, `Sscanln`, `Sscanf` |
468
+ | `strings` | `Contains`, `HasPrefix`, `HasSuffix`, `Index`, `LastIndex`, `Count`, `Repeat`, `Replace`, `ReplaceAll`, `ToUpper`, `ToLower`, `TrimSpace`, `Trim`, `TrimPrefix`, `TrimSuffix`, `TrimLeft`, `TrimRight`, `Split`, `Join`, `EqualFold`, `Fields`, `Cut`, `CutPrefix`, `CutSuffix`, `SplitN`, `SplitAfter`, `SplitAfterN`, `IndexAny`, `LastIndexAny`, `ContainsAny`, `ContainsRune`, `IndexRune`, `IndexByte`, `LastIndexByte`, `Map`, `Title`, `ToTitle`, `TrimFunc`, `TrimLeftFunc`, `TrimRightFunc`, `IndexFunc`, `LastIndexFunc`, `NewReplacer`; **`Builder`** type; **`NewReader`** → reader shim |
469
+ | `bytes` | `Contains`, `HasPrefix`, `HasSuffix`, `Index`, `Join`, `Split`, `Replace`, `ToUpper`, `ToLower`, `TrimSpace`, `Equal`, `Count`, `Repeat`, `ReplaceAll`, `TrimPrefix`, `TrimSuffix`, `TrimLeft`, `TrimRight`, `TrimFunc`, `IndexByte`, `LastIndex`, `LastIndexByte`, `Fields`, `Cut`, `ContainsAny`, `ContainsRune`, `Map`, `SplitN`; **`Buffer`** type; **`NewReader`** → reader shim |
470
+ | `strconv` | `Itoa`, `Atoi`, `FormatBool`, `FormatInt`, `FormatFloat`, `ParseFloat`, `ParseInt`, `ParseBool`, `Quote`, `Unquote`, `AppendInt`, `AppendFloat` |
471
+ | `sort` | `Ints`, `Float64s`, `Strings`, `Slice`, `SliceStable`, `SliceIsSorted`, `Search`, `IntsAreSorted`, `Float64sAreSorted`, `StringsAreSorted` |
472
+ | `math` | `Abs`, `Floor`, `Ceil`, `Round`, `Sqrt`, `Cbrt`, `Pow`, `Log`, `Log2`, `Log10`, `Sin`, `Cos`, `Tan`, `Atan`, `Atan2`, `Asin`, `Acos`, `Exp`, `Exp2`, `Trunc`, `Hypot`, `Signbit`, `Copysign`, `Dim`, `Remainder`, `Min`, `Max`, `Mod`, `Inf`, `IsNaN`, `IsInf`, `NaN` + `Pi`, `E`, `MaxFloat64`, `SmallestNonzeroFloat64`, `MaxInt`, `MinInt` |
473
+ | `math/rand` | `Intn`, `Float64`, `Float32`, `Int`, `Int63`, `Int63n`, `Int31`, `Int31n`, `Seed` (no-op), `Shuffle`, `Perm` |
473
474
  | `errors` | `New`, `Is`, `Unwrap` — custom error types via interface satisfaction |
474
- | `time` | `Now`, `Since`, `Sleep` + `Millisecond`, `Second`, `Minute`, `Hour` constants |
475
+ | `time` | `Now` → `time.Time`, `Since`, `Sleep`, `Parse`, `Unix`, `Date`; **`time.Time`** methods: `Format`, `String`, `Year`, `Month`, `Day`, `Hour`, `Minute`, `Second`, `Weekday`, `Unix`, `UnixMilli`, `Add`, `Sub`, `Before`, `After`, `Equal`; layout constants: `RFC3339`, `RFC3339Nano`, `DateOnly`, `TimeOnly`, `DateTime`; duration constants: `Millisecond`, `Second`, `Minute`, `Hour`; month/weekday constants |
475
476
  | `html` | `EscapeString`, `UnescapeString` |
476
477
  | `maps` | `Keys`, `Values`, `Clone`, `Copy`, `Equal`, `EqualFunc`, `Delete`, `DeleteFunc` |
477
478
  | `slices` | `Contains`, `Index`, `Equal`, `Compare`, `Sort`, `SortFunc`, `SortStableFunc`, `IsSorted`, `IsSortedFunc`, `Reverse`, `Max`, `Min`, `MaxFunc`, `MinFunc`, `Clone`, `Compact`, `CompactFunc`, `Concat`, `Delete`, `DeleteFunc`, `Insert`, `Replace`, `Grow`, `Clip` |
478
479
  | `regexp` | `MustCompile`, `Compile`, `MatchString`, `QuoteMeta`; **`*Regexp`** methods: `MatchString`, `FindString`, `FindStringIndex`, `FindAllString`, `FindStringSubmatch`, `FindAllStringSubmatch`, `ReplaceAllString`, `ReplaceAllLiteralString`, `Split`, `String`. Inline flags (`(?i)`, `(?m)`, `(?s)`) are extracted automatically into the JS `RegExp` constructor. |
479
480
  | `unicode` | `IsLetter`, `IsDigit`, `IsSpace`, `IsUpper`, `IsLower`, `IsPunct`, `IsControl`, `IsPrint`, `IsGraphic`, `ToUpper`, `ToLower` |
481
+ | `unicode/utf8` | `RuneCountInString`, `RuneLen`, `ValidString`, `ValidRune`, `DecodeRuneInString`, `DecodeLastRuneInString`, `FullRuneInString`; constants `RuneError`, `MaxRune`, `UTFMax` |
482
+ | `path` | `Base`, `Dir`, `Ext`, `Join`, `Clean`, `IsAbs`, `Split`, `Match`; `"path/filepath"` is an alias |
480
483
  | `os` | `Exit`, `Args`, `Getenv` |
481
- | `io` | `Writer`, `Reader`, `ReadWriter`, `Closer` interface types — used for accepting `*strings.Builder`, `*bytes.Buffer`, or any compatible writer/reader |
484
+ | `io` | `Writer`, `Reader`, `ReadWriter`, `Closer` interface types; `ReadAll`, `EOF`, `Discard`, `WriteString` |
482
485
  | `gom` | Browser-native declarative DOM component library. **Types**: `Node` (interface), `NodeFunc`, `Group`. **Core**: `El(tag, children...)`, `Text(s)`, `Mount(sel, node)`, `MountTo(sel, node)`. **Attributes**: `Attr`, `Class`, `Href`, `Type`, `Src`, `Placeholder`, `DataAttr`, `Style`, `For`, `Name`, `Value`, `Target`, `Rel`, `Alt`, `Title`, `Draggable`, `Role`, `AriaLabel`, `StyleAttr`; boolean: `Disabled`, `Checked`, `Selected`, `Readonly`. **Logic**: `If(cond, node)`, `Map(slice, fn)`. **Elements**: full HTML element set (`Div`, `Span`, `Button`, `Input`, `Ul`, `Li`, `Table`, `Form`, `Img`, `A`, `H1`–`H6`, …) |
483
486
 
484
487
  ### Packages & imports
@@ -558,11 +561,18 @@ Design documents for planned features are organised by release under `docs/v*/`
558
561
  ## Tests
559
562
 
560
563
  ```sh
561
- npm test
564
+ npm run test:unit # unit tests only (~1076 tests, no browser required)
565
+ npm run test:e2e # E2E tests (Playwright, headless Chromium)
566
+ npm test # both
562
567
  ```
563
568
 
564
- 990 tests covering language features, type errors, edge cases, DOM (jsdom), external
565
- `.d.ts`, npm resolver, multi-file compilation, embedded structs, string formatting, map
566
- iteration order, integer overflow semantics, unused variable detection, unused import
567
- detection, semantic difference verification, stdlib shim packages, generics (type params,
568
- inference, constraints), and both example apps.
569
+ **Unit tests** (1076) cover language features, type errors, edge cases, DOM (jsdom),
570
+ external `.d.ts`, npm resolver, multi-file compilation, embedded structs, string
571
+ formatting, map iteration order, integer overflow semantics, unused variable detection,
572
+ unused import detection, semantic difference verification, stdlib shim packages, and
573
+ generics.
574
+
575
+ **E2E tests** (74, Playwright) run all three example apps in a real browser and verify
576
+ CRUD, filtering, priority mode, persistence (reload), drag-and-drop reordering, and sync
577
+ status. Per-app suites check app-specific behaviour: scoped styles, stats bar, loading
578
+ placeholder, and `gom.If` conditional rendering.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gofront",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "author": "Luuk van Venrooij",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -34,14 +34,18 @@
34
34
  "build:simple": "node src/index.js example/simple/src -o example/simple/app.js",
35
35
  "build:reactive": "node src/index.js example/reactive/src -o example/reactive/app.js",
36
36
  "build:gom": "node src/index.js example/gom/src -o example/gom/app.js",
37
- "test": "node test/unit/run.js",
37
+ "test": "npm run test:unit && npm run test:e2e",
38
+ "test:unit": "node test/unit/run.js",
39
+ "test:e2e": "playwright test",
38
40
  "format": "biome format --write .",
39
- "check": "biome check .",
41
+ "check": "biome check . && node src/index.js example/simple/src --check && node src/index.js example/reactive/src --check && node src/index.js example/gom/src --check",
40
42
  "prepare": "husky"
41
43
  },
42
44
  "devDependencies": {
43
45
  "@biomejs/biome": "^2.4.12",
46
+ "@playwright/test": "^1.59.1",
44
47
  "husky": "^9.1.7",
45
- "jsdom": "^29.0.2"
48
+ "jsdom": "^29.0.2",
49
+ "serve": "^14.2.6"
46
50
  }
47
51
  }
@@ -2,8 +2,6 @@
2
2
 
3
3
  import { ERROR, isComplex } from "../typechecker/types.js";
4
4
 
5
- const globalError = ERROR;
6
-
7
5
  const INT_TYPE_NAMES = new Set([
8
6
  "int",
9
7
  "uint",
@@ -163,7 +161,7 @@ export const expressionGenMethods = {
163
161
  return "process.argv";
164
162
  }
165
163
  }
166
- // time constants (nanosecond durations)
164
+ // time constants (nanosecond durations and layout strings)
167
165
  if (expr.expr.kind === "Ident" && expr.expr.name === "time") {
168
166
  switch (expr.field) {
169
167
  case "Millisecond":
@@ -174,6 +172,68 @@ export const expressionGenMethods = {
174
172
  return "60000000000";
175
173
  case "Hour":
176
174
  return "3600000000000";
175
+ case "RFC3339":
176
+ return '"2006-01-02T15:04:05Z07:00"';
177
+ case "RFC3339Nano":
178
+ return '"2006-01-02T15:04:05.999999999Z07:00"';
179
+ case "DateOnly":
180
+ return '"2006-01-02"';
181
+ case "TimeOnly":
182
+ return '"15:04:05"';
183
+ case "DateTime":
184
+ return '"2006-01-02 15:04:05"';
185
+ case "UTC":
186
+ case "Local":
187
+ return "null";
188
+ case "January":
189
+ return "1";
190
+ case "February":
191
+ return "2";
192
+ case "March":
193
+ return "3";
194
+ case "April":
195
+ return "4";
196
+ case "May":
197
+ return "5";
198
+ case "June":
199
+ return "6";
200
+ case "July":
201
+ return "7";
202
+ case "August":
203
+ return "8";
204
+ case "September":
205
+ return "9";
206
+ case "October":
207
+ return "10";
208
+ case "November":
209
+ return "11";
210
+ case "December":
211
+ return "12";
212
+ case "Sunday":
213
+ return "0";
214
+ case "Monday":
215
+ return "1";
216
+ case "Tuesday":
217
+ return "2";
218
+ case "Wednesday":
219
+ return "3";
220
+ case "Thursday":
221
+ return "4";
222
+ case "Friday":
223
+ return "5";
224
+ case "Saturday":
225
+ return "6";
226
+ }
227
+ }
228
+ // utf8 constants
229
+ if (expr.expr.kind === "Ident" && expr.expr.name === "utf8") {
230
+ switch (expr.field) {
231
+ case "RuneError":
232
+ return "0xFFFD";
233
+ case "MaxRune":
234
+ return "0x10FFFF";
235
+ case "UTFMax":
236
+ return "4";
177
237
  }
178
238
  }
179
239
  const base = this.genExpr(expr.expr);
@@ -456,7 +516,7 @@ export const expressionGenMethods = {
456
516
  expr.func.kind === "SelectorExpr" &&
457
517
  expr.func.field === "Error" &&
458
518
  expr.func.expr._type &&
459
- (expr.func.expr._type === globalError ||
519
+ (expr.func.expr._type === ERROR ||
460
520
  expr.func.expr._type?.name === "error" ||
461
521
  (expr.func.expr._type?.kind === "named" &&
462
522
  expr.func.expr._type?.underlying?.kind === "interface"))
@@ -485,6 +545,9 @@ export const expressionGenMethods = {
485
545
  if (recvName === "regexp.Regexp") {
486
546
  return this._genRegexpMethodCall(expr.func.field, expr);
487
547
  }
548
+ if (recvName === "time.Time") {
549
+ return this._genTimeMethodCall(expr.func.field, expr);
550
+ }
488
551
  }
489
552
 
490
553
  // Mark the callee so SelectorExpr knows it's being called, not used as a value
@@ -1096,6 +1159,10 @@ export const expressionGenMethods = {
1096
1159
  // strings.Builder has { _buf: string }, bytes.Buffer has { _buf: [] }.
1097
1160
  return `((b,s) => { const w = b?.value ?? b; if (typeof w.WriteString === "function") return [w.WriteString(s), null]; if (typeof w._buf === "string") { w._buf += s; return [s.length, null]; } if (Array.isArray(w._buf)) { w._buf.push(...new TextEncoder().encode(s)); return [s.length, null]; } return [0, "io.WriteString: unsupported writer"]; })(${w}, ${s})`;
1098
1161
  }
1162
+ case "ReadAll": {
1163
+ const r = this.genExpr(expr.args[0]);
1164
+ return `((r) => { const w = r?.value ?? r; if (typeof w._src === "string") { const s = w._src.slice(w._pos); w._pos = w._src.length; return [[...s].map(c => c.charCodeAt(0)), null]; } if (Array.isArray(w._src)) { const b = w._src.slice(w._pos); w._pos = w._src.length; return [b, null]; } const chunks = []; const buf = new Array(4096); let err = null; while (!err) { const [n, e] = w.Read(buf); if (n > 0) chunks.push(...buf.slice(0, n)); err = e; } return [chunks, err === "EOF" ? null : err]; })(${r})`;
1165
+ }
1099
1166
  }
1100
1167
  return undefined;
1101
1168
  },
@@ -1202,6 +1269,12 @@ export const expressionGenMethods = {
1202
1269
  return this._genHtml(fn, a);
1203
1270
  case "io":
1204
1271
  return this._genIo(fn, a, expr);
1272
+ case "rand":
1273
+ return this._genRand(fn, a);
1274
+ case "utf8":
1275
+ return this._genUtf8(fn, a);
1276
+ case "path":
1277
+ return this._genPath(fn, a, expr);
1205
1278
  case "gom":
1206
1279
  return this._genGom(fn, expr);
1207
1280
  default:
@@ -1425,6 +1498,31 @@ export const expressionGenMethods = {
1425
1498
  return `(${buf} += __sprintf("%v\\n", ${restJs}))`;
1426
1499
  return `(${buf} += __sprintf("%v", ${restJs}))`;
1427
1500
  }
1501
+ case "Sscan": {
1502
+ const strArg = this.genExpr(expr.args[0]);
1503
+ const restArgs = expr.args
1504
+ .slice(1)
1505
+ .map((e) => this.genExpr(e))
1506
+ .join(", ");
1507
+ return `((str, ...ptrs) => { const tokens = str.trim().split(/\\s+/).filter(Boolean); let n = 0; for (let i = 0; i < ptrs.length && i < tokens.length; i++) { const t = tokens[i]; const p = ptrs[i]; if (typeof p.value === "number") p.value = Number(t); else if (typeof p.value === "boolean") p.value = t === "true"; else p.value = t; n++; } return [n, n < ptrs.length ? "unexpected EOF" : null]; })(${strArg}, ${restArgs})`;
1508
+ }
1509
+ case "Sscanln": {
1510
+ const strArg = this.genExpr(expr.args[0]);
1511
+ const restArgs = expr.args
1512
+ .slice(1)
1513
+ .map((e) => this.genExpr(e))
1514
+ .join(", ");
1515
+ return `((str, ...ptrs) => { const line = str.split("\\n")[0]; const tokens = line.trim().split(/\\s+/).filter(Boolean); let n = 0; for (let i = 0; i < ptrs.length && i < tokens.length; i++) { const t = tokens[i]; const p = ptrs[i]; if (typeof p.value === "number") p.value = Number(t); else if (typeof p.value === "boolean") p.value = t === "true"; else p.value = t; n++; } return [n, n < ptrs.length ? "unexpected EOF" : null]; })(${strArg}, ${restArgs})`;
1516
+ }
1517
+ case "Sscanf": {
1518
+ const strArg = this.genExpr(expr.args[0]);
1519
+ const fmtArg = this.genExpr(expr.args[1]);
1520
+ const restArgs = expr.args
1521
+ .slice(2)
1522
+ .map((e) => this.genExpr(e))
1523
+ .join(", ");
1524
+ return `((str, _fmt, ...ptrs) => { const tokens = str.trim().split(/\\s+/).filter(Boolean); let n = 0; for (let i = 0; i < ptrs.length && i < tokens.length; i++) { const t = tokens[i]; const p = ptrs[i]; if (typeof p.value === "number") p.value = Number(t); else if (typeof p.value === "boolean") p.value = t === "true"; else p.value = t; n++; } return [n, n < ptrs.length ? "input does not match format" : null]; })(${strArg}, ${fmtArg}, ${restArgs})`;
1525
+ }
1428
1526
  default:
1429
1527
  return undefined;
1430
1528
  }
@@ -1473,6 +1571,56 @@ export const expressionGenMethods = {
1473
1571
  return `${args[0]}.join(${args[1]})`;
1474
1572
  case "EqualFold":
1475
1573
  return `${args[0]}.toLowerCase() === ${args[1]}.toLowerCase()`;
1574
+ case "Fields":
1575
+ return `(${args[0]}).trim() === '' ? [] : (${args[0]}).trim().split(/\\s+/)`;
1576
+ case "Cut":
1577
+ return `((s, sep) => { const i = s.indexOf(sep); return i < 0 ? [s, "", false] : [s.slice(0, i), s.slice(i + sep.length), true]; })(${args[0]}, ${args[1]})`;
1578
+ case "CutPrefix":
1579
+ return `(${args[0]}).startsWith(${args[1]}) ? [(${args[0]}).slice((${args[1]}).length), true] : [${args[0]}, false]`;
1580
+ case "CutSuffix":
1581
+ return `(${args[0]}).endsWith(${args[1]}) ? [(${args[0]}).slice(0, -(${args[1]}).length), true] : [${args[0]}, false]`;
1582
+ case "SplitN":
1583
+ return `((s, sep, n) => { if (n === 0) return []; if (n < 0) return s.split(sep); const r = []; let cur = s; for (let i = 1; i < n && cur.length; i++) { const j = cur.indexOf(sep); if (j < 0) break; r.push(cur.slice(0, j)); cur = cur.slice(j + sep.length); } r.push(cur); return r; })(${args[0]}, ${args[1]}, ${args[2]})`;
1584
+ case "SplitAfter":
1585
+ return `((s, sep) => { if (sep === "") return [...s]; const r = []; let cur = s; while (cur.length) { const j = cur.indexOf(sep); if (j < 0) { r.push(cur); break; } r.push(cur.slice(0, j + sep.length)); cur = cur.slice(j + sep.length); } return r; })(${args[0]}, ${args[1]})`;
1586
+ case "SplitAfterN":
1587
+ return `((s, sep, n) => { if (n === 0) return []; if (n < 0 || sep === "") return ((s, sep) => { if (sep === "") return [...s]; const r = []; let cur = s; while (cur.length) { const j = cur.indexOf(sep); if (j < 0) { r.push(cur); break; } r.push(cur.slice(0, j + sep.length)); cur = cur.slice(j + sep.length); } return r; })(s, sep); const r = []; let cur = s; for (let i = 1; i < n && cur.length; i++) { const j = cur.indexOf(sep); if (j < 0) break; r.push(cur.slice(0, j + sep.length)); cur = cur.slice(j + sep.length); } r.push(cur); return r; })(${args[0]}, ${args[1]}, ${args[2]})`;
1588
+ case "IndexAny":
1589
+ return `((s, chars) => { let m = -1; for (const c of chars) { const i = s.indexOf(c); if (i >= 0 && (m < 0 || i < m)) m = i; } return m; })(${args[0]}, ${args[1]})`;
1590
+ case "LastIndexAny":
1591
+ return `((s, chars) => { let m = -1; for (const c of chars) { const i = s.lastIndexOf(c); if (i > m) m = i; } return m; })(${args[0]}, ${args[1]})`;
1592
+ case "ContainsAny":
1593
+ return `[...(${args[1]})].some(c => (${args[0]}).includes(c))`;
1594
+ case "ContainsRune":
1595
+ return `(${args[0]}).includes(String.fromCodePoint(${args[1]}))`;
1596
+ case "IndexRune":
1597
+ return `(${args[0]}).indexOf(String.fromCodePoint(${args[1]}))`;
1598
+ case "IndexByte":
1599
+ return `(${args[0]}).indexOf(String.fromCharCode(${args[1]}))`;
1600
+ case "LastIndexByte":
1601
+ return `(${args[0]}).lastIndexOf(String.fromCharCode(${args[1]}))`;
1602
+ case "Map":
1603
+ return `[...(${args[1]})].map(c => String.fromCodePoint((${args[0]})(c.codePointAt(0)))).join("")`;
1604
+ case "Title":
1605
+ return `(${args[0]}).replace(/\\b\\w/g, c => c.toUpperCase())`;
1606
+ case "ToTitle":
1607
+ return `(${args[0]}).toUpperCase()`;
1608
+ case "TrimFunc":
1609
+ return `((s, f) => { let l = 0, r = s.length; while (l < r && f(s.codePointAt(l))) l++; while (r > l && f(s.codePointAt(r - 1))) r--; return s.slice(l, r); })(${args[0]}, ${args[1]})`;
1610
+ case "TrimLeftFunc":
1611
+ return `((s, f) => { let l = 0; while (l < s.length && f(s.codePointAt(l))) l++; return s.slice(l); })(${args[0]}, ${args[1]})`;
1612
+ case "TrimRightFunc":
1613
+ return `((s, f) => { let r = s.length; while (r > 0 && f(s.codePointAt(r - 1))) r--; return s.slice(0, r); })(${args[0]}, ${args[1]})`;
1614
+ case "IndexFunc":
1615
+ return `((s, f) => { for (let i = 0; i < s.length; i++) { const cp = s.codePointAt(i); if (f(cp)) return i; if (cp > 0xFFFF) i++; } return -1; })(${args[0]}, ${args[1]})`;
1616
+ case "LastIndexFunc":
1617
+ return `((s, f) => { for (let i = s.length - 1; i >= 0; i--) { const cp = s.codePointAt(i); if (f(cp)) return i; } return -1; })(${args[0]}, ${args[1]})`;
1618
+ case "NewReader":
1619
+ return `{_src: ${args[0]}, _pos: 0, Read(p) { const n = Math.min(p.length, this._src.length - this._pos); for (let i = 0; i < n; i++) p[i] = this._src.charCodeAt(this._pos + i); this._pos += n; return [n, n === 0 ? "EOF" : null]; }, Len() { return this._src.length - this._pos; }, Reset(s) { this._src = s; this._pos = 0; }}`;
1620
+ case "NewReplacer": {
1621
+ const allArgs = args.join(", ");
1622
+ return `((...pairs) => { const p = []; for (let i = 0; i < pairs.length; i += 2) p.push([pairs[i], pairs[i+1]]); return { _p: p, Replace(s) { let r = s; for (const [o, n] of this._p) r = r.split(o).join(n); return r; } }; })(${allArgs})`;
1623
+ }
1476
1624
  default:
1477
1625
  return undefined;
1478
1626
  }
@@ -1511,6 +1659,38 @@ export const expressionGenMethods = {
1511
1659
  return `${__bs}(${args[0]}).split(${__bs}(${args[1]})).map(p => ${__sb}(p))`;
1512
1660
  case "Join":
1513
1661
  return `${__sb}(${args[0]}.map(p => ${__bs}(p)).join(${__bs}(${args[1]})))`;
1662
+ case "ReplaceAll":
1663
+ return `((b, o, n) => { const r = []; let i = 0; while (i <= b.length - o.length) { if (o.every((v, j) => b[i + j] === v)) { r.push(...n); i += o.length; } else r.push(b[i++]); } return r.concat(b.slice(i)); })(${args[0]}, ${args[1]}, ${args[2]})`;
1664
+ case "TrimPrefix":
1665
+ return `((b, p) => p.every((v, i) => b[i] === v) ? b.slice(p.length) : b.slice())(${args[0]}, ${args[1]})`;
1666
+ case "TrimSuffix":
1667
+ return `((b, s) => s.length && s.every((v, i) => b[b.length - s.length + i] === v) ? b.slice(0, -s.length) : b.slice())(${args[0]}, ${args[1]})`;
1668
+ case "TrimLeft":
1669
+ return `((b, c) => { let i = 0; while (i < b.length && c.includes(String.fromCharCode(b[i]))) i++; return b.slice(i); })(${args[0]}, ${args[1]})`;
1670
+ case "TrimRight":
1671
+ return `((b, c) => { let i = b.length; while (i > 0 && c.includes(String.fromCharCode(b[i - 1]))) i--; return b.slice(0, i); })(${args[0]}, ${args[1]})`;
1672
+ case "TrimFunc":
1673
+ return `((b, f) => { let l = 0, r = b.length; while (l < r && f(b[l])) l++; while (r > l && f(b[r - 1])) r--; return b.slice(l, r); })(${args[0]}, ${args[1]})`;
1674
+ case "IndexByte":
1675
+ return `(${args[0]}).indexOf(${args[1]})`;
1676
+ case "LastIndex":
1677
+ return `((b, s) => { for (let i = b.length - s.length; i >= 0; i--) if (s.every((v, j) => b[i + j] === v)) return i; return -1; })(${args[0]}, ${args[1]})`;
1678
+ case "LastIndexByte":
1679
+ return `(${args[0]}).lastIndexOf(${args[1]})`;
1680
+ case "Fields":
1681
+ return `((b) => { const s = String.fromCharCode(...b).trim(); return s === '' ? [] : s.split(/\\s+/).map(w => [...w].map(c => c.charCodeAt(0))); })(${args[0]})`;
1682
+ case "Cut":
1683
+ return `((b, sep) => { for (let i = 0; i <= b.length - sep.length; i++) if (sep.every((v, j) => b[i + j] === v)) return [b.slice(0, i), b.slice(i + sep.length), true]; return [b.slice(), [], false]; })(${args[0]}, ${args[1]})`;
1684
+ case "ContainsAny":
1685
+ return `[...(${args[1]})].some(c => (${args[0]}).includes(c.charCodeAt(0)))`;
1686
+ case "ContainsRune":
1687
+ return `(${args[0]}).includes(${args[1]})`;
1688
+ case "Map":
1689
+ return `(${args[1]}).map(v => (${args[0]})(v))`;
1690
+ case "SplitN":
1691
+ return `((b, sep, n) => { if (n === 0) return []; if (n < 0) return ((b, sep) => { const r = []; let i = 0; while (i <= b.length) { const j = b.findIndex((_, k) => k >= i && sep.every((v, l) => b[k + l] === sep[l])); if (j < 0 || j >= b.length) break; r.push(b.slice(i, j)); i = j + sep.length; } r.push(b.slice(i)); return r; })(b, sep); const r = []; let i = 0; for (let c = 1; c < n; c++) { const j = b.findIndex((_, k) => k >= i && sep.every((v, l) => b[k + l] === sep[l])); if (j < 0) break; r.push(b.slice(i, j)); i = j + sep.length; } r.push(b.slice(i)); return r; })(${args[0]}, ${args[1]}, ${args[2]})`;
1692
+ case "NewReader":
1693
+ return `{_src: ${args[0]}, _pos: 0, Read(p) { const n = Math.min(p.length, this._src.length - this._pos); for (let i = 0; i < n; i++) p[i] = this._src[this._pos + i]; this._pos += n; return [n, n === 0 ? "EOF" : null]; }, Len() { return this._src.length - this._pos; }, Reset(b) { this._src = b; this._pos = 0; }}`;
1514
1694
  default:
1515
1695
  return undefined;
1516
1696
  }
@@ -1535,6 +1715,14 @@ export const expressionGenMethods = {
1535
1715
  return `(Number.isNaN(parseInt(${args[0]}, ${args[1]} || 10)) ? [0, "invalid syntax"] : [parseInt(${args[0]}, ${args[1]} || 10), null])`;
1536
1716
  case "ParseBool":
1537
1717
  return `(${args[0]} === "true" || ${args[0]} === "1" ? [true, null] : ${args[0]} === "false" || ${args[0]} === "0" ? [false, null] : [false, "invalid syntax"])`;
1718
+ case "Quote":
1719
+ return `JSON.stringify(${args[0]})`;
1720
+ case "Unquote":
1721
+ return `((s) => { try { const v = JSON.parse(s); return [v, null]; } catch(e) { return ["", "invalid syntax"]; } })(${args[0]})`;
1722
+ case "AppendInt":
1723
+ return `[...(${args[0]}), ...new TextEncoder().encode((${args[1]}).toString(${args[2]}))]`;
1724
+ case "AppendFloat":
1725
+ return `[...(${args[0]}), ...new TextEncoder().encode(${args[1]}.toFixed(${args[3]} < 0 ? 6 : ${args[3]}))]`;
1538
1726
  default:
1539
1727
  return undefined;
1540
1728
  }
@@ -1553,6 +1741,12 @@ export const expressionGenMethods = {
1553
1741
  return `${args[0]}.sort((a, b) => ${args[1]}(a, b) ? -1 : ${args[1]}(b, a) ? 1 : 0)`;
1554
1742
  case "SliceIsSorted":
1555
1743
  return `${args[0]}.every((v, i, a) => i === 0 || ${args[1]}(a[i - 1], v))`;
1744
+ case "Search":
1745
+ return `((n, f) => { let lo = 0, hi = n; while (lo < hi) { const mid = (lo + hi) >>> 1; if (f(mid)) hi = mid; else lo = mid + 1; } return lo; })(${args[0]}, ${args[1]})`;
1746
+ case "IntsAreSorted":
1747
+ case "Float64sAreSorted":
1748
+ case "StringsAreSorted":
1749
+ return `(${args[0]}).every((v, i, a) => i === 0 || a[i - 1] <= v)`;
1556
1750
  default:
1557
1751
  return undefined;
1558
1752
  }
@@ -1601,6 +1795,30 @@ export const expressionGenMethods = {
1601
1795
  return `(${args[1]} > 0 ? ${args[0]} === Infinity : ${args[1]} < 0 ? ${args[0]} === -Infinity : !Number.isFinite(${args[0]}))`;
1602
1796
  case "NaN":
1603
1797
  return "NaN";
1798
+ case "Atan":
1799
+ return `Math.atan(${args[0]})`;
1800
+ case "Atan2":
1801
+ return `Math.atan2(${args[0]}, ${args[1]})`;
1802
+ case "Asin":
1803
+ return `Math.asin(${args[0]})`;
1804
+ case "Acos":
1805
+ return `Math.acos(${args[0]})`;
1806
+ case "Exp":
1807
+ return `Math.exp(${args[0]})`;
1808
+ case "Exp2":
1809
+ return `Math.pow(2, ${args[0]})`;
1810
+ case "Trunc":
1811
+ return `Math.trunc(${args[0]})`;
1812
+ case "Hypot":
1813
+ return `Math.hypot(${args[0]}, ${args[1]})`;
1814
+ case "Signbit":
1815
+ return `(${args[0]} < 0 || Object.is(${args[0]}, -0))`;
1816
+ case "Copysign":
1817
+ return `(Math.abs(${args[0]}) * (${args[1]} < 0 || Object.is(${args[1]}, -0) ? -1 : 1))`;
1818
+ case "Dim":
1819
+ return `Math.max(${args[0]} - ${args[1]}, 0)`;
1820
+ case "Remainder":
1821
+ return `(${args[0]} - Math.round(${args[0]} / ${args[1]}) * ${args[1]})`;
1604
1822
  default:
1605
1823
  return undefined;
1606
1824
  }
@@ -1665,15 +1883,142 @@ export const expressionGenMethods = {
1665
1883
  }
1666
1884
  },
1667
1885
 
1886
+ _genTimeMethodCall(method, expr) {
1887
+ const recv = expr.func.expr;
1888
+ const recvJs = this.genExpr(recv);
1889
+ const args = expr.args.map((a) => this.genExpr(a));
1890
+ switch (method) {
1891
+ case "Format":
1892
+ this._usesTimeFmt = true;
1893
+ return `__timeFmt(${recvJs}._d, ${args[0]})`;
1894
+ case "String":
1895
+ this._usesTimeFmt = true;
1896
+ return `__timeFmt(${recvJs}._d, "2006-01-02T15:04:05Z07:00")`;
1897
+ case "Year":
1898
+ return `${recvJs}._d.getFullYear()`;
1899
+ case "Month":
1900
+ return `${recvJs}._d.getMonth() + 1`;
1901
+ case "Day":
1902
+ return `${recvJs}._d.getDate()`;
1903
+ case "Hour":
1904
+ return `${recvJs}._d.getHours()`;
1905
+ case "Minute":
1906
+ return `${recvJs}._d.getMinutes()`;
1907
+ case "Second":
1908
+ return `${recvJs}._d.getSeconds()`;
1909
+ case "Weekday":
1910
+ return `${recvJs}._d.getDay()`;
1911
+ case "Unix":
1912
+ return `Math.floor(${recvJs}._d.getTime() / 1000)`;
1913
+ case "UnixMilli":
1914
+ return `${recvJs}._d.getTime()`;
1915
+ case "Add":
1916
+ return `{_d: new Date(${recvJs}._d.getTime() + (${args[0]}))}`;
1917
+ case "Sub":
1918
+ return `${recvJs}._d.getTime() - (${args[0]})._d.getTime()`;
1919
+ case "Before":
1920
+ return `${recvJs}._d < (${args[0]})._d`;
1921
+ case "After":
1922
+ return `${recvJs}._d > (${args[0]})._d`;
1923
+ case "Equal":
1924
+ return `${recvJs}._d.getTime() === (${args[0]})._d.getTime()`;
1925
+ default:
1926
+ return undefined;
1927
+ }
1928
+ },
1929
+
1668
1930
  _genTime(fn, a) {
1669
1931
  const args = a();
1670
1932
  switch (fn) {
1671
1933
  case "Now":
1672
- return "Date.now()";
1934
+ return "{_d: new Date()}";
1673
1935
  case "Since":
1674
- return `(Date.now() - ${args[0]})`;
1936
+ return `(Date.now() - (${args[0]})._d.getTime())`;
1675
1937
  case "Sleep":
1676
1938
  return `await new Promise(r => setTimeout(r, ${args[0]} / 1000000))`;
1939
+ case "Parse":
1940
+ this._usesTimeParse = true;
1941
+ return `__timeParse(${args[0]}, ${args[1]})`;
1942
+ case "Unix":
1943
+ return `{_d: new Date((${args[0]}) * 1000)}`;
1944
+ case "Date":
1945
+ return `{_d: new Date(${args[0]}, ${args[1]} - 1, ${args[2]}, ${args[3]}, ${args[4]}, ${args[5]})}`;
1946
+ default:
1947
+ return undefined;
1948
+ }
1949
+ },
1950
+
1951
+ _genRand(fn, a) {
1952
+ const args = a();
1953
+ switch (fn) {
1954
+ case "Intn":
1955
+ case "Int63n":
1956
+ case "Int31n":
1957
+ return `Math.floor(Math.random() * ${args[0]})`;
1958
+ case "Float64":
1959
+ case "Float32":
1960
+ return "Math.random()";
1961
+ case "Int":
1962
+ case "Int31":
1963
+ return "Math.floor(Math.random() * 2147483647)";
1964
+ case "Int63":
1965
+ return "Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)";
1966
+ case "Seed":
1967
+ return "(void 0)";
1968
+ case "Shuffle":
1969
+ return `((n, f) => { for (let i = n - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); f(i, j); } })(${args[0]}, ${args[1]})`;
1970
+ case "Perm":
1971
+ return `((n) => { const a = Array.from({length: n}, (_, i) => i); for (let i = n - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; })(${args[0]})`;
1972
+ default:
1973
+ return undefined;
1974
+ }
1975
+ },
1976
+
1977
+ _genUtf8(fn, a) {
1978
+ const args = a();
1979
+ switch (fn) {
1980
+ case "RuneCountInString":
1981
+ return `[...(${args[0]})].length`;
1982
+ case "RuneLen":
1983
+ return `((r) => r < 0 ? -1 : r <= 0x7F ? 1 : r <= 0x7FF ? 2 : r <= 0xFFFF ? (r >= 0xD800 && r <= 0xDFFF ? -1 : 3) : r <= 0x10FFFF ? 4 : -1)(${args[0]})`;
1984
+ case "ValidString":
1985
+ return `/[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]/.test(${args[0]}) === false`;
1986
+ case "ValidRune":
1987
+ return `(${args[0]}) >= 0 && (${args[0]}) <= 0x10FFFF && !((${args[0]}) >= 0xD800 && (${args[0]}) <= 0xDFFF)`;
1988
+ case "DecodeRuneInString":
1989
+ return `((s) => { if (!s) return [0xFFFD, 0]; const cp = s.codePointAt(0); return [cp, cp > 0xFFFF ? 2 : 1]; })(${args[0]})`;
1990
+ case "DecodeLastRuneInString":
1991
+ return `((s) => { if (!s) return [0xFFFD, 0]; const i = s.length > 1 && s.charCodeAt(s.length - 1) >= 0xDC00 && s.charCodeAt(s.length - 1) <= 0xDFFF ? s.length - 2 : s.length - 1; const cp = s.codePointAt(i); return [cp, cp > 0xFFFF ? 2 : 1]; })(${args[0]})`;
1992
+ case "FullRuneInString":
1993
+ return `(${args[0]}).length > 0`;
1994
+ default:
1995
+ return undefined;
1996
+ }
1997
+ },
1998
+
1999
+ _genPath(fn, a, expr) {
2000
+ const args = a();
2001
+ switch (fn) {
2002
+ case "Base":
2003
+ return `((p) => { if (!p) return "."; const stripped = p.replace(/\\/+$/, ""); if (!stripped) return "/"; const i = stripped.lastIndexOf("/"); return i < 0 ? stripped : stripped.slice(i + 1) || "/"; })(${args[0]})`;
2004
+ case "Dir":
2005
+ return `((p) => { const i = p.lastIndexOf("/"); if (i < 0) return "."; if (i === 0) return "/"; return p.slice(0, i); })(${args[0]})`;
2006
+ case "Ext":
2007
+ return `((p) => { const b = p.slice(p.lastIndexOf("/") + 1); const i = b.lastIndexOf("."); return i <= 0 ? "" : b.slice(i); })(${args[0]})`;
2008
+ case "Join": {
2009
+ this._usesPathClean = true;
2010
+ const allArgs = expr.args.map((e) => this.genExpr(e)).join(", ");
2011
+ return `__pathClean([${allArgs}].filter(x => x !== "").join("/"))`;
2012
+ }
2013
+ case "Clean":
2014
+ this._usesPathClean = true;
2015
+ return `__pathClean(${args[0]})`;
2016
+ case "IsAbs":
2017
+ return `(${args[0]}).startsWith("/")`;
2018
+ case "Split":
2019
+ return `((p) => { const i = p.lastIndexOf("/"); return i < 0 ? ["", p] : [p.slice(0, i + 1), p.slice(i + 1)]; })(${args[0]})`;
2020
+ case "Match":
2021
+ return `((pat, name) => { try { const sp=/[.+^$()|[\\]\\\\]/g; const re = new RegExp("^" + pat.replace(sp, "\\\\$&").replace(/\\*/g, "[^/]*").replace(/\\?/g, "[^/]") + "$"); return [re.test(name), null]; } catch(e) { return [false, "syntax error in pattern"]; } })(${args[0]}, ${args[1]})`;
1677
2022
  default:
1678
2023
  return undefined;
1679
2024
  }
@@ -16,9 +16,10 @@ function vlqEncode(value) {
16
16
  return result;
17
17
  }
18
18
 
19
- export function buildSourceMap(sources, mappings) {
20
- // sources: string[]
21
- // mappings: Array<{ genLine: number, srcLine: number, srcFileIdx?: number }> (0-based)
19
+ export function buildSourceMap(sources, mappings, sourcesContent) {
20
+ // sources: string[]
21
+ // mappings: Array<{ genLine: number, srcLine: number, srcFileIdx?: number }> (0-based)
22
+ // sourcesContent: string[] | undefined — original file contents, embedded for DevTools
22
23
  // Emits one segment per generated line, column 0 → source line (delta-encoded).
23
24
  const lines = [];
24
25
  let prevSrcLine = 0;
@@ -44,10 +45,12 @@ export function buildSourceMap(sources, mappings) {
44
45
  lines.push(""); // no mapping for this generated line
45
46
  }
46
47
  }
47
- return JSON.stringify({
48
+ const map = {
48
49
  version: 3,
49
50
  sources,
50
51
  names: [],
51
52
  mappings: lines.join(";"),
52
- });
53
+ };
54
+ if (sourcesContent) map.sourcesContent = sourcesContent;
55
+ return JSON.stringify(map);
53
56
  }
package/src/codegen.js CHANGED
@@ -52,6 +52,9 @@ export class CodeGen {
52
52
  this._usesCdiv = false;
53
53
  this._usesError = false;
54
54
  this._usesErrorIs = false;
55
+ this._usesPathClean = false;
56
+ this._usesTimeFmt = false;
57
+ this._usesTimeParse = false;
55
58
  // Iterator (range-over-func) context
56
59
  this._inIteratorBody = false;
57
60
  this._iterDepth = 0;
@@ -226,6 +229,47 @@ export class CodeGen {
226
229
  helpers.push(
227
230
  'var __errorIs = __errorIs || function(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; };',
228
231
  );
232
+ if (this._usesPathClean)
233
+ helpers.push(
234
+ 'var __pathClean = __pathClean || function(p) { if (!p) return "."; const abs = p.startsWith("/"); const parts = p.split("/").reduce((acc, s) => { if (s === "" || s === ".") return acc; if (s === "..") { if (acc.length && acc[acc.length-1] !== "..") acc.pop(); else if (!abs) acc.push(".."); } else acc.push(s); return acc; }, []); return (abs ? "/" : "") + (parts.join("/") || "."); };',
235
+ );
236
+ if (this._usesTimeFmt)
237
+ helpers.push(
238
+ [
239
+ "var __timeFmt = __timeFmt || function(d, layout) {",
240
+ 'const pad = (n, w=2) => String(n).padStart(w, "0");',
241
+ 'const DAYS=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];',
242
+ 'const MONTHS=["January","February","March","April","May","June","July","August","September","October","November","December"];',
243
+ "return layout.replace(/2006|06|Monday|Mon|January|Jan|01|1|02|2|15|PM|3|04|4|05|5|Z07:00|\\.000|\\.999/g, t => {",
244
+ "switch(t){",
245
+ 'case "2006": return d.getFullYear();',
246
+ 'case "06": return String(d.getFullYear()).slice(-2);',
247
+ 'case "Monday": return DAYS[d.getDay()];',
248
+ 'case "Mon": return DAYS[d.getDay()].slice(0,3);',
249
+ 'case "January": return MONTHS[d.getMonth()];',
250
+ 'case "Jan": return MONTHS[d.getMonth()].slice(0,3);',
251
+ 'case "01": return pad(d.getMonth()+1);',
252
+ 'case "1": return String(d.getMonth()+1);',
253
+ 'case "02": return pad(d.getDate());',
254
+ 'case "2": return String(d.getDate());',
255
+ 'case "15": return pad(d.getHours());',
256
+ 'case "PM": return d.getHours()<12?"AM":"PM";',
257
+ 'case "3": return String(d.getHours()%12||12);',
258
+ 'case "04": return pad(d.getMinutes());',
259
+ 'case "4": return String(d.getMinutes());',
260
+ 'case "05": return pad(d.getSeconds());',
261
+ 'case "5": return String(d.getSeconds());',
262
+ 'case "Z07:00": { const o=-d.getTimezoneOffset(); return o===0?"Z":(o>0?"+":"-")+pad(Math.floor(Math.abs(o)/60))+":"+pad(Math.abs(o)%60); }',
263
+ 'case ".000": return "."+pad(d.getMilliseconds(),3);',
264
+ 'case ".999": return d.getMilliseconds()>0?"."+String(d.getMilliseconds()).replace(/0+$/,""):"";',
265
+ "default: return t;}});",
266
+ "};",
267
+ ].join(""),
268
+ );
269
+ if (this._usesTimeParse)
270
+ helpers.push(
271
+ 'var __timeParse = __timeParse || function(layout, value) { try { const d = new Date(value); if (isNaN(d.getTime())) throw 0; return [{_d:d}, null]; } catch { return [{_d:new Date(0)}, "parsing time: cannot parse"]; } };',
272
+ );
229
273
 
230
274
  if (helpers.length > 0) this.out.unshift(...helpers, "");
231
275
 
@@ -248,9 +292,10 @@ export class CodeGen {
248
292
 
249
293
  // Returns a source map JSON string for the last generate() call.
250
294
  // sources: string[] of source filenames (relative to the output file).
251
- getSourceMap(sources) {
295
+ // sourcesContent: string[] of original file contents (embedded for DevTools breakpoints).
296
+ getSourceMap(sources, sourcesContent) {
252
297
  const srcArray = Array.isArray(sources) ? sources : [sources];
253
- return buildSourceMap(srcArray, this._srcMappings);
298
+ return buildSourceMap(srcArray, this._srcMappings, sourcesContent);
254
299
  }
255
300
 
256
301
  // ── Type declarations ────────────────────────────────────────
@@ -534,7 +579,10 @@ export class CodeGen {
534
579
  }
535
580
  } else {
536
581
  const zero = spec.type ? this.zeroValueForTypeNode(spec.type) : "null";
537
- for (const name of spec.names) this.line(`let ${name} = ${zero};`);
582
+ for (const name of spec.names) {
583
+ const val = this._boxedVars.has(name) ? `{ value: ${zero} }` : zero;
584
+ this.line(`let ${name} = ${val};`);
585
+ }
538
586
  }
539
587
  }
540
588
  }
package/src/compiler.js CHANGED
@@ -170,7 +170,8 @@ export function compileFiles(files, options = {}) {
170
170
  if (options.sourceMap) {
171
171
  const outputDir = options.outputDir ?? fromDir;
172
172
  const sourceFiles = files.map((f) => relative(outputDir, f));
173
- const map = codegen.getSourceMap(sourceFiles);
173
+ const sourcesContent = files.map((f) => readFileSync(f, "utf8"));
174
+ const map = codegen.getSourceMap(sourceFiles, sourcesContent);
174
175
  const b64 = Buffer.from(map).toString("base64");
175
176
  js += `\n//# sourceMappingURL=data:application/json;base64,${b64}`;
176
177
  }
package/src/dev-server.js CHANGED
@@ -10,6 +10,7 @@ const MIME = {
10
10
  ".js": "application/javascript; charset=utf-8",
11
11
  ".css": "text/css; charset=utf-8",
12
12
  ".json": "application/json; charset=utf-8",
13
+ ".go": "text/plain; charset=utf-8",
13
14
  ".svg": "image/svg+xml",
14
15
  ".png": "image/png",
15
16
  ".jpg": "image/jpeg",
package/src/index.js CHANGED
@@ -232,7 +232,7 @@ function runCompile() {
232
232
  if (sourceMap) {
233
233
  const outputDir = outputFile ? dirname(resolve(outputFile)) : resolve(".");
234
234
  const srcPath = relative(outputDir, inputPath);
235
- const map = cg.getSourceMap(srcPath);
235
+ const map = cg.getSourceMap(srcPath, [source]);
236
236
  const b64 = Buffer.from(map).toString("base64");
237
237
  js += `\n//# sourceMappingURL=data:application/json;base64,${b64}`;
238
238
  }
package/src/resolver.js CHANGED
@@ -25,11 +25,39 @@ function findNodeModules(startDir) {
25
25
  }
26
26
  }
27
27
 
28
+ // Built-in stdlib package paths that are always available as globals — no resolution needed.
29
+ const BUILTIN_PACKAGES = new Set([
30
+ "fmt",
31
+ "strings",
32
+ "bytes",
33
+ "strconv",
34
+ "sort",
35
+ "math",
36
+ "math/rand",
37
+ "errors",
38
+ "time",
39
+ "unicode",
40
+ "unicode/utf8",
41
+ "os",
42
+ "slices",
43
+ "html",
44
+ "io",
45
+ "gom",
46
+ "maps",
47
+ "regexp",
48
+ "path",
49
+ "path/filepath",
50
+ ]);
51
+
28
52
  // Returns true for local relative paths (start with ./ or ../)
29
53
  export function isLocalPath(importPath) {
30
54
  return importPath.startsWith("./") || importPath.startsWith("../");
31
55
  }
32
56
 
57
+ export function isBuiltinPackage(importPath) {
58
+ return BUILTIN_PACKAGES.has(importPath);
59
+ }
60
+
33
61
  // Resolve a local relative import path to an absolute directory of .go files.
34
62
  // Returns the absolute dir path, or null if it doesn't contain any .go files.
35
63
  export function resolveGwDir(importPath, fromFile) {
@@ -85,6 +113,7 @@ export function resolveAll(imports, fromFile, parseDts) {
85
113
  for (const { path } of imp.imports) {
86
114
  if (path.startsWith("js:")) continue; // handled separately
87
115
  if (isLocalPath(path)) continue; // local .go packages — handled by compiler
116
+ if (isBuiltinPackage(path)) continue; // built-in stdlib namespaces — always available
88
117
 
89
118
  if (resolved.has(path)) continue;
90
119
 
@@ -8,6 +8,9 @@
8
8
 
9
9
  import { ANY, BOOL, ERROR, FLOAT64, INT, STRING, VOID } from "./types.js";
10
10
 
11
+ // time.Time named type — used by the time package
12
+ const TIME_T = { kind: "named", name: "time.Time", underlying: ANY };
13
+
11
14
  export function setupGlobals(globals, types) {
12
15
  // Browser globals — typed as 'any' so any access/call is permitted
13
16
  const browserGlobals = [
@@ -113,6 +116,24 @@ export function setupGlobals(globals, types) {
113
116
  returns: [INT, ERROR],
114
117
  variadic: true,
115
118
  },
119
+ Sscan: {
120
+ kind: "func",
121
+ params: [STRING, ANY],
122
+ returns: [INT, ERROR],
123
+ variadic: true,
124
+ },
125
+ Sscanln: {
126
+ kind: "func",
127
+ params: [STRING, ANY],
128
+ returns: [INT, ERROR],
129
+ variadic: true,
130
+ },
131
+ Sscanf: {
132
+ kind: "func",
133
+ params: [STRING, STRING, ANY],
134
+ returns: [INT, ERROR],
135
+ variadic: true,
136
+ },
116
137
  },
117
138
  });
118
139
 
@@ -164,6 +185,71 @@ export function setupGlobals(globals, types) {
164
185
  returns: [STRING],
165
186
  },
166
187
  EqualFold: strFn2(STRING, BOOL),
188
+ Fields: strFn1({ kind: "slice", elem: STRING }),
189
+ Cut: {
190
+ kind: "func",
191
+ params: [STRING, STRING],
192
+ returns: [STRING, STRING, BOOL],
193
+ },
194
+ CutPrefix: {
195
+ kind: "func",
196
+ params: [STRING, STRING],
197
+ returns: [STRING, BOOL],
198
+ },
199
+ CutSuffix: {
200
+ kind: "func",
201
+ params: [STRING, STRING],
202
+ returns: [STRING, BOOL],
203
+ },
204
+ SplitN: strFn3(STRING, INT, { kind: "slice", elem: STRING }),
205
+ SplitAfter: strFn2(STRING, { kind: "slice", elem: STRING }),
206
+ SplitAfterN: strFn3(STRING, INT, { kind: "slice", elem: STRING }),
207
+ IndexAny: strFn2(STRING, INT),
208
+ LastIndexAny: strFn2(STRING, INT),
209
+ ContainsAny: strFn2(STRING, BOOL),
210
+ ContainsRune: strFn2(INT, BOOL),
211
+ IndexRune: strFn2(INT, INT),
212
+ IndexByte: strFn2(INT, INT),
213
+ LastIndexByte: strFn2(INT, INT),
214
+ Map: {
215
+ kind: "func",
216
+ params: [ANY, STRING],
217
+ returns: [STRING],
218
+ },
219
+ Title: strFn1(STRING),
220
+ ToTitle: strFn1(STRING),
221
+ TrimFunc: {
222
+ kind: "func",
223
+ params: [STRING, ANY],
224
+ returns: [STRING],
225
+ },
226
+ TrimLeftFunc: {
227
+ kind: "func",
228
+ params: [STRING, ANY],
229
+ returns: [STRING],
230
+ },
231
+ TrimRightFunc: {
232
+ kind: "func",
233
+ params: [STRING, ANY],
234
+ returns: [STRING],
235
+ },
236
+ IndexFunc: {
237
+ kind: "func",
238
+ params: [STRING, ANY],
239
+ returns: [INT],
240
+ },
241
+ LastIndexFunc: {
242
+ kind: "func",
243
+ params: [STRING, ANY],
244
+ returns: [INT],
245
+ },
246
+ NewReplacer: {
247
+ kind: "func",
248
+ params: [STRING],
249
+ returns: [ANY],
250
+ variadic: true,
251
+ },
252
+ NewReader: { kind: "func", params: [STRING], returns: [ANY] },
167
253
  },
168
254
  });
169
255
 
@@ -205,6 +291,46 @@ export function setupGlobals(globals, types) {
205
291
  params: [{ kind: "slice", elem: BYTE_SLICE }, BYTE_SLICE],
206
292
  returns: [BYTE_SLICE],
207
293
  },
294
+ ReplaceAll: {
295
+ kind: "func",
296
+ params: [BYTE_SLICE, BYTE_SLICE, BYTE_SLICE],
297
+ returns: [BYTE_SLICE],
298
+ },
299
+ TrimPrefix: byFn2(BYTE_SLICE, BYTE_SLICE),
300
+ TrimSuffix: byFn2(BYTE_SLICE, BYTE_SLICE),
301
+ TrimLeft: byFn2(STRING, BYTE_SLICE),
302
+ TrimRight: byFn2(STRING, BYTE_SLICE),
303
+ TrimFunc: {
304
+ kind: "func",
305
+ params: [BYTE_SLICE, ANY],
306
+ returns: [BYTE_SLICE],
307
+ },
308
+ IndexByte: byFn2(INT, INT),
309
+ LastIndex: byFn2(BYTE_SLICE, INT),
310
+ LastIndexByte: byFn2(INT, INT),
311
+ Fields: byFn1({ kind: "slice", elem: BYTE_SLICE }),
312
+ Cut: {
313
+ kind: "func",
314
+ params: [BYTE_SLICE, BYTE_SLICE],
315
+ returns: [BYTE_SLICE, BYTE_SLICE, BOOL],
316
+ },
317
+ ContainsAny: byFn2(STRING, BOOL),
318
+ ContainsRune: byFn2(INT, BOOL),
319
+ Map: {
320
+ kind: "func",
321
+ params: [ANY, BYTE_SLICE],
322
+ returns: [BYTE_SLICE],
323
+ },
324
+ SplitN: {
325
+ kind: "func",
326
+ params: [BYTE_SLICE, BYTE_SLICE, INT],
327
+ returns: [{ kind: "slice", elem: BYTE_SLICE }],
328
+ },
329
+ NewReader: {
330
+ kind: "func",
331
+ params: [BYTE_SLICE],
332
+ returns: [ANY],
333
+ },
208
334
  },
209
335
  });
210
336
 
@@ -241,6 +367,22 @@ export function setupGlobals(globals, types) {
241
367
  params: [STRING],
242
368
  returns: [BOOL, ERROR],
243
369
  },
370
+ Quote: { kind: "func", params: [STRING], returns: [STRING] },
371
+ Unquote: {
372
+ kind: "func",
373
+ params: [STRING],
374
+ returns: [STRING, ERROR],
375
+ },
376
+ AppendInt: {
377
+ kind: "func",
378
+ params: [{ kind: "slice", elem: INT }, INT, INT],
379
+ returns: [{ kind: "slice", elem: INT }],
380
+ },
381
+ AppendFloat: {
382
+ kind: "func",
383
+ params: [{ kind: "slice", elem: INT }, FLOAT64, INT, INT, INT],
384
+ returns: [{ kind: "slice", elem: INT }],
385
+ },
244
386
  },
245
387
  });
246
388
 
@@ -279,6 +421,26 @@ export function setupGlobals(globals, types) {
279
421
  params: [ANY, ANY],
280
422
  returns: [BOOL],
281
423
  },
424
+ Search: {
425
+ kind: "func",
426
+ params: [INT, ANY],
427
+ returns: [INT],
428
+ },
429
+ IntsAreSorted: {
430
+ kind: "func",
431
+ params: [{ kind: "slice", elem: INT }],
432
+ returns: [BOOL],
433
+ },
434
+ Float64sAreSorted: {
435
+ kind: "func",
436
+ params: [{ kind: "slice", elem: FLOAT64 }],
437
+ returns: [BOOL],
438
+ },
439
+ StringsAreSorted: {
440
+ kind: "func",
441
+ params: [{ kind: "slice", elem: STRING }],
442
+ returns: [BOOL],
443
+ },
282
444
  },
283
445
  });
284
446
 
@@ -327,6 +489,26 @@ export function setupGlobals(globals, types) {
327
489
  returns: [BOOL],
328
490
  },
329
491
  NaN: { kind: "func", params: [], returns: [FLOAT64] },
492
+ Atan: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
493
+ Atan2: { kind: "func", params: [FLOAT64, FLOAT64], returns: [FLOAT64] },
494
+ Asin: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
495
+ Acos: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
496
+ Exp: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
497
+ Exp2: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
498
+ Trunc: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
499
+ Hypot: { kind: "func", params: [FLOAT64, FLOAT64], returns: [FLOAT64] },
500
+ Signbit: { kind: "func", params: [FLOAT64], returns: [BOOL] },
501
+ Copysign: {
502
+ kind: "func",
503
+ params: [FLOAT64, FLOAT64],
504
+ returns: [FLOAT64],
505
+ },
506
+ Dim: { kind: "func", params: [FLOAT64, FLOAT64], returns: [FLOAT64] },
507
+ Remainder: {
508
+ kind: "func",
509
+ params: [FLOAT64, FLOAT64],
510
+ returns: [FLOAT64],
511
+ },
330
512
  // Constants
331
513
  Pi: FLOAT64,
332
514
  E: FLOAT64,
@@ -353,15 +535,57 @@ export function setupGlobals(globals, types) {
353
535
  kind: "namespace",
354
536
  name: "time",
355
537
  members: {
356
- Now: { kind: "func", params: [], returns: [ANY] },
357
- Since: { kind: "func", params: [ANY], returns: [ANY] },
538
+ Now: { kind: "func", params: [], returns: [TIME_T] },
539
+ Since: { kind: "func", params: [TIME_T], returns: [INT] },
358
540
  Sleep: { kind: "func", params: [ANY], returns: [VOID], async: true },
541
+ Parse: {
542
+ kind: "func",
543
+ params: [STRING, STRING],
544
+ returns: [TIME_T, ERROR],
545
+ },
546
+ Unix: { kind: "func", params: [INT, INT], returns: [TIME_T] },
547
+ Date: {
548
+ kind: "func",
549
+ params: [INT, INT, INT, INT, INT, INT, INT, ANY],
550
+ returns: [TIME_T],
551
+ },
359
552
  Millisecond: INT,
360
553
  Second: INT,
361
554
  Minute: INT,
362
555
  Hour: INT,
556
+ // Layout constants
557
+ RFC3339: STRING,
558
+ RFC3339Nano: STRING,
559
+ DateOnly: STRING,
560
+ TimeOnly: STRING,
561
+ DateTime: STRING,
562
+ // Location constants
563
+ UTC: ANY,
564
+ Local: ANY,
565
+ // Month constants
566
+ January: INT,
567
+ February: INT,
568
+ March: INT,
569
+ April: INT,
570
+ May: INT,
571
+ June: INT,
572
+ July: INT,
573
+ August: INT,
574
+ September: INT,
575
+ October: INT,
576
+ November: INT,
577
+ December: INT,
578
+ // Weekday constants
579
+ Sunday: INT,
580
+ Monday: INT,
581
+ Tuesday: INT,
582
+ Wednesday: INT,
583
+ Thursday: INT,
584
+ Friday: INT,
585
+ Saturday: INT,
363
586
  },
364
587
  });
588
+ types.set("time.Time", TIME_T);
365
589
 
366
590
  // unicode package
367
591
  const runeToStr = { kind: "func", params: [INT], returns: [BOOL] };
@@ -455,22 +679,30 @@ export function setupGlobals(globals, types) {
455
679
  // io package — Writer interface + WriteString
456
680
  // io.Writer is typed as ANY so GoFront's built-in writer types (strings.Builder,
457
681
  // bytes.Buffer) are accepted without explicit interface-satisfaction registration.
682
+ const BYTE_SLICE_IO = { kind: "slice", elem: INT };
458
683
  globals.define("io", {
459
684
  kind: "namespace",
460
685
  name: "io",
461
686
  members: {
462
687
  Writer: ANY,
688
+ Reader: ANY,
463
689
  EOF: ERROR,
464
690
  WriteString: {
465
691
  kind: "func",
466
692
  params: [ANY, STRING],
467
693
  returns: [INT, ERROR],
468
694
  },
695
+ ReadAll: {
696
+ kind: "func",
697
+ params: [ANY],
698
+ returns: [BYTE_SLICE_IO, ERROR],
699
+ },
469
700
  Discard: ANY,
470
701
  },
471
702
  });
472
703
  // Register io.Writer as a type alias so it resolves in parameter/field annotations.
473
704
  types.set("io.Writer", ANY);
705
+ types.set("io.Reader", ANY);
474
706
 
475
707
  // gom package — browser-native component built-in
476
708
  const GOM_NODE_T = {
@@ -742,6 +974,77 @@ export function setupGlobals(globals, types) {
742
974
  },
743
975
  });
744
976
 
977
+ // math/rand package — import "math/rand" → local name "rand"
978
+ globals.define("rand", {
979
+ kind: "namespace",
980
+ name: "rand",
981
+ members: {
982
+ Intn: { kind: "func", params: [INT], returns: [INT] },
983
+ Float64: { kind: "func", params: [], returns: [FLOAT64] },
984
+ Float32: { kind: "func", params: [], returns: [FLOAT64] },
985
+ Int: { kind: "func", params: [], returns: [INT] },
986
+ Int63: { kind: "func", params: [], returns: [INT] },
987
+ Int63n: { kind: "func", params: [INT], returns: [INT] },
988
+ Int31: { kind: "func", params: [], returns: [INT] },
989
+ Int31n: { kind: "func", params: [INT], returns: [INT] },
990
+ Seed: { kind: "func", params: [INT], returns: [VOID] },
991
+ Shuffle: { kind: "func", params: [INT, ANY], returns: [VOID] },
992
+ Perm: {
993
+ kind: "func",
994
+ params: [INT],
995
+ returns: [{ kind: "slice", elem: INT }],
996
+ },
997
+ },
998
+ });
999
+
1000
+ // unicode/utf8 package — import "unicode/utf8" → local name "utf8"
1001
+ globals.define("utf8", {
1002
+ kind: "namespace",
1003
+ name: "utf8",
1004
+ members: {
1005
+ RuneCountInString: { kind: "func", params: [STRING], returns: [INT] },
1006
+ RuneLen: { kind: "func", params: [INT], returns: [INT] },
1007
+ ValidString: { kind: "func", params: [STRING], returns: [BOOL] },
1008
+ ValidRune: { kind: "func", params: [INT], returns: [BOOL] },
1009
+ DecodeRuneInString: {
1010
+ kind: "func",
1011
+ params: [STRING],
1012
+ returns: [INT, INT],
1013
+ },
1014
+ DecodeLastRuneInString: {
1015
+ kind: "func",
1016
+ params: [STRING],
1017
+ returns: [INT, INT],
1018
+ },
1019
+ FullRuneInString: { kind: "func", params: [STRING], returns: [BOOL] },
1020
+ // Constants
1021
+ RuneError: INT,
1022
+ MaxRune: INT,
1023
+ UTFMax: INT,
1024
+ },
1025
+ });
1026
+
1027
+ // path package — import "path" or "path/filepath" → local name "path"
1028
+ globals.define("path", {
1029
+ kind: "namespace",
1030
+ name: "path",
1031
+ members: {
1032
+ Base: { kind: "func", params: [STRING], returns: [STRING] },
1033
+ Dir: { kind: "func", params: [STRING], returns: [STRING] },
1034
+ Ext: { kind: "func", params: [STRING], returns: [STRING] },
1035
+ Join: {
1036
+ kind: "func",
1037
+ params: [STRING],
1038
+ returns: [STRING],
1039
+ variadic: true,
1040
+ },
1041
+ Clean: { kind: "func", params: [STRING], returns: [STRING] },
1042
+ IsAbs: { kind: "func", params: [STRING], returns: [BOOL] },
1043
+ Split: { kind: "func", params: [STRING], returns: [STRING, STRING] },
1044
+ Match: { kind: "func", params: [STRING, STRING], returns: [BOOL, ERROR] },
1045
+ },
1046
+ });
1047
+
745
1048
  // Built-in functions
746
1049
  globals.define("append", { kind: "builtin", name: "append" });
747
1050
  globals.define("len", { kind: "builtin", name: "len" });
@@ -93,14 +93,6 @@ export const BASIC_TYPES = {
93
93
 
94
94
  export const COMPARABLE = { kind: "basic", name: "comparable" };
95
95
 
96
- export function isTypeParam(t) {
97
- return t?.kind === "typeParam";
98
- }
99
-
100
- export function isGeneric(t) {
101
- return t?.kind === "generic";
102
- }
103
-
104
96
  function makeBasicPredicate(...names) {
105
97
  const set = new Set(names);
106
98
  const pred = (t) => {
@@ -137,13 +129,6 @@ export function isNil(t) {
137
129
  export function isVoid(t) {
138
130
  return t?.kind === "basic" && t.name === "void";
139
131
  }
140
- export function isError(t) {
141
- if (!t) return false;
142
- if (t === ERROR) return true;
143
- if (t.kind === "interface" && t.name === "error") return true;
144
- if (t.kind === "named" && isError(t.underlying)) return true;
145
- return false;
146
- }
147
132
  export function isPointer(t) {
148
133
  if (!t) return false;
149
134
  if (t.kind === "pointer") return true;