gofront 0.0.6 → 0.0.7
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 +80 -19
- package/package.json +7 -4
- package/src/codegen/expressions.js +252 -3
- package/src/codegen/statements.js +30 -26
- package/src/codegen.js +68 -5
- package/src/parser.js +0 -10
- package/src/typechecker/expressions.js +38 -9
- package/src/typechecker/stdlib.js +764 -0
- package/src/typechecker/types.js +15 -20
- package/src/typechecker.js +25 -601
package/README.md
CHANGED
|
@@ -184,7 +184,7 @@ The type checker enforces correctness; JavaScript doesn't need to know.
|
|
|
184
184
|
|
|
185
185
|
## Examples — Todo App
|
|
186
186
|
|
|
187
|
-
There are
|
|
187
|
+
There are three example apps — all implement the same todo app to show different aspects
|
|
188
188
|
of GoFront.
|
|
189
189
|
|
|
190
190
|
### Simple (vanilla DOM)
|
|
@@ -202,7 +202,7 @@ example/simple/
|
|
|
202
202
|
styles.go ← injectStyles() creates <style> element with all CSS
|
|
203
203
|
main.go ← createApp() builds DOM shell · setupEvents() · event delegation
|
|
204
204
|
utils/
|
|
205
|
-
utils.go ← Plural() · generic Filter
|
|
205
|
+
utils.go ← Plural() · generic Filter — cross-package import demo
|
|
206
206
|
browser.d.ts ← minimal external type declarations (sleep)
|
|
207
207
|
index.html ← bare HTML shell, loads app.js as ES module
|
|
208
208
|
app.js ← generated output
|
|
@@ -212,41 +212,99 @@ example/simple/
|
|
|
212
212
|
|
|
213
213
|
Same app rebuilt with [reactive.js](https://github.com/seriva/microtastic), a tiny
|
|
214
214
|
signals-based reactive framework. Demonstrates how GoFront integrates with external JS
|
|
215
|
-
libraries via `.d.ts` type declarations — the
|
|
216
|
-
|
|
217
|
-
all described in a hand-written `browser.d.ts` shim.
|
|
215
|
+
libraries via `.d.ts` type declarations — the entire reactive API surface is typed via a
|
|
216
|
+
hand-written `browser.d.ts` shim and exercised from GoFront source.
|
|
218
217
|
|
|
219
218
|
```
|
|
220
219
|
example/reactive/
|
|
221
220
|
src/
|
|
222
221
|
types.go ← Todo, Stats, AppElements structs
|
|
223
|
-
store.go ←
|
|
224
|
-
render.go ←
|
|
225
|
-
styles.go ←
|
|
226
|
-
main.go ←
|
|
222
|
+
store.go ← reactive state: Signals.create/computed/computedAsync/batch/update
|
|
223
|
+
render.go ← Reactive.Component lifecycle · ctx.bind* · setupStatsBar
|
|
224
|
+
styles.go ← per-section cssClass() scoped styles
|
|
225
|
+
main.go ← async boot, Reactive.mount loading placeholder, signal.subscribe/once
|
|
227
226
|
utils/
|
|
228
|
-
utils.go ← Plural() · generic Filter
|
|
229
|
-
browser.d.ts ← Signal
|
|
227
|
+
utils.go ← Plural() · generic Filter
|
|
228
|
+
browser.d.ts ← Signal · ComponentContext · Signals/Reactive namespaces · htmlTag
|
|
230
229
|
reactive.js ← signals framework (from microtastic)
|
|
231
|
-
index.html ← loads reactive.js
|
|
230
|
+
index.html ← loads reactive.js, exposes helpers, imports app.js
|
|
232
231
|
app.js ← generated output
|
|
233
232
|
```
|
|
234
233
|
|
|
234
|
+
The reactive example covers the full reactive.js API surface:
|
|
235
|
+
|
|
236
|
+
| Category | Features used |
|
|
237
|
+
|---|---|
|
|
238
|
+
| HTML helpers | `html` tagged template (via `htmlTag`), `trusted`, `join` |
|
|
239
|
+
| CSS | `cssClass` scoped styles with nested `&` selectors |
|
|
240
|
+
| Signals | `create`, `computed`, `computedAsync`, `batch`, `get`, `peek`, `set`, `update`, `subscribe`, `once` |
|
|
241
|
+
| Reactive namespace | `mount` (loading placeholder), `createComponent`, `scan` |
|
|
242
|
+
| Component context | `bind`, `bindAttr`, `bindBoolAttr`, `bindClass`, `bindText`, `bindStyle`, `bindMultiple`, `computed` |
|
|
243
|
+
| Component lifecycle | `state`, `template`, `styles`, `mount`, `mountTo`, `appendTo`, `refs` (via `data-ref`) |
|
|
244
|
+
| Component instance | `signal`, `effect`, `on` (auto-cleanup event listeners) |
|
|
245
|
+
| Scan attributes | `data-model`, `data-text`, `data-html`, `data-if`, `data-visible`, `data-class-*`, `data-attr-*`, `data-bool-*`, `data-on-*`, `data-ref` |
|
|
246
|
+
|
|
247
|
+
### gom (stdlib component library + todo app)
|
|
248
|
+
|
|
249
|
+
Browser-native declarative DOM components inspired by
|
|
250
|
+
[gomponents](https://www.gomponents.com). `gom` is a **built-in stdlib package** — no
|
|
251
|
+
import path needed, available in every GoFront program just like `fmt` or `strings`. It
|
|
252
|
+
uses **methods on named non-struct types** so that plain functions and slices can satisfy
|
|
253
|
+
the `Node` interface without any struct boilerplate.
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
example/gom/
|
|
257
|
+
src/
|
|
258
|
+
types.go ← Todo struct, filter/priority constants
|
|
259
|
+
store.go ← state, mutations, localStorage persistence
|
|
260
|
+
render.go ← gom node builders (pure node tree, no innerHTML)
|
|
261
|
+
styles.go ← appStyles() CSS string
|
|
262
|
+
main.go ← event setup, submitInput, entry point
|
|
263
|
+
utils/ ← Filter[T], Plural, HasText
|
|
264
|
+
app.js ← generated output
|
|
265
|
+
index.html ← HTML shell
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Key types and functions (all accessed as `gom.*`, no import needed):
|
|
269
|
+
- `gom.Node` — interface with a single `Mount(parent any)` method
|
|
270
|
+
- `gom.NodeFunc` — `type NodeFunc func(parent any)` with `Mount` method; any function becomes a `Node`
|
|
271
|
+
- `gom.Group` — `type Group []Node` with `Mount` method; composes children in order
|
|
272
|
+
- `gom.El(tag, children...)` — creates an element node
|
|
273
|
+
- `gom.Text(s)` — creates a text node
|
|
274
|
+
- `gom.Attr(name, val)` — generic attribute node; named shortcuts: `gom.Class`, `gom.Href`, `gom.Type`, etc.
|
|
275
|
+
- `gom.If(cond, node)` — conditionally renders a node
|
|
276
|
+
- `gom.Map(slice, fn)` — maps a slice to a `Group` of nodes
|
|
277
|
+
- `gom.Style(css)` — returns a `Node` that injects a `<style>` element; styles are part of the node tree
|
|
278
|
+
- `gom.Mount(selector, n)` — mounts a node into the DOM, replacing the element's content
|
|
279
|
+
- `gom.MountTo(selector, n)` — appends a node without clearing; used to inject styles into `<head>`
|
|
280
|
+
- HTML element helpers: `gom.Div`, `gom.Span`, `gom.Button`, `gom.Input`, `gom.Ul`, `gom.Li`, … (full HTML element set)
|
|
281
|
+
|
|
235
282
|
### Features demonstrated
|
|
236
283
|
|
|
237
|
-
|
|
238
|
-
slices, `for range`, `switch`, cross-package imports, multi-file
|
|
239
|
-
`async`/`await`, `defer`/`recover`, localStorage persistence,
|
|
240
|
-
utility functions (`Filter[T]`, `Map[T, U]`).
|
|
284
|
+
The simple and reactive examples cover: structs & methods, iota constants, named return
|
|
285
|
+
values, closures, slices, `for range`, `switch`, cross-package imports, multi-file
|
|
286
|
+
same-package compilation, `async`/`await`, `defer`/`recover`, localStorage persistence,
|
|
287
|
+
DOM APIs, and generic utility functions (`Filter[T]`, `Map[T, U]`).
|
|
241
288
|
|
|
242
289
|
The reactive example additionally demonstrates: external `.d.ts` type imports,
|
|
243
|
-
`declare namespace` patterns for typing JS libraries,
|
|
290
|
+
`declare namespace` patterns for typing JS libraries, the full reactive signal graph
|
|
291
|
+
(source signals → computed signals → async computed → DOM bindings), reactive UI with
|
|
292
|
+
no `querySelector` or `getElementById` in application code, and `Reactive.Component`
|
|
293
|
+
as the primary composition unit (state, template, styles, mount lifecycle, `data-ref`
|
|
294
|
+
element refs, auto-cleanup event listeners).
|
|
295
|
+
|
|
296
|
+
The gom example additionally demonstrates: **the `gom` built-in stdlib package**,
|
|
297
|
+
styles as nodes (`gom.Style` + `gom.MountTo`), `gom.Map` for list rendering, the
|
|
298
|
+
declarative node-tree pattern (no `innerHTML`, no `querySelector`), and full feature
|
|
299
|
+
parity with the other examples (priority mode, validation, localStorage persistence,
|
|
300
|
+
sync status, drag-and-drop reordering).
|
|
244
301
|
|
|
245
302
|
### Build and run
|
|
246
303
|
|
|
247
304
|
```sh
|
|
248
305
|
npm run build:simple # → example/simple/app.js
|
|
249
306
|
npm run build:reactive # → example/reactive/app.js
|
|
307
|
+
npm run build:gom # → example/gom/app.js
|
|
250
308
|
# open the respective index.html in a browser
|
|
251
309
|
```
|
|
252
310
|
|
|
@@ -367,6 +425,7 @@ signatures into GoFront's internal type representation.
|
|
|
367
425
|
| `error` type | ✓ — interface `{ Error() string }`; custom error types, `errors.Is`/`Unwrap`, `%w` wrapping |
|
|
368
426
|
| Complex numbers (`complex64`, `complex128`, `3i`) | ✓ — `complex()`, `real()`, `imag()` builtins; `__cmul`/`__cdiv` helpers |
|
|
369
427
|
| Type definitions, type aliases (`type A = B`) | ✓ |
|
|
428
|
+
| Methods on named non-struct types | ✓ — `type T func(...)` or `type T []E` with methods; emitted as ES6 wrapper classes; satisfies interfaces |
|
|
370
429
|
| Type conversions, type assertions (plain & comma-ok) | ✓ |
|
|
371
430
|
| Type switch (`switch v := x.(type)`) | ✓ — compiles to `if/else if` with `typeof` / `instanceof` |
|
|
372
431
|
| Sized integers (`int8`–`int64`, `uint8`–`uint64`, `float32`) | ✓ — mapped to `number` at runtime |
|
|
@@ -419,6 +478,8 @@ signatures into GoFront's internal type representation.
|
|
|
419
478
|
| `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. |
|
|
420
479
|
| `unicode` | `IsLetter`, `IsDigit`, `IsSpace`, `IsUpper`, `IsLower`, `IsPunct`, `IsControl`, `IsPrint`, `IsGraphic`, `ToUpper`, `ToLower` |
|
|
421
480
|
| `os` | `Exit`, `Args`, `Getenv` |
|
|
481
|
+
| `io` | `Writer`, `Reader`, `ReadWriter`, `Closer` interface types — used for accepting `*strings.Builder`, `*bytes.Buffer`, or any compatible writer/reader |
|
|
482
|
+
| `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`, …) |
|
|
422
483
|
|
|
423
484
|
### Packages & imports
|
|
424
485
|
|
|
@@ -490,7 +551,7 @@ you know exactly what to expect.
|
|
|
490
551
|
|
|
491
552
|
See [`docs/ROADMAP.md`](docs/ROADMAP.md) for the full roadmap and release history.
|
|
492
553
|
Design documents for planned features are organised by release under `docs/v*/`
|
|
493
|
-
(e.g. [`docs/v0.0.
|
|
554
|
+
(e.g. [`docs/v0.0.8/`](docs/v0.0.8/), [`docs/v0.0.9/`](docs/v0.0.9/)).
|
|
494
555
|
|
|
495
556
|
---
|
|
496
557
|
|
|
@@ -500,7 +561,7 @@ Design documents for planned features are organised by release under `docs/v*/`
|
|
|
500
561
|
npm test
|
|
501
562
|
```
|
|
502
563
|
|
|
503
|
-
|
|
564
|
+
990 tests covering language features, type errors, edge cases, DOM (jsdom), external
|
|
504
565
|
`.d.ts`, npm resolver, multi-file compilation, embedded structs, string formatting, map
|
|
505
566
|
iteration order, integer overflow semantics, unused variable detection, unused import
|
|
506
567
|
detection, semantic difference verification, stdlib shim packages, generics (type params,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gofront",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"author": "Luuk van Venrooij",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -33,12 +33,15 @@
|
|
|
33
33
|
"scripts": {
|
|
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
38
|
"format": "biome format --write .",
|
|
38
|
-
"check": "biome check ."
|
|
39
|
+
"check": "biome check .",
|
|
40
|
+
"prepare": "husky"
|
|
39
41
|
},
|
|
40
42
|
"devDependencies": {
|
|
41
|
-
"@biomejs/biome": "^2.4.
|
|
43
|
+
"@biomejs/biome": "^2.4.12",
|
|
44
|
+
"husky": "^9.1.7",
|
|
42
45
|
"jsdom": "^29.0.2"
|
|
43
46
|
}
|
|
44
47
|
}
|
|
@@ -147,6 +147,15 @@ export const expressionGenMethods = {
|
|
|
147
147
|
return "Number.MIN_SAFE_INTEGER";
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
|
+
// io constants
|
|
151
|
+
if (expr.expr.kind === "Ident" && expr.expr.name === "io") {
|
|
152
|
+
switch (expr.field) {
|
|
153
|
+
case "EOF":
|
|
154
|
+
return '"EOF"';
|
|
155
|
+
case "Discard":
|
|
156
|
+
return "{ WriteString(s) { return s.length; }, Write(b) { return b.length; } }";
|
|
157
|
+
}
|
|
158
|
+
}
|
|
150
159
|
// os constants/vars
|
|
151
160
|
if (expr.expr.kind === "Ident" && expr.expr.name === "os") {
|
|
152
161
|
switch (expr.field) {
|
|
@@ -184,7 +193,11 @@ export const expressionGenMethods = {
|
|
|
184
193
|
}
|
|
185
194
|
|
|
186
195
|
case "IndexExpr": {
|
|
187
|
-
const
|
|
196
|
+
const exprType = expr.expr._type;
|
|
197
|
+
const wrapField = this._namedWrapperField(exprType, expr.expr);
|
|
198
|
+
const base = wrapField
|
|
199
|
+
? `${this.genExpr(expr.expr)}.${wrapField}`
|
|
200
|
+
: this.genExpr(expr.expr);
|
|
188
201
|
const idx = this.genExpr(expr.index);
|
|
189
202
|
if (expr._mapValueType && !expr._lvalue) {
|
|
190
203
|
const zero = this.zeroValueForType(expr._mapValueType);
|
|
@@ -196,7 +209,6 @@ export const expressionGenMethods = {
|
|
|
196
209
|
return `(${base}[${idx}] ?? ${zero})`;
|
|
197
210
|
}
|
|
198
211
|
// String indexing returns a byte (charCodeAt), not a JS character
|
|
199
|
-
const exprType = expr.expr._type;
|
|
200
212
|
const isStr =
|
|
201
213
|
(exprType?.kind === "basic" && exprType.name === "string") ||
|
|
202
214
|
(exprType?.kind === "untyped" && exprType.base === "string");
|
|
@@ -212,6 +224,7 @@ export const expressionGenMethods = {
|
|
|
212
224
|
const hi = expr.high ? this.genExpr(expr.high) : "";
|
|
213
225
|
if (!lo && !hi) return `${base}.slice()`;
|
|
214
226
|
if (!hi) return `${base}.slice(${lo})`;
|
|
227
|
+
if (!lo) return `${base}.slice(0, ${hi})`;
|
|
215
228
|
return `${base}.slice(${lo}, ${hi})`;
|
|
216
229
|
}
|
|
217
230
|
|
|
@@ -284,6 +297,10 @@ export const expressionGenMethods = {
|
|
|
284
297
|
this._usesError = true;
|
|
285
298
|
return `__error(${inner})`;
|
|
286
299
|
}
|
|
300
|
+
// Named wrapper type: NodeFunc(fn) → new NodeFunc(fn)
|
|
301
|
+
if (target && this.namedWrapperNames.has(target)) {
|
|
302
|
+
return `new ${target}(${inner})`;
|
|
303
|
+
}
|
|
287
304
|
switch (target) {
|
|
288
305
|
case "string": {
|
|
289
306
|
// Go string(65) → "A" (Unicode code point), not "65"
|
|
@@ -339,6 +356,16 @@ export const expressionGenMethods = {
|
|
|
339
356
|
},
|
|
340
357
|
|
|
341
358
|
genCall(expr) {
|
|
359
|
+
// Named type constructor call: Greeter(fn), NodeFunc(fn) → new Greeter(fn)
|
|
360
|
+
if (expr._isTypeConversion) {
|
|
361
|
+
const name = expr._conversionTargetType?.name;
|
|
362
|
+
const arg = this.genExpr(expr.args[0]);
|
|
363
|
+
if (name && this.namedWrapperNames.has(name)) {
|
|
364
|
+
return `new ${name}(${arg})`;
|
|
365
|
+
}
|
|
366
|
+
return arg; // non-wrapper named types: identity
|
|
367
|
+
}
|
|
368
|
+
|
|
342
369
|
// Unwrap InstantiationExpr for function calls: Foo[int](42) → Foo(42)
|
|
343
370
|
const funcExpr =
|
|
344
371
|
expr.func.kind === "InstantiationExpr" ? expr.func.expr : expr.func;
|
|
@@ -351,6 +378,10 @@ export const expressionGenMethods = {
|
|
|
351
378
|
if (expr._constLen != null) return String(expr._constLen);
|
|
352
379
|
const arg = expr.args[0];
|
|
353
380
|
const t = arg?._type;
|
|
381
|
+
const wrapField = this._namedWrapperField(t, arg);
|
|
382
|
+
if (wrapField) {
|
|
383
|
+
return `${this.genExpr(arg)}.${wrapField}.length`;
|
|
384
|
+
}
|
|
354
385
|
const js = this.genExpr(arg);
|
|
355
386
|
if (t?.kind === "map") return `Object.keys(${js}).length`;
|
|
356
387
|
this._usesLen = true;
|
|
@@ -472,6 +503,18 @@ export const expressionGenMethods = {
|
|
|
472
503
|
},
|
|
473
504
|
|
|
474
505
|
genAppend(expr) {
|
|
506
|
+
const retType = expr._type;
|
|
507
|
+
const wrapField = this._namedWrapperField(retType, expr.args[0]);
|
|
508
|
+
if (wrapField) {
|
|
509
|
+
// Named slice wrapper: append(g, x) → new G(__append(g._items, x))
|
|
510
|
+
const sliceJS = `${this.genExpr(expr.args[0])}.${wrapField}`;
|
|
511
|
+
const elems = expr.args
|
|
512
|
+
.slice(1)
|
|
513
|
+
.map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genExpr(a)));
|
|
514
|
+
if (elems.length === 0) return this.genExpr(expr.args[0]);
|
|
515
|
+
this._usesAppend = true;
|
|
516
|
+
return `new ${retType.name}(__append(${sliceJS}, ${elems.join(", ")}))`;
|
|
517
|
+
}
|
|
475
518
|
const slice = this.genExpr(expr.args[0]);
|
|
476
519
|
const elems = expr.args
|
|
477
520
|
.slice(1)
|
|
@@ -556,6 +599,34 @@ export const expressionGenMethods = {
|
|
|
556
599
|
return `new ${typeName}({ ${this._genStructFields(expr.elems)} })`;
|
|
557
600
|
}
|
|
558
601
|
|
|
602
|
+
// Named wrapper type: Group{a, b} → new Group([a, b])
|
|
603
|
+
if (typeName && this.namedWrapperNames.has(typeName)) {
|
|
604
|
+
const namedType = this.checker?.types.get(typeName);
|
|
605
|
+
const u = namedType?.underlying;
|
|
606
|
+
if (u?.kind === "map") {
|
|
607
|
+
const entries = expr.elems
|
|
608
|
+
.map((e) => {
|
|
609
|
+
if (e.kind === "KeyValueExpr") {
|
|
610
|
+
const k =
|
|
611
|
+
e.key.litKind === "STRING"
|
|
612
|
+
? JSON.stringify(e.key.value)
|
|
613
|
+
: `[${this.genExpr(e.key)}]`;
|
|
614
|
+
return `${k}: ${this.genExpr(e.value)}`;
|
|
615
|
+
}
|
|
616
|
+
return this.genExpr(e);
|
|
617
|
+
})
|
|
618
|
+
.join(", ");
|
|
619
|
+
return `new ${typeName}({ ${entries} })`;
|
|
620
|
+
}
|
|
621
|
+
// Default: slice
|
|
622
|
+
const elems = expr.elems
|
|
623
|
+
.map((e) =>
|
|
624
|
+
e.kind === "KeyValueExpr" ? this.genExpr(e.value) : this.genExpr(e),
|
|
625
|
+
)
|
|
626
|
+
.join(", ");
|
|
627
|
+
return `new ${typeName}([${elems}])`;
|
|
628
|
+
}
|
|
629
|
+
|
|
559
630
|
// Fallback: named type that's not a struct but has positional elements → treat as struct
|
|
560
631
|
if (expr.elems.some((e) => e._positionalField)) {
|
|
561
632
|
if (typeName) {
|
|
@@ -617,7 +688,8 @@ export const expressionGenMethods = {
|
|
|
617
688
|
if (typeNode.kind === "Ident") return typeNode.name;
|
|
618
689
|
if (typeNode.kind === "InstantiationExpr")
|
|
619
690
|
return this.getTypeName(typeNode.expr);
|
|
620
|
-
if (typeNode.kind === "SelectorExpr")
|
|
691
|
+
if (typeNode.kind === "SelectorExpr")
|
|
692
|
+
return `${this.getTypeName(typeNode.expr)}.${typeNode.field}`;
|
|
621
693
|
return null;
|
|
622
694
|
},
|
|
623
695
|
|
|
@@ -1002,6 +1074,32 @@ export const expressionGenMethods = {
|
|
|
1002
1074
|
return undefined;
|
|
1003
1075
|
},
|
|
1004
1076
|
|
|
1077
|
+
_genIo(fn, _a, expr) {
|
|
1078
|
+
switch (fn) {
|
|
1079
|
+
case "WriteString": {
|
|
1080
|
+
const writerArg = expr.args[0];
|
|
1081
|
+
const writerType = writerArg._type;
|
|
1082
|
+
const typeName =
|
|
1083
|
+
writerType?.name ??
|
|
1084
|
+
(writerType?.kind === "pointer" ? writerType.base?.name : null);
|
|
1085
|
+
const w = this.genExpr(writerArg);
|
|
1086
|
+
const s = this.genExpr(expr.args[1]);
|
|
1087
|
+
const isPtr = writerType?.kind === "pointer";
|
|
1088
|
+
const base = isPtr ? `${w}.value` : w;
|
|
1089
|
+
if (typeName === "strings.Builder") {
|
|
1090
|
+
return `((b,s) => { b._buf += s; return [s.length, null]; })(${base}, ${s})`;
|
|
1091
|
+
}
|
|
1092
|
+
if (typeName === "bytes.Buffer") {
|
|
1093
|
+
return `((b,s) => { b._buf.push(...new TextEncoder().encode(s)); return [s.length, null]; })(${base}, ${s})`;
|
|
1094
|
+
}
|
|
1095
|
+
// Generic: auto-dereference pointer, then dispatch on concrete writer type.
|
|
1096
|
+
// strings.Builder has { _buf: string }, bytes.Buffer has { _buf: [] }.
|
|
1097
|
+
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
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
return undefined;
|
|
1101
|
+
},
|
|
1102
|
+
|
|
1005
1103
|
_genHtml(fn, a) {
|
|
1006
1104
|
switch (fn) {
|
|
1007
1105
|
case "EscapeString": {
|
|
@@ -1102,11 +1200,162 @@ export const expressionGenMethods = {
|
|
|
1102
1200
|
return this._genMaps(fn, a);
|
|
1103
1201
|
case "html":
|
|
1104
1202
|
return this._genHtml(fn, a);
|
|
1203
|
+
case "io":
|
|
1204
|
+
return this._genIo(fn, a, expr);
|
|
1205
|
+
case "gom":
|
|
1206
|
+
return this._genGom(fn, expr);
|
|
1105
1207
|
default:
|
|
1106
1208
|
return undefined;
|
|
1107
1209
|
}
|
|
1108
1210
|
},
|
|
1109
1211
|
|
|
1212
|
+
_genGom(fn, expr) {
|
|
1213
|
+
const a = () =>
|
|
1214
|
+
expr.args.map((e) =>
|
|
1215
|
+
e._spread ? `...${this.genExpr(e)}` : this.genExpr(e),
|
|
1216
|
+
);
|
|
1217
|
+
const elementTags = {
|
|
1218
|
+
Div: "div",
|
|
1219
|
+
Section: "section",
|
|
1220
|
+
Article: "article",
|
|
1221
|
+
Aside: "aside",
|
|
1222
|
+
Header: "header",
|
|
1223
|
+
Footer: "footer",
|
|
1224
|
+
Main: "main",
|
|
1225
|
+
Nav: "nav",
|
|
1226
|
+
Figure: "figure",
|
|
1227
|
+
H1: "h1",
|
|
1228
|
+
H2: "h2",
|
|
1229
|
+
H3: "h3",
|
|
1230
|
+
H4: "h4",
|
|
1231
|
+
H5: "h5",
|
|
1232
|
+
H6: "h6",
|
|
1233
|
+
Span: "span",
|
|
1234
|
+
A: "a",
|
|
1235
|
+
Strong: "strong",
|
|
1236
|
+
Em: "em",
|
|
1237
|
+
Code: "code",
|
|
1238
|
+
Pre: "pre",
|
|
1239
|
+
Small: "small",
|
|
1240
|
+
Mark: "mark",
|
|
1241
|
+
P: "p",
|
|
1242
|
+
Br: "br",
|
|
1243
|
+
Hr: "hr",
|
|
1244
|
+
Ul: "ul",
|
|
1245
|
+
Ol: "ol",
|
|
1246
|
+
Li: "li",
|
|
1247
|
+
Dl: "dl",
|
|
1248
|
+
Dt: "dt",
|
|
1249
|
+
Dd: "dd",
|
|
1250
|
+
Form: "form",
|
|
1251
|
+
Input: "input",
|
|
1252
|
+
Button: "button",
|
|
1253
|
+
Textarea: "textarea",
|
|
1254
|
+
Select: "select",
|
|
1255
|
+
Option: "option",
|
|
1256
|
+
Label: "label",
|
|
1257
|
+
Fieldset: "fieldset",
|
|
1258
|
+
Legend: "legend",
|
|
1259
|
+
Img: "img",
|
|
1260
|
+
Video: "video",
|
|
1261
|
+
Audio: "audio",
|
|
1262
|
+
Canvas: "canvas",
|
|
1263
|
+
Table: "table",
|
|
1264
|
+
Thead: "thead",
|
|
1265
|
+
Tbody: "tbody",
|
|
1266
|
+
Tfoot: "tfoot",
|
|
1267
|
+
Tr: "tr",
|
|
1268
|
+
Th: "th",
|
|
1269
|
+
Td: "td",
|
|
1270
|
+
};
|
|
1271
|
+
if (elementTags[fn]) {
|
|
1272
|
+
const tag = elementTags[fn];
|
|
1273
|
+
const args = a();
|
|
1274
|
+
if (args.length === 0)
|
|
1275
|
+
return `(()=>({Mount(p){const e=document.createElement("${tag}");p.appendChild(e);}}))()`;
|
|
1276
|
+
return (
|
|
1277
|
+
`((...c)=>({Mount(p){const e=document.createElement("${tag}");c.forEach(n=>n?.Mount?.(e));p.appendChild(e);}}))` +
|
|
1278
|
+
`(${args.join(",")})`
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
const attrHelpers = {
|
|
1282
|
+
For: "for",
|
|
1283
|
+
Name: "name",
|
|
1284
|
+
Value: "value",
|
|
1285
|
+
Target: "target",
|
|
1286
|
+
Rel: "rel",
|
|
1287
|
+
Alt: "alt",
|
|
1288
|
+
Title: "title",
|
|
1289
|
+
Lang: "lang",
|
|
1290
|
+
Action: "action",
|
|
1291
|
+
Method: "method",
|
|
1292
|
+
AutoComplete: "autocomplete",
|
|
1293
|
+
Draggable: "draggable",
|
|
1294
|
+
Role: "role",
|
|
1295
|
+
StyleAttr: "style",
|
|
1296
|
+
};
|
|
1297
|
+
if (attrHelpers[fn]) {
|
|
1298
|
+
const [v] = a();
|
|
1299
|
+
return `((v)=>({Mount(e){e.setAttribute("${attrHelpers[fn]}",v)}}))(${v})`;
|
|
1300
|
+
}
|
|
1301
|
+
if (fn === "AriaLabel") {
|
|
1302
|
+
const [v] = a();
|
|
1303
|
+
return `((v)=>({Mount(e){e.setAttribute("aria-label",v)}}))(${v})`;
|
|
1304
|
+
}
|
|
1305
|
+
const boolAttrs = {
|
|
1306
|
+
Disabled: "disabled",
|
|
1307
|
+
Checked: "checked",
|
|
1308
|
+
Selected: "selected",
|
|
1309
|
+
Readonly: "readonly",
|
|
1310
|
+
};
|
|
1311
|
+
if (boolAttrs[fn])
|
|
1312
|
+
return `({Mount(e){e.setAttribute("${boolAttrs[fn]}","")}})`;
|
|
1313
|
+
|
|
1314
|
+
const args = a();
|
|
1315
|
+
switch (fn) {
|
|
1316
|
+
case "El": {
|
|
1317
|
+
const [tag, ...children] = args;
|
|
1318
|
+
if (children.length === 0)
|
|
1319
|
+
return `((t)=>({Mount(p){const e=document.createElement(t);p.appendChild(e);}})) (${tag})`;
|
|
1320
|
+
return (
|
|
1321
|
+
`((t,...c)=>({Mount(p){const e=document.createElement(t);c.forEach(n=>n?.Mount?.(e));p.appendChild(e);}}))` +
|
|
1322
|
+
`(${tag},${children.join(",")})`
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1325
|
+
case "Text": {
|
|
1326
|
+
const [s] = args;
|
|
1327
|
+
return `((s)=>({Mount(p){p.appendChild(document.createTextNode(s))}}))(${s})`;
|
|
1328
|
+
}
|
|
1329
|
+
case "Attr": {
|
|
1330
|
+
const [name, value] = args;
|
|
1331
|
+
return `((n,v)=>({Mount(e){e.setAttribute(n,v)}}))(${name},${value})`;
|
|
1332
|
+
}
|
|
1333
|
+
case "Class":
|
|
1334
|
+
return `((v)=>({Mount(e){e.className=v}}))(${args[0]})`;
|
|
1335
|
+
case "Type":
|
|
1336
|
+
return `((v)=>({Mount(e){e.type=v}}))(${args[0]})`;
|
|
1337
|
+
case "Href":
|
|
1338
|
+
return `((v)=>({Mount(e){e.href=v}}))(${args[0]})`;
|
|
1339
|
+
case "Src":
|
|
1340
|
+
return `((v)=>({Mount(e){e.src=v}}))(${args[0]})`;
|
|
1341
|
+
case "Placeholder":
|
|
1342
|
+
return `((v)=>({Mount(e){e.placeholder=v}}))(${args[0]})`;
|
|
1343
|
+
case "DataAttr":
|
|
1344
|
+
return `((k,v)=>({Mount(e){e.setAttribute("data-"+k,v)}}))(${args[0]},${args[1]})`;
|
|
1345
|
+
case "If":
|
|
1346
|
+
return `((c,n)=>c?n:{Mount(){}})(${args[0]},${args[1]})`;
|
|
1347
|
+
case "Map":
|
|
1348
|
+
return `((s,f)=>{const c=s.map(f);return{_items:c,Mount(p){c.forEach(n=>n.Mount(p))}}})(${args[0]},${args[1]})`;
|
|
1349
|
+
case "Style":
|
|
1350
|
+
return `((s)=>({Mount(p){const e=document.createElement("style");e.textContent=s;p.appendChild(e);}})) (${args[0]})`;
|
|
1351
|
+
case "Mount":
|
|
1352
|
+
return `((sel,n)=>{const e=document.querySelector(sel);e.innerHTML="";n.Mount(e)})(${args[0]},${args[1]})`;
|
|
1353
|
+
case "MountTo":
|
|
1354
|
+
return `((sel,n)=>{const e=document.querySelector(sel);n.Mount(e)})(${args[0]},${args[1]})`;
|
|
1355
|
+
}
|
|
1356
|
+
return undefined;
|
|
1357
|
+
},
|
|
1358
|
+
|
|
1110
1359
|
_genFmt(fn, _a, expr) {
|
|
1111
1360
|
const fmtArgs = expr.args.map((e) => this.genExpr(e)).join(", ");
|
|
1112
1361
|
switch (fn) {
|
|
@@ -7,6 +7,30 @@ export const statementGenMethods = {
|
|
|
7
7
|
for (const stmt of block.stmts) this.genStmt(stmt);
|
|
8
8
|
},
|
|
9
9
|
|
|
10
|
+
// Shared helper for comma-ok map index in both DefineStmt and AssignStmt.
|
|
11
|
+
// isDefine=true → emit `let v = m[k]; let ok = k in m;`
|
|
12
|
+
// isDefine=false → emit `v = k in m ? m[k] : zero; ok = k in m;`
|
|
13
|
+
_genCommaOkMapIndex(stmt, rhsNode, isDefine) {
|
|
14
|
+
const mapExpr = this.genExpr(rhsNode.expr);
|
|
15
|
+
const keyExpr = this.genExpr(rhsNode.index);
|
|
16
|
+
const [vName, okName] = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
17
|
+
const decl = isDefine ? "let " : "";
|
|
18
|
+
if (vName !== "_") {
|
|
19
|
+
if (isDefine) {
|
|
20
|
+
this.line(`let ${vName} = ${mapExpr}[${keyExpr}];`);
|
|
21
|
+
} else {
|
|
22
|
+
const zero = rhsNode._mapValueType
|
|
23
|
+
? this.zeroValueForType(rhsNode._mapValueType)
|
|
24
|
+
: "undefined";
|
|
25
|
+
this.line(
|
|
26
|
+
`${vName} = (${keyExpr}) in ${mapExpr} ? ${mapExpr}[${keyExpr}] : ${zero};`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (okName !== "_")
|
|
31
|
+
this.line(`${decl}${okName} = (${keyExpr}) in ${mapExpr};`);
|
|
32
|
+
},
|
|
33
|
+
|
|
10
34
|
genStmt(stmt) {
|
|
11
35
|
// Record source mapping for the first line this statement produces
|
|
12
36
|
const srcLine = stmt._line ?? null;
|
|
@@ -31,15 +55,7 @@ export const statementGenMethods = {
|
|
|
31
55
|
rhsNode?.kind === "IndexExpr" &&
|
|
32
56
|
rhsNode.expr?._type?.kind === "map"
|
|
33
57
|
) {
|
|
34
|
-
|
|
35
|
-
const keyExpr = this.genExpr(rhsNode.index);
|
|
36
|
-
const [vName, okName] = stmt.lhs.map(
|
|
37
|
-
(e) => e.name ?? this.genExpr(e),
|
|
38
|
-
);
|
|
39
|
-
if (vName !== "_")
|
|
40
|
-
this.line(`let ${vName} = ${mapExpr}[${keyExpr}];`);
|
|
41
|
-
if (okName !== "_")
|
|
42
|
-
this.line(`let ${okName} = (${keyExpr}) in ${mapExpr};`);
|
|
58
|
+
this._genCommaOkMapIndex(stmt, rhsNode, true);
|
|
43
59
|
break;
|
|
44
60
|
}
|
|
45
61
|
const rhs = stmt.rhs.map((e) => this.genExpr(e));
|
|
@@ -117,22 +133,7 @@ export const statementGenMethods = {
|
|
|
117
133
|
}
|
|
118
134
|
// comma-ok map index: v, ok = m["key"]
|
|
119
135
|
if (stmt._commaOkMap) {
|
|
120
|
-
|
|
121
|
-
const mapExpr = this.genExpr(rhsNode.expr);
|
|
122
|
-
const keyExpr = this.genExpr(rhsNode.index);
|
|
123
|
-
const [vName, okName] = stmt.lhs.map(
|
|
124
|
-
(e) => e.name ?? this.genExpr(e),
|
|
125
|
-
);
|
|
126
|
-
if (vName !== "_") {
|
|
127
|
-
const zero = rhsNode._mapValueType
|
|
128
|
-
? this.zeroValueForType(rhsNode._mapValueType)
|
|
129
|
-
: "undefined";
|
|
130
|
-
this.line(
|
|
131
|
-
`${vName} = (${keyExpr}) in ${mapExpr} ? ${mapExpr}[${keyExpr}] : ${zero};`,
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
if (okName !== "_")
|
|
135
|
-
this.line(`${okName} = (${keyExpr}) in ${mapExpr};`);
|
|
136
|
+
this._genCommaOkMapIndex(stmt, stmt.rhs[0], false);
|
|
136
137
|
break;
|
|
137
138
|
}
|
|
138
139
|
// comma-ok type assertion: v, ok = x.(T)
|
|
@@ -383,8 +384,11 @@ export const statementGenMethods = {
|
|
|
383
384
|
const init = stmt.init;
|
|
384
385
|
const range = init.rhs[0];
|
|
385
386
|
const lhs = init.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
386
|
-
const iteree = this.genExpr(range.expr);
|
|
387
387
|
const iterType = range.expr._type;
|
|
388
|
+
const wrapField = this._namedWrapperField(iterType, range.expr);
|
|
389
|
+
const iteree = wrapField
|
|
390
|
+
? `${this.genExpr(range.expr)}.${wrapField}`
|
|
391
|
+
: this.genExpr(range.expr);
|
|
388
392
|
|
|
389
393
|
let iterExpr;
|
|
390
394
|
if (
|