gofront 1.3.4 → 1.3.6
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/package.json +1 -1
- package/src/cli-core.js +118 -13
- package/src/codegen/expressions.js +408 -89
- package/src/codegen/index.js +213 -29
- package/src/codegen/runtime.js +22 -1
- package/src/codegen/statements.js +170 -45
- package/src/codegen/stdlib/builder.js +7 -2
- package/src/codegen/stdlib/fmt.js +2 -2
- package/src/codegen/stdlib/io.js +1 -1
- package/src/dev-server.js +22 -10
- package/src/index.js +8 -2
- package/src/parser/declarations.js +2 -1
- package/src/pwa.js +3 -1
- package/src/typechecker/assignability.js +50 -4
- package/src/typechecker/expressions.js +13 -1
- package/src/typechecker/index.js +71 -31
- package/src/typechecker/resolve.js +2 -0
- package/src/typechecker/statements.js +10 -1
- package/src/typechecker/stdlib/web.js +54 -26
- package/src/typechecker/types.js +3 -2
- package/src/vendor.js +12 -7
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// CodeGen statement methods — installed as a mixin on CodeGen.prototype.
|
|
2
2
|
|
|
3
|
-
import { isComplex } from "../typechecker/types.js";
|
|
3
|
+
import { isComplex, isNumeric } from "../typechecker/types.js";
|
|
4
4
|
|
|
5
5
|
/** @typedef {import('./index.js').CodeGen} CodeGen */
|
|
6
6
|
|
|
@@ -122,14 +122,20 @@ export const statementGenMethods = {
|
|
|
122
122
|
|
|
123
123
|
_genDefineMixedRedecl(lhs, rhs, redecls) {
|
|
124
124
|
if (rhs.length === 1 && lhs.length > 1) {
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
126
|
+
const tmp = `__t${this._tmpCounter}`;
|
|
127
|
+
this.line(`const ${tmp} = ${rhs[0]};`);
|
|
127
128
|
for (let i = 0; i < lhs.length; i++) {
|
|
129
|
+
if (lhs[i] === "_") continue;
|
|
128
130
|
const prefix = redecls[i] ? "" : "let ";
|
|
129
131
|
this.line(`${prefix}${lhs[i]} = ${tmp}[${i}];`);
|
|
130
132
|
}
|
|
131
133
|
} else {
|
|
132
134
|
for (let i = 0; i < lhs.length; i++) {
|
|
135
|
+
if (lhs[i] === "_") {
|
|
136
|
+
this.line(`${rhs[i]};`);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
133
139
|
const prefix = redecls[i] ? "" : "let ";
|
|
134
140
|
this.line(`${prefix}${lhs[i]} = ${rhs[i]};`);
|
|
135
141
|
}
|
|
@@ -142,7 +148,13 @@ export const statementGenMethods = {
|
|
|
142
148
|
this._genCommaOkMapIndex(stmt, rhsNode, true);
|
|
143
149
|
return;
|
|
144
150
|
}
|
|
145
|
-
const
|
|
151
|
+
const paired = stmt.rhs.length === stmt.lhs.length;
|
|
152
|
+
const rhs = stmt.rhs.map((e, i) => {
|
|
153
|
+
const l = paired ? stmt.lhs[i] : null;
|
|
154
|
+
if (!l || l.name === "_") return this.genExpr(e);
|
|
155
|
+
if (l._redecl) return this.genValueExpr(e);
|
|
156
|
+
return this._genDeclValue(l.name, e);
|
|
157
|
+
});
|
|
146
158
|
const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
147
159
|
const redecls = stmt.lhs.map((e) => !!e._redecl);
|
|
148
160
|
const anyRedecl = redecls.some((r) => r);
|
|
@@ -150,7 +162,8 @@ export const statementGenMethods = {
|
|
|
150
162
|
this._genDefineSingleVar(lhs, rhs, redecls);
|
|
151
163
|
} else if (!anyRedecl) {
|
|
152
164
|
const rhsStr = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
|
|
153
|
-
|
|
165
|
+
const names = lhs.map((n) => (n === "_" ? "" : n));
|
|
166
|
+
this.line(`let [${names.join(", ")}] = ${rhsStr};`);
|
|
154
167
|
} else {
|
|
155
168
|
this._genDefineMixedRedecl(lhs, rhs, redecls);
|
|
156
169
|
}
|
|
@@ -218,7 +231,12 @@ export const statementGenMethods = {
|
|
|
218
231
|
this._genStructDerefAssign(stmt);
|
|
219
232
|
return;
|
|
220
233
|
}
|
|
221
|
-
const
|
|
234
|
+
const paired = stmt.rhs.length === stmt.lhs.length && stmt.op === "=";
|
|
235
|
+
const rhs = stmt.rhs.map((e, i) =>
|
|
236
|
+
paired && stmt.lhs[i].name !== "_"
|
|
237
|
+
? this.genValueExpr(e)
|
|
238
|
+
: this.genExpr(e),
|
|
239
|
+
);
|
|
222
240
|
this._markIndexLvalues(stmt.lhs);
|
|
223
241
|
const lhs = stmt.lhs.map((e) => this._genAssignLhsExpr(e));
|
|
224
242
|
const pairs = lhs.map((l, i) => ({ l, r: rhs[i] ?? rhs[0] }));
|
|
@@ -243,7 +261,14 @@ export const statementGenMethods = {
|
|
|
243
261
|
|
|
244
262
|
_genStructDerefAssign(stmt) {
|
|
245
263
|
const target = this.genExpr(stmt.lhs[0].operand);
|
|
246
|
-
const
|
|
264
|
+
const rhsNode = stmt.rhs[0];
|
|
265
|
+
const fields = this._aggregateBase(rhsNode._type)?.fields;
|
|
266
|
+
const shallowOk =
|
|
267
|
+
fields instanceof Map &&
|
|
268
|
+
![...fields.values()].some((t) => this._aggregateBase(t));
|
|
269
|
+
const source = shallowOk
|
|
270
|
+
? this.genExpr(rhsNode)
|
|
271
|
+
: this.genValueExpr(rhsNode);
|
|
247
272
|
this.line(`Object.assign(${target}, ${source});`);
|
|
248
273
|
},
|
|
249
274
|
|
|
@@ -293,7 +318,7 @@ export const statementGenMethods = {
|
|
|
293
318
|
}
|
|
294
319
|
const vals =
|
|
295
320
|
stmt.values.length > 0
|
|
296
|
-
? stmt.values.map((v) => this.
|
|
321
|
+
? stmt.values.map((v) => this._genReturnValue(v))
|
|
297
322
|
: (this.namedReturnVars ?? []);
|
|
298
323
|
const stored = vals.length === 1 ? vals[0] : `[${vals.join(", ")}]`;
|
|
299
324
|
this.line(
|
|
@@ -318,10 +343,10 @@ export const statementGenMethods = {
|
|
|
318
343
|
this.line("return;");
|
|
319
344
|
}
|
|
320
345
|
} else if (stmt.values.length === 1) {
|
|
321
|
-
this.line(`return ${this.
|
|
346
|
+
this.line(`return ${this._genReturnValue(stmt.values[0])};`);
|
|
322
347
|
} else {
|
|
323
348
|
this.line(
|
|
324
|
-
`return [${stmt.values.map((v) => this.
|
|
349
|
+
`return [${stmt.values.map((v) => this._genReturnValue(v)).join(", ")}];`,
|
|
325
350
|
);
|
|
326
351
|
}
|
|
327
352
|
},
|
|
@@ -425,12 +450,7 @@ export const statementGenMethods = {
|
|
|
425
450
|
},
|
|
426
451
|
|
|
427
452
|
_isIntRangeType(iterType) {
|
|
428
|
-
return (
|
|
429
|
-
(iterType?.kind === "basic" &&
|
|
430
|
-
(iterType.name === "int" || iterType.name === "float64")) ||
|
|
431
|
-
(iterType?.kind === "untyped" &&
|
|
432
|
-
(iterType.base === "int" || iterType.base === "float64"))
|
|
433
|
-
);
|
|
453
|
+
return isNumeric(iterType);
|
|
434
454
|
},
|
|
435
455
|
|
|
436
456
|
_isMapRangeType(iterType) {
|
|
@@ -447,47 +467,152 @@ export const statementGenMethods = {
|
|
|
447
467
|
);
|
|
448
468
|
},
|
|
449
469
|
|
|
450
|
-
|
|
470
|
+
_rangeVarTarget(e, isAssign) {
|
|
471
|
+
if (e.kind === "Ident") {
|
|
472
|
+
if (e.name === "_") return null;
|
|
473
|
+
const boxed = this._boxedVars.has(e.name);
|
|
474
|
+
if (isAssign) return boxed ? `${e.name}.value = ` : `${e.name} = `;
|
|
475
|
+
return boxed ? { name: e.name, boxed } : { name: e.name };
|
|
476
|
+
}
|
|
477
|
+
this._markIndexLvalues([e]);
|
|
478
|
+
return `${this._genAssignLhsExpr(e)} = `;
|
|
479
|
+
},
|
|
480
|
+
|
|
481
|
+
_emitRangeVar(e, valueJs, isAssign) {
|
|
482
|
+
if (!e) return;
|
|
483
|
+
const target = this._rangeVarTarget(e, isAssign);
|
|
484
|
+
if (target === null) return;
|
|
485
|
+
if (typeof target === "string") {
|
|
486
|
+
this.line(`${target}${valueJs};`);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
const v = target.boxed ? `{ value: ${valueJs} }` : valueJs;
|
|
490
|
+
this.line(`let ${target.name} = ${v};`);
|
|
491
|
+
},
|
|
492
|
+
|
|
493
|
+
// True when the body re-declares one of `names` at its top level (legal Go shadowing).
|
|
494
|
+
_bodyRedeclares(body, names) {
|
|
495
|
+
if (names.length === 0) return false;
|
|
496
|
+
for (const s of body.stmts ?? []) {
|
|
497
|
+
if (s.kind === "DefineStmt" && s.lhs.some((e) => names.includes(e.name)))
|
|
498
|
+
return true;
|
|
499
|
+
if (
|
|
500
|
+
(s.kind === "VarDecl" || s.kind === "ConstDecl") &&
|
|
501
|
+
s.decls?.some((d) => d.names?.some((n) => names.includes(n)))
|
|
502
|
+
)
|
|
503
|
+
return true;
|
|
504
|
+
}
|
|
505
|
+
return false;
|
|
506
|
+
},
|
|
507
|
+
|
|
508
|
+
// Range value variable: aliases the element unless the body writes the variable or the collection.
|
|
509
|
+
_rangeElemValue(stmt, valNode, js, isAssign) {
|
|
510
|
+
const iterNode = stmt.init.rhs[0].expr;
|
|
511
|
+
const iterType = iterNode._type;
|
|
512
|
+
const base = iterType?.kind === "named" ? iterType.underlying : iterType;
|
|
513
|
+
const elemType = base?.elem;
|
|
514
|
+
if (!valNode || !this._aggregateBase(elemType)) return js;
|
|
515
|
+
if (isAssign || valNode.kind !== "Ident")
|
|
516
|
+
return this._cloneJs(elemType, js);
|
|
517
|
+
if (valNode.name === "_") return js;
|
|
518
|
+
const root = this._rootIdentName(iterNode);
|
|
519
|
+
const mutated =
|
|
520
|
+
this._fnMutates(valNode.name) ||
|
|
521
|
+
(root !== null && this._nodeMutatesVar(stmt.body, root));
|
|
522
|
+
this._markOwnership(valNode.name, mutated);
|
|
523
|
+
return mutated ? this._cloneJs(elemType, js) : js;
|
|
524
|
+
},
|
|
525
|
+
|
|
526
|
+
_genSliceRangeFor(stmt, iteree, isAssign) {
|
|
527
|
+
this._loopDepth = this._loopDepth ?? 0;
|
|
528
|
+
const d = this._loopDepth++;
|
|
529
|
+
const [idxNode, valNode] = stmt.init.lhs;
|
|
530
|
+
const i = `__i${d}`;
|
|
451
531
|
const arr = `__arr${d}`;
|
|
452
532
|
const len = `__len${d}`;
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
this.
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
533
|
+
this.line(
|
|
534
|
+
`for (let ${i} = 0, ${arr} = ${iteree}, ${len} = ${arr} ? ${arr}.length : 0; ${i} < ${len}; ${i}++) {`,
|
|
535
|
+
);
|
|
536
|
+
this.indented(() => {
|
|
537
|
+
this._emitRangeVar(idxNode, i, isAssign);
|
|
538
|
+
this._emitRangeVar(
|
|
539
|
+
valNode,
|
|
540
|
+
this._rangeElemValue(stmt, valNode, `${arr}[${i}]`, isAssign),
|
|
541
|
+
isAssign,
|
|
462
542
|
);
|
|
463
|
-
|
|
543
|
+
const declared = isAssign
|
|
544
|
+
? []
|
|
545
|
+
: [idxNode, valNode]
|
|
546
|
+
.filter((e) => e?.kind === "Ident" && e.name !== "_")
|
|
547
|
+
.map((e) => e.name);
|
|
548
|
+
if (this._bodyRedeclares(stmt.body, declared)) {
|
|
549
|
+
this.line("{");
|
|
550
|
+
this.indented(() => this.genBlock(stmt.body));
|
|
551
|
+
this.line("}");
|
|
552
|
+
} else {
|
|
553
|
+
this.genBlock(stmt.body);
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
this.line("}");
|
|
557
|
+
this._loopDepth--;
|
|
464
558
|
},
|
|
465
559
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
this.
|
|
560
|
+
// Whether `for i := range n` can be emitted as a plain `for (let i = 0; …)` without hidden registers.
|
|
561
|
+
_isSimpleIntRange(stmt, name, isAssign) {
|
|
562
|
+
if (isAssign) return false;
|
|
563
|
+
if (!name || name === "_") return true;
|
|
564
|
+
return !this._boxedVars.has(name) && !this._nodeAssigns(stmt.body, name);
|
|
471
565
|
},
|
|
472
566
|
|
|
473
|
-
|
|
567
|
+
_genIntRangeBody(stmt, name, isAssign) {
|
|
568
|
+
const wrap = !isAssign && name && this._bodyRedeclares(stmt.body, [name]);
|
|
569
|
+
if (!wrap) return this.genBlock(stmt.body);
|
|
570
|
+
this.line("{");
|
|
571
|
+
this.indented(() => this.genBlock(stmt.body));
|
|
572
|
+
this.line("}");
|
|
573
|
+
},
|
|
574
|
+
|
|
575
|
+
_genIntRangeFor(stmt, iteree, isAssign) {
|
|
576
|
+
const varNode = stmt.init.lhs[0];
|
|
577
|
+
const name = varNode?.kind === "Ident" ? varNode.name : null;
|
|
578
|
+
if (this._isSimpleIntRange(stmt, name, isAssign)) {
|
|
579
|
+
const v = !name || name === "_" ? "_$" : name;
|
|
580
|
+
this.line(`for (let ${v} = 0; ${v} < ${iteree}; ${v}++) {`);
|
|
581
|
+
this.indented(() => this.genBlock(stmt.body));
|
|
582
|
+
this.line("}");
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
474
585
|
this._loopDepth = this._loopDepth ?? 0;
|
|
475
|
-
const d = this._loopDepth
|
|
476
|
-
|
|
477
|
-
|
|
586
|
+
const d = this._loopDepth++;
|
|
587
|
+
const i = `__i${d}`;
|
|
588
|
+
const n = `__n${d}`;
|
|
589
|
+
this.line(`for (let ${i} = 0, ${n} = ${iteree}; ${i} < ${n}; ${i}++) {`);
|
|
478
590
|
this.indented(() => {
|
|
479
|
-
this.
|
|
480
|
-
this.
|
|
591
|
+
this._emitRangeVar(varNode, i, isAssign);
|
|
592
|
+
this._genIntRangeBody(stmt, name, isAssign);
|
|
481
593
|
});
|
|
482
594
|
this.line("}");
|
|
483
595
|
this._loopDepth--;
|
|
484
596
|
},
|
|
485
597
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
598
|
+
// True when `node` contains an assignment or ++/-- targeting identifier `name`.
|
|
599
|
+
_nodeAssigns(node, name) {
|
|
600
|
+
if (!node || typeof node !== "object") return false;
|
|
601
|
+
if (Array.isArray(node))
|
|
602
|
+
return node.some((n) => this._nodeAssigns(n, name));
|
|
603
|
+
if (
|
|
604
|
+
(node.kind === "AssignStmt" &&
|
|
605
|
+
node.lhs.some((e) => e.kind === "Ident" && e.name === name)) ||
|
|
606
|
+
(node.kind === "IncDecStmt" &&
|
|
607
|
+
node.expr.kind === "Ident" &&
|
|
608
|
+
node.expr.name === name)
|
|
609
|
+
)
|
|
610
|
+
return true;
|
|
611
|
+
for (const key of Object.keys(node)) {
|
|
612
|
+
if (key.startsWith("_")) continue;
|
|
613
|
+
if (this._nodeAssigns(node[key], name)) return true;
|
|
614
|
+
}
|
|
615
|
+
return false;
|
|
491
616
|
},
|
|
492
617
|
|
|
493
618
|
_genIterFor(init, iterType, iteree, lhs, body) {
|
|
@@ -515,12 +640,12 @@ export const statementGenMethods = {
|
|
|
515
640
|
: this.genExpr(range.expr);
|
|
516
641
|
|
|
517
642
|
if (lhs.length <= 1 && this._isIntRangeType(iterType)) {
|
|
518
|
-
this._genIntRangeFor(
|
|
643
|
+
this._genIntRangeFor(stmt, iteree, init.kind === "AssignStmt");
|
|
519
644
|
return;
|
|
520
645
|
}
|
|
521
646
|
|
|
522
647
|
if (!this._isMapRangeType(iterType) && !this._isStringRangeType(iterType)) {
|
|
523
|
-
this._genSliceRangeFor(stmt, iteree,
|
|
648
|
+
this._genSliceRangeFor(stmt, iteree, init.kind === "AssignStmt");
|
|
524
649
|
return;
|
|
525
650
|
}
|
|
526
651
|
|
|
@@ -28,12 +28,17 @@ const BUILDER_COMMON = {
|
|
|
28
28
|
export const builderMethods = {
|
|
29
29
|
_genBuilderCall(typeName, method, expr) {
|
|
30
30
|
const recv = expr.func.expr;
|
|
31
|
-
const
|
|
32
|
-
const base = `(${recvJs}?.value ?? ${recvJs})`;
|
|
31
|
+
const base = this._unboxWriter(this.genExpr(recv));
|
|
33
32
|
const args = expr.args.map((a) => this.genExpr(a));
|
|
34
33
|
const isStr = typeName === "strings.Builder";
|
|
35
34
|
const table = isStr ? BUILDER_STR : BUILDER_BYTES;
|
|
36
35
|
const gen = table[method] ?? BUILDER_COMMON[method];
|
|
37
36
|
return gen ? gen(base, args) : undefined;
|
|
38
37
|
},
|
|
38
|
+
|
|
39
|
+
// Accepts a Builder/Buffer or a pointer box around one; evaluates `js` once.
|
|
40
|
+
_unboxWriter(js) {
|
|
41
|
+
if (/^[\w$.]+$/.test(js)) return `(${js}?.value ?? ${js})`;
|
|
42
|
+
return `((__w) => __w?.value ?? __w)(${js})`;
|
|
43
|
+
},
|
|
39
44
|
};
|
|
@@ -80,12 +80,12 @@ export const fmtMethods = {
|
|
|
80
80
|
const sprintfCall = this._buildFprintSprintfCall(fn, rest.length, restJs);
|
|
81
81
|
if (targetTypeName === "strings.Builder") {
|
|
82
82
|
const w = this.genExpr(writerArg);
|
|
83
|
-
const base =
|
|
83
|
+
const base = this._unboxWriter(w);
|
|
84
84
|
return `(${base}._buf += ${sprintfCall})`;
|
|
85
85
|
}
|
|
86
86
|
if (targetTypeName === "bytes.Buffer") {
|
|
87
87
|
const w = this.genExpr(writerArg);
|
|
88
|
-
const base =
|
|
88
|
+
const base = this._unboxWriter(w);
|
|
89
89
|
return `((__b,__s)=>{ __b._buf.push(...new TextEncoder().encode(__s)); })(${base}, ${sprintfCall})`;
|
|
90
90
|
}
|
|
91
91
|
// Generic io.Writer fallback — call .WriteString if available
|
package/src/codegen/stdlib/io.js
CHANGED
|
@@ -14,7 +14,7 @@ export const ioMethods = {
|
|
|
14
14
|
(writerType?.kind === "pointer" ? writerType.base?.name : null);
|
|
15
15
|
const w = this.genExpr(writerArg);
|
|
16
16
|
const s = this.genExpr(expr.args[1]);
|
|
17
|
-
const base =
|
|
17
|
+
const base = this._unboxWriter(w);
|
|
18
18
|
if (typeName === "strings.Builder") {
|
|
19
19
|
return `((b,s) => { b._buf += s; return [s.length, null]; })(${base}, ${s})`;
|
|
20
20
|
}
|
package/src/dev-server.js
CHANGED
|
@@ -140,13 +140,15 @@ function isInsideDir(root, target) {
|
|
|
140
140
|
return rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
function handleSseRequest(req, res, clients) {
|
|
143
|
+
function handleSseRequest(req, res, clients, lastError = null) {
|
|
144
144
|
res.writeHead(200, {
|
|
145
145
|
"Content-Type": "text/event-stream",
|
|
146
146
|
"Cache-Control": "no-cache",
|
|
147
147
|
Connection: "keep-alive",
|
|
148
148
|
});
|
|
149
149
|
res.write(": connected\n\n");
|
|
150
|
+
if (lastError)
|
|
151
|
+
res.write(`event: build-error\ndata: ${JSON.stringify(lastError)}\n\n`);
|
|
150
152
|
clients.add(res);
|
|
151
153
|
req.on?.("close", () => clients.delete(res));
|
|
152
154
|
}
|
|
@@ -216,7 +218,7 @@ export function handleDevRequest(
|
|
|
216
218
|
options = {},
|
|
217
219
|
) {
|
|
218
220
|
if (req.url === "/_gofront/events") {
|
|
219
|
-
handleSseRequest(req, res, clients);
|
|
221
|
+
handleSseRequest(req, res, clients, options.getLastError?.() ?? null);
|
|
220
222
|
return;
|
|
221
223
|
}
|
|
222
224
|
|
|
@@ -312,15 +314,19 @@ function closeServer(server, clients, timer) {
|
|
|
312
314
|
export function createDevServer(serveDir, port = 3000, options = {}) {
|
|
313
315
|
const clients = new Set();
|
|
314
316
|
const heartbeatMs = options.heartbeatInterval ?? 15000;
|
|
317
|
+
const host = options.host ?? "localhost";
|
|
318
|
+
// Kept so browsers that connect (or reload) after a failed build still see the overlay.
|
|
319
|
+
let lastError = null;
|
|
320
|
+
const requestOptions = { ...options, getLastError: () => lastError };
|
|
315
321
|
|
|
316
322
|
const server = createServer((req, res) => {
|
|
317
|
-
handleDevRequest(req, res, serveDir, clients,
|
|
323
|
+
handleDevRequest(req, res, serveDir, clients, requestOptions);
|
|
318
324
|
});
|
|
319
325
|
|
|
320
326
|
server.on("error", (err) => handleServerError(err, port));
|
|
321
|
-
server.listen(port, () => {
|
|
327
|
+
server.listen(port, host, () => {
|
|
322
328
|
const actualPort = server.address()?.port ?? port;
|
|
323
|
-
if (port !== 0) {
|
|
329
|
+
if (port !== 0 && !options.silent) {
|
|
324
330
|
console.error(`gofront: dev server → http://localhost:${actualPort}`);
|
|
325
331
|
}
|
|
326
332
|
});
|
|
@@ -329,17 +335,23 @@ export function createDevServer(serveDir, port = 3000, options = {}) {
|
|
|
329
335
|
heartbeatMs > 0 ? setInterval(() => sendPings(clients), heartbeatMs) : null;
|
|
330
336
|
heartbeatTimer?.unref?.();
|
|
331
337
|
|
|
332
|
-
const
|
|
338
|
+
const broadcastNotify = createNotify(clients);
|
|
339
|
+
const notify = (arg) => {
|
|
340
|
+
if (!arg?.type) lastError = null;
|
|
341
|
+
broadcastNotify(arg);
|
|
342
|
+
};
|
|
343
|
+
const notifyError = (err) => {
|
|
344
|
+
lastError = buildErrorPayload(err);
|
|
345
|
+
broadcastToClients(clients, "build-error", lastError);
|
|
346
|
+
};
|
|
333
347
|
|
|
334
348
|
return {
|
|
335
349
|
broadcast: (event, data) => broadcastToClients(clients, event, data),
|
|
336
350
|
notify,
|
|
337
351
|
notifyCss: (file) =>
|
|
338
352
|
broadcastToClients(clients, "css-update", { type: "css-update", file }),
|
|
339
|
-
notifyError
|
|
340
|
-
|
|
341
|
-
notifyBuildError: (err) =>
|
|
342
|
-
broadcastToClients(clients, "build-error", buildErrorPayload(err)),
|
|
353
|
+
notifyError,
|
|
354
|
+
notifyBuildError: notifyError,
|
|
343
355
|
ping: () => sendPings(clients),
|
|
344
356
|
clients,
|
|
345
357
|
server,
|
package/src/index.js
CHANGED
|
@@ -162,8 +162,14 @@ if (args[0] === "dev") {
|
|
|
162
162
|
const devOptions = parseDevArgs(args.slice(1));
|
|
163
163
|
try {
|
|
164
164
|
const dev = await handleDev(devOptions.targetDir, devOptions);
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
if (dev.initialError) {
|
|
166
|
+
console.error("gofront: ERROR");
|
|
167
|
+
for (const line of dev.initialError.message.split("\n"))
|
|
168
|
+
console.error(` ${line}`);
|
|
169
|
+
} else {
|
|
170
|
+
console.error(`gofront: OK — wrote ${dev.outputFile}`);
|
|
171
|
+
}
|
|
172
|
+
console.error(`gofront: dev server running → http://localhost:${dev.port}`);
|
|
167
173
|
console.error(`gofront: watching ${dev.srcDir} for changes...`);
|
|
168
174
|
|
|
169
175
|
const shutdown = async () => {
|
|
@@ -29,7 +29,7 @@ export const declarationParseMethods = {
|
|
|
29
29
|
this.check(T.IDENT) && !this.isReceiverTerminator(this.peek2())
|
|
30
30
|
? this.advance().value
|
|
31
31
|
: "_";
|
|
32
|
-
this.match(T.STAR);
|
|
32
|
+
const recvPointer = Boolean(this.match(T.STAR));
|
|
33
33
|
const recvType = this.parseTypeName();
|
|
34
34
|
this.expect(T.RPAREN);
|
|
35
35
|
const name = this.expect(T.IDENT).value;
|
|
@@ -40,6 +40,7 @@ export const declarationParseMethods = {
|
|
|
40
40
|
kind: "MethodDecl",
|
|
41
41
|
recvName,
|
|
42
42
|
recvType,
|
|
43
|
+
recvPointer,
|
|
43
44
|
name,
|
|
44
45
|
params,
|
|
45
46
|
returnType,
|
package/src/pwa.js
CHANGED
|
@@ -86,7 +86,9 @@ self.addEventListener("fetch", (event) => {
|
|
|
86
86
|
return response;
|
|
87
87
|
}).catch(() => {
|
|
88
88
|
if (event.request.headers?.get?.("accept")?.includes("text/html")) {
|
|
89
|
-
return caches
|
|
89
|
+
return caches
|
|
90
|
+
.match("./")
|
|
91
|
+
.then((r) => r || caches.match("./index.html"));
|
|
90
92
|
}
|
|
91
93
|
});
|
|
92
94
|
}),
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
isNumeric,
|
|
15
15
|
isPointer,
|
|
16
16
|
isString,
|
|
17
|
+
isTypedArraySlice,
|
|
17
18
|
isUntyped,
|
|
18
19
|
LOG_OPS,
|
|
19
20
|
SIZED_FLOAT_NAMES,
|
|
@@ -157,6 +158,7 @@ export const assignabilityMethods = {
|
|
|
157
158
|
if (!target || !source) return;
|
|
158
159
|
target = this.resolveType(target);
|
|
159
160
|
source = this.resolveType(source);
|
|
161
|
+
this._markIfaceBox(target, source, node);
|
|
160
162
|
if (this._assertAssignableEarlyReturn(target, source)) return;
|
|
161
163
|
if (isPointer(target) && isPointer(source)) {
|
|
162
164
|
this.assertAssignable(
|
|
@@ -170,10 +172,34 @@ export const assignabilityMethods = {
|
|
|
170
172
|
if (this._isNumericCoercible(target, source)) return;
|
|
171
173
|
if (this._checkArrayAssignable(target, source, node)) return;
|
|
172
174
|
if (this._checkTypedArrayAssignable(target, source, node)) return;
|
|
175
|
+
if (this._checkFuncAssignable(target, source, node)) return;
|
|
173
176
|
if (typeStr(target) !== typeStr(source))
|
|
174
177
|
this._assertAssignableTypeMismatch(target, source, node);
|
|
175
178
|
},
|
|
176
179
|
|
|
180
|
+
// Tags struct values / struct pointers flowing into an interface so codegen can
|
|
181
|
+
// tell `T` from `*T` at runtime (both compile to the same class instance).
|
|
182
|
+
_markIfaceBox(target, source, node) {
|
|
183
|
+
if (this._skipIfaceMark) return;
|
|
184
|
+
if (!node || typeof node !== "object" || !target || !source) return;
|
|
185
|
+
if (target.kind === "typeParam") return;
|
|
186
|
+
const tBase = this.resolveType(
|
|
187
|
+
target.kind === "named" ? target.underlying : target,
|
|
188
|
+
);
|
|
189
|
+
if (!isAny(target) && tBase?.kind !== "interface") return;
|
|
190
|
+
const structOf = (t) => {
|
|
191
|
+
const r = this.resolveType(t);
|
|
192
|
+
const b = r?.kind === "named" ? this.resolveType(r.underlying) : r;
|
|
193
|
+
return b?.kind === "struct";
|
|
194
|
+
};
|
|
195
|
+
const sBase = this.resolveType(
|
|
196
|
+
source.kind === "named" ? source.underlying : source,
|
|
197
|
+
);
|
|
198
|
+
if (sBase?.kind === "struct") node._ifaceBox = "value";
|
|
199
|
+
else if (sBase?.kind === "pointer" && structOf(sBase.base))
|
|
200
|
+
node._ifaceBox = "ptr";
|
|
201
|
+
},
|
|
202
|
+
|
|
177
203
|
_assertAssignableEarlyReturn(target, source) {
|
|
178
204
|
return (
|
|
179
205
|
target?.kind === "typeParam" ||
|
|
@@ -211,9 +237,13 @@ export const assignabilityMethods = {
|
|
|
211
237
|
|
|
212
238
|
_isNumericCoercible(target, source) {
|
|
213
239
|
if (target.kind !== "basic" || source.kind !== "basic") return false;
|
|
240
|
+
const pair = `${target.name}:${source.name}`;
|
|
214
241
|
return (
|
|
215
|
-
|
|
216
|
-
|
|
242
|
+
pair === "float64:int" ||
|
|
243
|
+
pair === "int:float64" ||
|
|
244
|
+
// rune was historically int in GoFront; keep rune/int interchangeable.
|
|
245
|
+
pair === "int:int32" ||
|
|
246
|
+
pair === "int32:int"
|
|
217
247
|
);
|
|
218
248
|
},
|
|
219
249
|
|
|
@@ -258,8 +288,9 @@ export const assignabilityMethods = {
|
|
|
258
288
|
const taElem = targetTa
|
|
259
289
|
? (target.underlying?._elemType ?? target._elemType)
|
|
260
290
|
: (source.underlying?._elemType ?? source._elemType);
|
|
261
|
-
const
|
|
262
|
-
if (typeStr(taElem) === typeStr(
|
|
291
|
+
const slice = targetSlice ?? sourceSlice;
|
|
292
|
+
if (isTypedArraySlice(slice) && typeStr(taElem) === typeStr(slice.elem))
|
|
293
|
+
return true;
|
|
263
294
|
this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
|
|
264
295
|
return true;
|
|
265
296
|
}
|
|
@@ -275,6 +306,21 @@ export const assignabilityMethods = {
|
|
|
275
306
|
return false;
|
|
276
307
|
},
|
|
277
308
|
|
|
309
|
+
_checkFuncAssignable(target, source, node) {
|
|
310
|
+
const tFunc = target.kind === "named" ? target.underlying : target;
|
|
311
|
+
const sFunc = source.kind === "named" ? source.underlying : source;
|
|
312
|
+
if (tFunc?.kind === "func" && sFunc?.kind === "func") {
|
|
313
|
+
if (!this._implementsMethod(tFunc, sFunc)) {
|
|
314
|
+
this.err(
|
|
315
|
+
`Cannot assign ${typeStr(source)} to ${typeStr(target)}`,
|
|
316
|
+
node,
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
return false;
|
|
322
|
+
},
|
|
323
|
+
|
|
278
324
|
// ── Interface satisfaction ────────────────────────────────────────
|
|
279
325
|
|
|
280
326
|
_sigParamsMatch(reqList, actList) {
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
isNumeric,
|
|
16
16
|
isString,
|
|
17
17
|
NIL,
|
|
18
|
+
RUNE,
|
|
18
19
|
Scope,
|
|
19
20
|
STRING,
|
|
20
21
|
TAINTED_ANY,
|
|
@@ -40,6 +41,17 @@ const BUILTIN_CHECK = {
|
|
|
40
41
|
s.err(`cannot append to array (type ${typeStr(st)})`, e);
|
|
41
42
|
return st;
|
|
42
43
|
}
|
|
44
|
+
const sb = s.resolveType(st?.kind === "named" ? st.underlying : st);
|
|
45
|
+
if (sb?.kind === "slice") {
|
|
46
|
+
for (let i = 1; i < e.args.length; i++) {
|
|
47
|
+
if (!e.args[i]._spread && at[i])
|
|
48
|
+
s._markIfaceBox(
|
|
49
|
+
s.resolveType(sb.elem),
|
|
50
|
+
s.resolveType(at[i]),
|
|
51
|
+
e.args[i],
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
43
55
|
return st;
|
|
44
56
|
},
|
|
45
57
|
copy: () => INT,
|
|
@@ -379,7 +391,7 @@ export const expressionCheckMethods = {
|
|
|
379
391
|
},
|
|
380
392
|
|
|
381
393
|
_rangeCollTypeTuple(resolved, collType) {
|
|
382
|
-
if (isString(resolved)) return { kind: "tuple", types: [INT,
|
|
394
|
+
if (isString(resolved)) return { kind: "tuple", types: [INT, RUNE] };
|
|
383
395
|
if (resolved?.kind === "slice" || resolved?.kind === "array")
|
|
384
396
|
return { kind: "tuple", types: [INT, resolved.elem ?? ANY] };
|
|
385
397
|
if (resolved?.kind === "map")
|