gofront 1.8.0 → 1.9.0
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 +3 -3
- package/package.json +5 -4
- package/src/backend/js/expressions.js +849 -371
- package/src/backend/js/index.js +241 -147
- package/src/backend/js/natives.js +383 -0
- package/src/backend/js/runtime.js +512 -2547
- package/src/backend/js/statements.js +506 -103
- package/src/backend/js/stdlib/index.js +45 -93
- package/src/backend/js/templ.js +32 -45
- package/src/backend/js/type-info.js +177 -13
- package/src/backend/shared/fixed-float.js +20 -0
- package/src/backend/shared/fmt-types.js +63 -171
- package/src/backend/shared/host-unicode.js +36 -0
- package/src/backend/wasm/boundary.js +490 -965
- package/src/backend/wasm/emit-builtins.js +643 -256
- package/src/backend/wasm/emit-exprs.js +124 -57
- package/src/backend/wasm/emit-maps.js +17 -367
- package/src/backend/wasm/emit-stmts.js +325 -75
- package/src/backend/wasm/emit.js +104 -13
- package/src/backend/wasm/globals.js +47 -8
- package/src/backend/wasm/glue.js +2 -4
- package/src/backend/wasm/index.js +36 -530
- package/src/backend/wasm/method-expr.js +12 -1
- package/src/backend/wasm/module-emitter.js +92 -717
- package/src/backend/wasm/monomorph.js +103 -6
- package/src/backend/wasm/natives.js +596 -0
- package/src/backend/wasm/prescan.js +5 -153
- package/src/backend/wasm/prune-funcs.js +89 -0
- package/src/backend/wasm/stdlib/index.js +2 -32
- package/src/backend/wasm/types.js +68 -20
- package/src/backend/wasm/variadic.js +3 -3
- package/src/cli/cli-core.js +5 -1
- package/src/cli/test-runner.js +115 -342
- package/src/compiler.js +108 -130
- package/src/lexer/lexer.js +105 -11
- package/src/lexer/templ-lexer.js +61 -18
- package/src/lexer/tokens.js +1 -0
- package/src/link/stdlib.js +354 -132
- package/src/link/type-names.js +148 -0
- package/src/link/wasm.js +30 -6
- package/src/lower/bounds.js +153 -0
- package/src/lower/captures.js +92 -33
- package/src/lower/embedding.js +32 -3
- package/src/lower/errors-as.js +196 -0
- package/src/lower/fmt-formatters.js +905 -0
- package/src/lower/functions.js +2 -0
- package/src/lower/gen-types.js +274 -0
- package/src/lower/index.js +7 -1
- package/src/lower/json-codecs.js +228 -247
- package/src/lower/ownership.js +25 -7
- package/src/lower/reachability.js +69 -51
- package/src/lower/walk.js +12 -0
- package/src/parser/declarations.js +28 -5
- package/src/parser/expressions.js +86 -16
- package/src/parser/statements.js +1 -0
- package/src/parser/templ.js +44 -5
- package/src/parser/types.js +1 -0
- package/src/resolver.js +1 -2
- package/src/typechecker/assignability.js +124 -11
- package/src/typechecker/const-fold.js +70 -27
- package/src/typechecker/constants.js +32 -6
- package/src/typechecker/expressions.js +326 -98
- package/src/typechecker/index.js +208 -33
- package/src/typechecker/init-order.js +136 -40
- package/src/typechecker/operand-text.js +40 -0
- package/src/typechecker/resolve.js +192 -36
- package/src/typechecker/statements.js +122 -11
- package/src/typechecker/stdlib/excluded.js +9 -5
- package/src/typechecker/stdlib/index.js +25 -9
- package/src/typechecker/termination.js +10 -1
- package/src/typechecker/types.js +203 -573
- package/stdlib/bytes/buffer.go +479 -0
- package/stdlib/bytes/bytes.go +1010 -0
- package/stdlib/bytes/reader.go +161 -0
- package/stdlib/context/context.go +115 -0
- package/stdlib/crypto/rand/rand.go +34 -0
- package/stdlib/crypto/sha256/sha256.go +249 -0
- package/stdlib/encoding/base64/base64.go +331 -0
- package/stdlib/encoding/binary/binary.go +332 -0
- package/stdlib/encoding/hex/hex.go +140 -0
- package/stdlib/encoding/json/json.go +62 -0
- package/stdlib/errors/errors.go +174 -0
- package/stdlib/fmt/errors.go +110 -0
- package/stdlib/fmt/format.go +732 -0
- package/stdlib/fmt/print.go +1232 -0
- package/stdlib/gom/gom.go +293 -0
- package/stdlib/html/html.go +251 -0
- package/stdlib/internal/fmtrt/fmtrt.go +163 -0
- package/stdlib/internal/jsonrt/dec.go +171 -38
- package/stdlib/internal/jsonrt/enc.go +186 -36
- package/stdlib/internal/jsonrt/scanner.go +17 -8
- package/stdlib/io/io.go +157 -1
- package/stdlib/maps/maps.go +131 -0
- package/stdlib/math/bits/bits.go +507 -0
- package/stdlib/math/extra.go +16 -14
- package/stdlib/math/math.go +439 -0
- package/stdlib/math/rand/rand.go +297 -0
- package/stdlib/os/os.go +93 -0
- package/stdlib/path/filepath/filepath.go +47 -0
- package/stdlib/path/path.go +394 -0
- package/stdlib/regexp/regexp.go +231 -0
- package/stdlib/slices/slices.go +526 -0
- package/stdlib/slices/sort.go +209 -0
- package/stdlib/slices/zsort_cmp.go +480 -0
- package/stdlib/slices/zsort_ordered.go +482 -0
- package/stdlib/sort/sort.go +391 -452
- package/stdlib/sort/zsort_func.go +480 -0
- package/stdlib/sort/zsort_iface.go +480 -0
- package/stdlib/strconv/float.go +1699 -0
- package/stdlib/strconv/isprint.go +752 -0
- package/stdlib/strconv/quote.go +235 -50
- package/stdlib/strconv/strconv.go +616 -0
- package/stdlib/strings/builder.go +95 -0
- package/stdlib/strings/reader.go +163 -0
- package/stdlib/strings/replacer.go +102 -0
- package/stdlib/strings/strings.go +807 -0
- package/stdlib/sync/sync.go +268 -0
- package/stdlib/testing/async_js.go +61 -0
- package/stdlib/testing/benchmark.go +297 -0
- package/stdlib/testing/testing.go +523 -0
- package/stdlib/time/duration.go +233 -0
- package/stdlib/time/format.go +745 -0
- package/stdlib/time/parse.go +790 -0
- package/stdlib/time/sleep_js.go +63 -0
- package/stdlib/time/time.go +834 -0
- package/stdlib/unicode/unicode.go +141 -3
- package/stdlib/unicode/utf8/utf8.go +464 -0
- package/src/backend/js/fmt-runtime.js +0 -573
- package/src/backend/js/stdlib/base64.js +0 -12
- package/src/backend/js/stdlib/binary.js +0 -102
- package/src/backend/js/stdlib/bits.js +0 -119
- package/src/backend/js/stdlib/builder.js +0 -98
- package/src/backend/js/stdlib/bytes.js +0 -368
- package/src/backend/js/stdlib/errors.js +0 -45
- package/src/backend/js/stdlib/fmt.js +0 -517
- package/src/backend/js/stdlib/gom.js +0 -155
- package/src/backend/js/stdlib/hex.js +0 -17
- package/src/backend/js/stdlib/html.js +0 -20
- package/src/backend/js/stdlib/io.js +0 -137
- package/src/backend/js/stdlib/json.js +0 -1167
- package/src/backend/js/stdlib/maps.js +0 -37
- package/src/backend/js/stdlib/math.js +0 -85
- package/src/backend/js/stdlib/os.js +0 -13
- package/src/backend/js/stdlib/path.js +0 -48
- package/src/backend/js/stdlib/rand.js +0 -32
- package/src/backend/js/stdlib/regexp.js +0 -54
- package/src/backend/js/stdlib/sha256.js +0 -12
- package/src/backend/js/stdlib/shared.js +0 -17
- package/src/backend/js/stdlib/simd.js +0 -34
- package/src/backend/js/stdlib/slices.js +0 -107
- package/src/backend/js/stdlib/sort.js +0 -30
- package/src/backend/js/stdlib/strconv.js +0 -79
- package/src/backend/js/stdlib/strings.js +0 -301
- package/src/backend/js/stdlib/sync.js +0 -13
- package/src/backend/js/stdlib/testing.js +0 -41
- package/src/backend/js/stdlib/time.js +0 -152
- package/src/backend/js/stdlib/unicode.js +0 -25
- package/src/backend/js/stdlib/utf8.js +0 -26
- package/src/backend/wasm/stdlib/bytes.js +0 -6716
- package/src/backend/wasm/stdlib/core.js +0 -10278
- package/src/backend/wasm/stdlib/io.js +0 -3578
- package/src/backend/wasm/stdlib/packages.js +0 -2402
- package/src/backend/wasm/stdlib/pure.js +0 -720
- package/src/backend/wasm/stdlib/time.js +0 -2229
- package/src/typechecker/stdlib/core.js +0 -1079
- package/src/typechecker/stdlib/extended.js +0 -1210
- package/stdlib/bytes/iter.go +0 -192
- package/stdlib/bytes/search.go +0 -240
- package/stdlib/bytes/split.go +0 -218
- package/stdlib/errors/overlay.go +0 -12
- package/stdlib/maps/iter.go +0 -65
- package/stdlib/slices/extra.go +0 -73
- package/stdlib/slices/iter.go +0 -113
- package/stdlib/strings/extra.go +0 -35
- package/stdlib/strings/iter.go +0 -122
- package/stdlib/sync/extra.go +0 -105
- package/stdlib/unicode/utf8/bytes.go +0 -239
package/src/lower/ownership.js
CHANGED
|
@@ -13,6 +13,31 @@ export function rootIdentName(e) {
|
|
|
13
13
|
return null;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
// True when `e` names an element of a slice or array, possibly through
|
|
17
|
+
// fields (`xs[i]`, `xs[i].f.g`).
|
|
18
|
+
function isElementPath(e) {
|
|
19
|
+
while (e) {
|
|
20
|
+
if (e.kind === "IndexExpr") return !e._mapValueType;
|
|
21
|
+
if (e.kind !== "SelectorExpr" && e.kind !== "ParenExpr") return false;
|
|
22
|
+
e = e.expr;
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// True when `node` contains `&xs[i]` (or `&xs[i].f`): some code may then hold a
|
|
28
|
+
// pointer to an element, so a whole-element store must write into it.
|
|
29
|
+
export function takesElementAddress(node) {
|
|
30
|
+
if (!node || typeof node !== "object") return false;
|
|
31
|
+
if (Array.isArray(node)) return node.some(takesElementAddress);
|
|
32
|
+
if (
|
|
33
|
+
node.kind === "UnaryExpr" &&
|
|
34
|
+
node.op === "&" &&
|
|
35
|
+
isElementPath(node.operand)
|
|
36
|
+
)
|
|
37
|
+
return true;
|
|
38
|
+
return someChild(node, takesElementAddress);
|
|
39
|
+
}
|
|
40
|
+
|
|
16
41
|
// True when `node` itself (not its children) writes to variable `name`.
|
|
17
42
|
export function nodeWritesVar(node, name) {
|
|
18
43
|
const root = (e) => rootIdentName(e) === name;
|
|
@@ -160,13 +185,6 @@ export class OwnershipContext {
|
|
|
160
185
|
constructor(body) {
|
|
161
186
|
this.body = body;
|
|
162
187
|
this.mut = new Map();
|
|
163
|
-
this.owned = new Set();
|
|
164
|
-
this.borrowed = new Set();
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
markOwnership(name, owned) {
|
|
168
|
-
if (!name || name === "_") return;
|
|
169
|
-
(owned ? this.owned : this.borrowed).add(name);
|
|
170
188
|
}
|
|
171
189
|
|
|
172
190
|
fnMutates(name, addrOnly = false) {
|
|
@@ -11,10 +11,12 @@
|
|
|
11
11
|
import { forEachChild } from "./walk.js";
|
|
12
12
|
|
|
13
13
|
// Methods the runtime or emitter-backed stdlib call without a selector in
|
|
14
|
-
// GoFront code (fmt verbs, errors.Is/As/Unwrap,
|
|
14
|
+
// GoFront code (fmt verbs and Fprint* writers, errors.Is/As/Unwrap,
|
|
15
|
+
// encoding/json).
|
|
15
16
|
const IMPLICIT_METHODS = new Set([
|
|
16
17
|
"Error",
|
|
17
18
|
"String",
|
|
19
|
+
"Write",
|
|
18
20
|
"GoString",
|
|
19
21
|
"Format",
|
|
20
22
|
"Unwrap",
|
|
@@ -48,6 +50,9 @@ function collectRefs(node, names, fields) {
|
|
|
48
50
|
)
|
|
49
51
|
names.add(node.name);
|
|
50
52
|
else if (node.kind === "SelectorExpr") fields.add(node.field);
|
|
53
|
+
// A reached interface needs its methods on the types that satisfy it.
|
|
54
|
+
else if (node.kind === "InterfaceType")
|
|
55
|
+
for (const m of node.methods ?? []) fields.add(m.name);
|
|
51
56
|
forEachChild(node, (child) => collectRefs(child, names, fields));
|
|
52
57
|
}
|
|
53
58
|
|
|
@@ -70,63 +75,76 @@ function indexStdlib(stdlibPrograms) {
|
|
|
70
75
|
return { byName, methods, inits };
|
|
71
76
|
}
|
|
72
77
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
78
|
+
// Incremental closure over one or more Go-source stdlib packages. A
|
|
79
|
+
// declaration's body may still be unchecked (lazy checking): `decl._ensure()`,
|
|
80
|
+
// when present, checks and mangles it first and returns an error text on
|
|
81
|
+
// failure, so the references collected from it are the mangled ones. Failed
|
|
82
|
+
// declarations are listed in `failures`.
|
|
83
|
+
export class StdlibReach {
|
|
84
|
+
constructor(stdlibPrograms) {
|
|
85
|
+
this.programs = stdlibPrograms;
|
|
86
|
+
const { byName, methods, inits } = indexStdlib(stdlibPrograms);
|
|
87
|
+
this.byName = byName;
|
|
88
|
+
this.methods = methods;
|
|
89
|
+
this.inits = inits;
|
|
90
|
+
this.reached = new Set();
|
|
91
|
+
this.names = new Set();
|
|
92
|
+
this.fields = new Set(IMPLICIT_METHODS);
|
|
93
|
+
this.pending = [];
|
|
94
|
+
this.failures = [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Adds `rootNodes` (AST that is emitted) and closes over what they use.
|
|
98
|
+
add(rootNodes) {
|
|
99
|
+
const roots = new Set();
|
|
100
|
+
collectRefs(rootNodes, roots, this.fields);
|
|
101
|
+
for (const n of roots) this._name(n);
|
|
102
|
+
this._close();
|
|
103
|
+
}
|
|
86
104
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
105
|
+
_name(n) {
|
|
106
|
+
if (this.names.has(n)) return;
|
|
107
|
+
this.names.add(n);
|
|
108
|
+
this.pending.push(n);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
_reach(d) {
|
|
112
|
+
if (this.reached.has(d)) return;
|
|
113
|
+
this.reached.add(d);
|
|
114
|
+
const failure = d._ensure?.();
|
|
115
|
+
if (failure) this.failures.push(failure);
|
|
116
|
+
const local = new Set();
|
|
117
|
+
collectRefs(d, local, this.fields);
|
|
118
|
+
for (const n of local) this._name(n);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
_close() {
|
|
122
|
+
let changed = true;
|
|
123
|
+
while (changed) {
|
|
124
|
+
while (this.pending.length > 0)
|
|
125
|
+
for (const d of this.byName.get(this.pending.pop()) ?? [])
|
|
126
|
+
this._reach(d);
|
|
127
|
+
const size = this.reached.size;
|
|
128
|
+
for (const m of this.methods)
|
|
129
|
+
if (this.names.has(m.recv) && this.fields.has(m.decl.name))
|
|
130
|
+
this._reach(m.decl);
|
|
131
|
+
for (const p of this.programs)
|
|
132
|
+
if (p.decls.some((d) => this.reached.has(d)))
|
|
133
|
+
for (const d of this.inits.get(p) ?? []) this._reach(d);
|
|
134
|
+
changed = this.reached.size !== size || this.pending.length > 0;
|
|
135
|
+
}
|
|
91
136
|
}
|
|
92
137
|
}
|
|
93
138
|
|
|
94
139
|
/**
|
|
95
140
|
* Returns the set of declarations of `stdlibPrograms` reachable from
|
|
96
141
|
* `rootPrograms` (all user code that is emitted). A program's `init` is
|
|
97
|
-
* kept when anything else in that program is reached.
|
|
142
|
+
* kept when anything else in that program is reached. A reached body that
|
|
143
|
+
* fails its (lazy) check throws.
|
|
98
144
|
*/
|
|
99
145
|
export function reachableStdlibDecls(rootPrograms, stdlibPrograms) {
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
collectRefs(rootPrograms, names, fields);
|
|
105
|
-
addExtraRoots(names);
|
|
106
|
-
|
|
107
|
-
const pending = [...names];
|
|
108
|
-
const reach = (d) => {
|
|
109
|
-
if (reached.has(d)) return;
|
|
110
|
-
reached.add(d);
|
|
111
|
-
const local = new Set();
|
|
112
|
-
collectRefs(d, local, fields);
|
|
113
|
-
for (const n of local) {
|
|
114
|
-
if (names.has(n)) continue;
|
|
115
|
-
names.add(n);
|
|
116
|
-
pending.push(n);
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
let changed = true;
|
|
120
|
-
while (changed) {
|
|
121
|
-
while (pending.length > 0)
|
|
122
|
-
for (const d of byName.get(pending.pop()) ?? []) reach(d);
|
|
123
|
-
const size = reached.size;
|
|
124
|
-
for (const m of methods)
|
|
125
|
-
if (names.has(m.recv) && fields.has(m.decl.name)) reach(m.decl);
|
|
126
|
-
for (const p of stdlibPrograms)
|
|
127
|
-
if (p.decls.some((d) => reached.has(d)))
|
|
128
|
-
for (const d of inits.get(p) ?? []) reach(d);
|
|
129
|
-
changed = reached.size !== size || pending.length > 0;
|
|
130
|
-
}
|
|
131
|
-
return reached;
|
|
146
|
+
const reach = new StdlibReach(stdlibPrograms);
|
|
147
|
+
reach.add(rootPrograms);
|
|
148
|
+
if (reach.failures.length > 0) throw new Error(reach.failures[0]);
|
|
149
|
+
return reach.reached;
|
|
132
150
|
}
|
package/src/lower/walk.js
CHANGED
|
@@ -23,3 +23,15 @@ export function someChild(node, pred) {
|
|
|
23
23
|
}
|
|
24
24
|
return false;
|
|
25
25
|
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* True when evaluating `node` runs a call or a channel receive (conversions
|
|
29
|
+
* excluded), the only operands Go still evaluates for a constant `len`.
|
|
30
|
+
*/
|
|
31
|
+
export function hasCallOrReceive(node) {
|
|
32
|
+
if (!node || typeof node !== "object") return false;
|
|
33
|
+
if (Array.isArray(node)) return node.some(hasCallOrReceive);
|
|
34
|
+
if (node.kind === "CallExpr" && !node._isTypeConversion) return true;
|
|
35
|
+
if (node.kind === "UnaryExpr" && node.op === "<-") return true;
|
|
36
|
+
return someChild(node, hasCallOrReceive);
|
|
37
|
+
}
|
|
@@ -34,10 +34,12 @@ export const declarationParseMethods = {
|
|
|
34
34
|
this.expect(T.RPAREN);
|
|
35
35
|
const name = this.expect(T.IDENT).value;
|
|
36
36
|
const { params, returnType } = this.parseSignature();
|
|
37
|
+
const native = this.tokens?.natives?.get(_line - 1) ?? null;
|
|
37
38
|
const body = this.parseBlock();
|
|
38
39
|
this.semi();
|
|
39
40
|
return {
|
|
40
41
|
kind: "MethodDecl",
|
|
42
|
+
...(native && { native }),
|
|
41
43
|
recvName,
|
|
42
44
|
recvType,
|
|
43
45
|
recvPointer,
|
|
@@ -59,7 +61,11 @@ export const declarationParseMethods = {
|
|
|
59
61
|
// `//gofront:intrinsic name` on the line before a body-less func: the
|
|
60
62
|
// backend provides the implementation; every pass sees an empty body.
|
|
61
63
|
const intrinsic = this.tokens?.intrinsics?.get(_line - 1) ?? null;
|
|
62
|
-
|
|
64
|
+
// `//gofront:native name` marks a func that keeps its Go body; a
|
|
65
|
+
// body-less one parses (and is rejected by the checker).
|
|
66
|
+
const native = this.tokens?.natives?.get(_line - 1) ?? null;
|
|
67
|
+
const bodyless =
|
|
68
|
+
(intrinsic !== null || native !== null) && !this.check(T.LBRACE);
|
|
63
69
|
const body = bodyless ? { kind: "Block", stmts: [] } : this.parseBlock();
|
|
64
70
|
this.semi();
|
|
65
71
|
return {
|
|
@@ -70,6 +76,7 @@ export const declarationParseMethods = {
|
|
|
70
76
|
returnType,
|
|
71
77
|
body,
|
|
72
78
|
...(intrinsic && { intrinsic, _bodyless: bodyless }),
|
|
79
|
+
...(native && { native, _bodyless: bodyless }),
|
|
73
80
|
_line,
|
|
74
81
|
};
|
|
75
82
|
},
|
|
@@ -119,10 +126,16 @@ export const declarationParseMethods = {
|
|
|
119
126
|
// `Name[…]` followed by `,` or `)` in a result list is an instantiated
|
|
120
127
|
// generic type; a result name is followed by its type (`a []int`).
|
|
121
128
|
_isGenericResultType() {
|
|
122
|
-
|
|
129
|
+
return this._isGenericInstanceAt(this.pos);
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
// True when the IDENT at `at` starts an instantiated type (`Box[int]`) that
|
|
133
|
+
// ends a list entry, rather than `name [N]T` / `name []T`.
|
|
134
|
+
_isGenericInstanceAt(at) {
|
|
135
|
+
if (this.tokens[at + 1]?.type !== T.LBRACKET) return false;
|
|
123
136
|
const src = this.tokens;
|
|
124
137
|
let depth = 0;
|
|
125
|
-
for (let i =
|
|
138
|
+
for (let i = at + 1; i < src.length; i++) {
|
|
126
139
|
const t = src[i].type;
|
|
127
140
|
if (t === T.LBRACKET) depth++;
|
|
128
141
|
else if (t === T.RBRACKET && --depth === 0) {
|
|
@@ -179,11 +192,13 @@ export const declarationParseMethods = {
|
|
|
179
192
|
// if a type-start token follows — if so, the IDENTs are names; if ) or ,
|
|
180
193
|
// follows, the IDENTs are themselves types.
|
|
181
194
|
_isNamedParam() {
|
|
195
|
+
if (this._isGenericInstanceAt(this.pos)) return false;
|
|
182
196
|
let pos = this.pos + 1; // skip current IDENT
|
|
183
197
|
while (
|
|
184
198
|
this.tokens[pos]?.type === T.COMMA &&
|
|
185
199
|
this.tokens[pos + 1]?.type === T.IDENT
|
|
186
200
|
) {
|
|
201
|
+
if (this._isGenericInstanceAt(pos + 1)) return false;
|
|
187
202
|
pos += 2;
|
|
188
203
|
}
|
|
189
204
|
const next = this.tokens[pos];
|
|
@@ -212,8 +227,16 @@ export const declarationParseMethods = {
|
|
|
212
227
|
this.check(T.EOF)
|
|
213
228
|
)
|
|
214
229
|
return null;
|
|
215
|
-
// Inside a type expression (e.g. func type in a tuple
|
|
216
|
-
|
|
230
|
+
// Inside a type expression (e.g. func type in a tuple or a type-switch
|
|
231
|
+
// `case func():`), `,`, `)` and `:` also end the type
|
|
232
|
+
if (
|
|
233
|
+
inTypeExpr &&
|
|
234
|
+
(this.check(T.COMMA) ||
|
|
235
|
+
this.check(T.RPAREN) ||
|
|
236
|
+
this.check(T.COLON) ||
|
|
237
|
+
this.check(T.STRING) ||
|
|
238
|
+
this.check(T.ASSIGN))
|
|
239
|
+
)
|
|
217
240
|
return null;
|
|
218
241
|
// Multiple return values: (int, string) or named (result int, ok bool)
|
|
219
242
|
if (this.check(T.LPAREN)) {
|
|
@@ -97,6 +97,18 @@ function _isDisallowedInTypeArgs(tt) {
|
|
|
97
97
|
return DISALLOWED_IN_TYPE_ARGS.has(tt);
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
// `[T]` followed by `(` or a composite literal's `{` is an instantiation. A
|
|
101
|
+
// `{` after the condition of if/for/switch opens the block (`if v == s[i] {`).
|
|
102
|
+
function _opensArgs(after, noCompLit) {
|
|
103
|
+
return after.type === T.LPAREN || (after.type === T.LBRACE && !noCompLit);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function _isCallParen(src, i, braces) {
|
|
107
|
+
return (
|
|
108
|
+
src[i].type === T.LPAREN && braces === 0 && src[i - 1]?.type === T.IDENT
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
100
112
|
function _isTypeIntroducerToken(tt) {
|
|
101
113
|
return (
|
|
102
114
|
tt === T.STAR ||
|
|
@@ -173,11 +185,20 @@ export const expressionParserMethods = {
|
|
|
173
185
|
} else if (t.type === T.LBRACKET) {
|
|
174
186
|
// Check if this looks like generic type args: Ident[Type](...) or Ident[Type]{...}
|
|
175
187
|
// `pkg.F[int](x)` / `pkg.T[X]{...}` instantiate through a qualifier.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
188
|
+
const nameLike =
|
|
189
|
+
expr.kind === "Ident" ||
|
|
190
|
+
(expr.kind === "SelectorExpr" && expr.expr.kind === "Ident");
|
|
191
|
+
const plainArgs = nameLike && this.looksLikeTypeArgList(noCompLit);
|
|
192
|
+
const literalArgs =
|
|
193
|
+
nameLike && !plainArgs ? this.tryParseTypeLiteralArgs() : null;
|
|
194
|
+
if (literalArgs) {
|
|
195
|
+
expr = {
|
|
196
|
+
kind: "InstantiationExpr",
|
|
197
|
+
expr,
|
|
198
|
+
typeArgs: literalArgs,
|
|
199
|
+
_line: expr._line,
|
|
200
|
+
};
|
|
201
|
+
} else if (plainArgs) {
|
|
181
202
|
this.advance(); // consume [
|
|
182
203
|
const typeArgs = [this.parseType()];
|
|
183
204
|
while (this.match(T.COMMA)) typeArgs.push(this.parseType());
|
|
@@ -233,15 +254,59 @@ export const expressionParserMethods = {
|
|
|
233
254
|
);
|
|
234
255
|
},
|
|
235
256
|
|
|
257
|
+
// `Id[[]int, int]`, `slices.Sort[map[string]int]`: type arguments that are
|
|
258
|
+
// type literals. Returns the parsed type list, or null (position restored)
|
|
259
|
+
// when the brackets hold an index expression such as `a[[]int{1}[0]]`.
|
|
260
|
+
tryParseTypeLiteralArgs() {
|
|
261
|
+
const first = this.peek2()?.type;
|
|
262
|
+
const startsLiteral =
|
|
263
|
+
first === T.LBRACKET || first === T.MAP || first === T.FUNC;
|
|
264
|
+
// `*T` could be a dereference in an index, but an index has no comma.
|
|
265
|
+
if (!startsLiteral && !(first === T.STAR || this._hasTopLevelComma()))
|
|
266
|
+
return null;
|
|
267
|
+
const start = this.pos;
|
|
268
|
+
try {
|
|
269
|
+
this.expect(T.LBRACKET);
|
|
270
|
+
const typeArgs = [this.parseType()];
|
|
271
|
+
while (this.match(T.COMMA)) typeArgs.push(this.parseType());
|
|
272
|
+
this.expect(T.RBRACKET);
|
|
273
|
+
if (!startsLiteral && typeArgs.length < 2) throw new Error("not types");
|
|
274
|
+
// `a[foo, bar]` is not an instantiation: names alone need a literal.
|
|
275
|
+
if (!startsLiteral && typeArgs.every((a) => a.kind === "TypeName"))
|
|
276
|
+
throw new Error("not type literals");
|
|
277
|
+
return typeArgs;
|
|
278
|
+
} catch {
|
|
279
|
+
this.pos = start;
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
// Whether the bracket pair at the current `[` contains a comma outside any
|
|
285
|
+
// nested brackets, parentheses or braces.
|
|
286
|
+
_hasTopLevelComma() {
|
|
287
|
+
let depth = 0;
|
|
288
|
+
for (let i = this.pos; i < this.tokens.length; i++) {
|
|
289
|
+
const tt = this.tokens[i].type;
|
|
290
|
+
if (tt === T.LBRACKET || tt === T.LPAREN || tt === T.LBRACE) depth++;
|
|
291
|
+
else if (tt === T.RBRACKET || tt === T.RPAREN || tt === T.RBRACE) {
|
|
292
|
+
depth--;
|
|
293
|
+
if (depth === 0) return false;
|
|
294
|
+
} else if (tt === T.COMMA && depth === 1) return true;
|
|
295
|
+
else if (tt === T.EOF) return false;
|
|
296
|
+
}
|
|
297
|
+
return false;
|
|
298
|
+
},
|
|
299
|
+
|
|
236
300
|
// Lookahead: does the [ ... ] after an Ident look like generic type args?
|
|
237
301
|
// Returns true if: only type-like tokens inside brackets, AND followed by a valid continuation
|
|
238
|
-
looksLikeTypeArgList() {
|
|
302
|
+
looksLikeTypeArgList(noCompLit = false) {
|
|
239
303
|
let i = this.pos + 1; // skip [
|
|
240
304
|
const src = this.tokens;
|
|
241
305
|
let depth = 1;
|
|
242
306
|
let hasComma = false;
|
|
243
307
|
let hasTypeKeyword = false;
|
|
244
308
|
let hasUpperIdent = false;
|
|
309
|
+
let braces = 0;
|
|
245
310
|
while (i < src.length && depth > 0) {
|
|
246
311
|
const tt = src[i].type;
|
|
247
312
|
if (tt === T.LBRACKET) {
|
|
@@ -257,6 +322,13 @@ export const expressionParserMethods = {
|
|
|
257
322
|
i++;
|
|
258
323
|
continue;
|
|
259
324
|
}
|
|
325
|
+
// `k.(int)` is a type assertion on a value: an index expression
|
|
326
|
+
if (tt === T.DOT && src[i + 1]?.type === T.LPAREN) return false;
|
|
327
|
+
// `uint(u)` / `f(x)`: a call or conversion is a value (a method
|
|
328
|
+
// signature inside `interface{ M() }` is the only type that has one).
|
|
329
|
+
if (tt === T.LBRACE) braces++;
|
|
330
|
+
else if (tt === T.RBRACE) braces--;
|
|
331
|
+
else if (_isCallParen(src, i, braces)) return false;
|
|
260
332
|
// Reject literals — it's an index expression
|
|
261
333
|
if (tt === T.INT || tt === T.FLOAT || tt === T.STRING) return false;
|
|
262
334
|
// Reject binary operators that wouldn't appear in type args
|
|
@@ -281,7 +353,7 @@ export const expressionParserMethods = {
|
|
|
281
353
|
const after = src[i + 1];
|
|
282
354
|
if (!after) return false;
|
|
283
355
|
// Call or composite lit — always valid as type args
|
|
284
|
-
if (after
|
|
356
|
+
if (_opensArgs(after, noCompLit)) return true;
|
|
285
357
|
// Function value in argument list: Identity[int], 99) — only if clearly a type
|
|
286
358
|
if (after.type === T.COMMA || after.type === T.RPAREN) {
|
|
287
359
|
return hasComma || hasTypeKeyword || hasUpperIdent;
|
|
@@ -307,17 +379,15 @@ export const expressionParserMethods = {
|
|
|
307
379
|
(t.type === T.MAP || isSliceOrArrayType) &&
|
|
308
380
|
!this.looksLikeCompositeLitArg()
|
|
309
381
|
) {
|
|
382
|
+
const start = this.pos;
|
|
310
383
|
const typeNode = this.parseType();
|
|
311
|
-
// []byte(s) / []
|
|
384
|
+
// `[]byte(s)` / `[]int(nil)` is a conversion: an ordinary
|
|
385
|
+
// operand that may be compared, spread or selected from.
|
|
312
386
|
if (this.check(T.LPAREN)) {
|
|
313
|
-
this.
|
|
314
|
-
const
|
|
315
|
-
this.
|
|
316
|
-
args.push(
|
|
317
|
-
kind: "TypeConversion",
|
|
318
|
-
targetType: typeNode,
|
|
319
|
-
expr: convExpr,
|
|
320
|
-
});
|
|
387
|
+
this.pos = start;
|
|
388
|
+
const arg = this.parseExpr();
|
|
389
|
+
if (this.match(T.ELLIPSIS)) arg._spread = true;
|
|
390
|
+
args.push(arg);
|
|
321
391
|
} else {
|
|
322
392
|
args.push({ kind: "TypeExpr", type: typeNode });
|
|
323
393
|
}
|
package/src/parser/statements.js
CHANGED
package/src/parser/templ.js
CHANGED
|
@@ -10,11 +10,12 @@
|
|
|
10
10
|
// TemplText { kind, value }
|
|
11
11
|
// TemplExpr { kind, tokens } ← raw Go token array
|
|
12
12
|
// TemplComponent { kind, tokens } ← raw Go token array for call
|
|
13
|
-
// TemplChildren { kind }
|
|
13
|
+
// TemplChildren { kind, name } ← `{ name... }`, name of a variadic slot
|
|
14
14
|
// TemplIf { kind, condTokens, then: TemplNode[], else_: TemplNode[] | null }
|
|
15
15
|
// TemplFor { kind, stmtTokens, body: TemplNode[] }
|
|
16
16
|
// TemplSwitch { kind, exprTokens, cases: TemplCase[] }
|
|
17
17
|
// TemplCase { kind, caseTokens: tokens | null (null = default), body: TemplNode[] }
|
|
18
|
+
// (a `case a, b:` also carries `exprs`, one expression per value)
|
|
18
19
|
//
|
|
19
20
|
// Go expressions embedded in templ (`{ expr }`, attribute values, `if`
|
|
20
21
|
// conditions, `switch` and `case` expressions, `@Comp(...)` calls) and `for`
|
|
@@ -71,12 +72,45 @@ function parseExprTokens(tokens, allowEmpty = true) {
|
|
|
71
72
|
if (allowEmpty && (!tokens || tokens.length === 0)) return { expr: null };
|
|
72
73
|
try {
|
|
73
74
|
const eofTok = new Token(T.EOF, "", 1, 1);
|
|
74
|
-
|
|
75
|
+
const p = new Parser([...tokens, eofTok]);
|
|
76
|
+
const expr = p.parseExpr();
|
|
77
|
+
while (p.check(T.SEMICOLON)) p.advance();
|
|
78
|
+
if (!p.check(T.EOF))
|
|
79
|
+
p.err(`unexpected '${p.peek().value}' after expression`);
|
|
80
|
+
return { expr };
|
|
75
81
|
} catch (exprError) {
|
|
76
82
|
return { expr: null, exprError };
|
|
77
83
|
}
|
|
78
84
|
}
|
|
79
85
|
|
|
86
|
+
// Parses `tokens` as a comma-separated expression list (`case 1, 2:`). `expr`
|
|
87
|
+
// and `exprError` describe the first expression (or the first error); `exprs`
|
|
88
|
+
// holds every parsed expression.
|
|
89
|
+
function parseExprListTokens(tokens) {
|
|
90
|
+
if (!tokens || tokens.length === 0) return { expr: null };
|
|
91
|
+
const parts = [[]];
|
|
92
|
+
let depth = 0;
|
|
93
|
+
for (const t of trimSemicolons(tokens ?? [])) {
|
|
94
|
+
if (t.type === T.LPAREN || t.type === T.LBRACKET || t.type === T.LBRACE)
|
|
95
|
+
depth++;
|
|
96
|
+
else if (
|
|
97
|
+
t.type === T.RPAREN ||
|
|
98
|
+
t.type === T.RBRACKET ||
|
|
99
|
+
t.type === T.RBRACE
|
|
100
|
+
)
|
|
101
|
+
depth--;
|
|
102
|
+
if (t.type === T.COMMA && depth === 0) parts.push([]);
|
|
103
|
+
else parts[parts.length - 1].push(t);
|
|
104
|
+
}
|
|
105
|
+
const exprs = [];
|
|
106
|
+
for (const part of parts) {
|
|
107
|
+
const parsed = parseExprTokens(part, false);
|
|
108
|
+
if (parsed.exprError) return { expr: null, exprError: parsed.exprError };
|
|
109
|
+
exprs.push(parsed.expr);
|
|
110
|
+
}
|
|
111
|
+
return { expr: exprs[0] ?? null, exprs };
|
|
112
|
+
}
|
|
113
|
+
|
|
80
114
|
// Parses an `if` condition; the lexer may leave the `if` keyword in front.
|
|
81
115
|
function parseCondTokens(tokens) {
|
|
82
116
|
if (!tokens || tokens.length === 0) return { expr: null };
|
|
@@ -220,7 +254,10 @@ export class TemplParser extends Parser {
|
|
|
220
254
|
return this.templComponent(t.value);
|
|
221
255
|
case TT.TEMPL_CHILDREN:
|
|
222
256
|
this.advance();
|
|
223
|
-
return
|
|
257
|
+
return attach(
|
|
258
|
+
{ kind: "TemplChildren", name: t.value },
|
|
259
|
+
parseExprTokens([new Token(T.IDENT, t.value, t.line, t.col)]),
|
|
260
|
+
);
|
|
224
261
|
case TT.TEMPL_IF: {
|
|
225
262
|
this.advance();
|
|
226
263
|
return this.parseTemplIf(t);
|
|
@@ -243,7 +280,9 @@ export class TemplParser extends Parser {
|
|
|
243
280
|
// `@templ.Raw(arg)` parses `arg`; any other component parses the whole call.
|
|
244
281
|
templComponent(tokens) {
|
|
245
282
|
const raw = isTemplRaw(tokens);
|
|
246
|
-
const parsed = parseExprTokens(
|
|
283
|
+
const parsed = parseExprTokens(
|
|
284
|
+
raw ? trimSemicolons(tokens).slice(4, -1) : tokens,
|
|
285
|
+
);
|
|
247
286
|
return attach({ kind: "TemplComponent", tokens }, { raw, ...parsed });
|
|
248
287
|
}
|
|
249
288
|
|
|
@@ -331,7 +370,7 @@ export class TemplParser extends Parser {
|
|
|
331
370
|
cases.push(
|
|
332
371
|
attach(
|
|
333
372
|
{ kind: "TemplCase", caseTokens, body },
|
|
334
|
-
isDefault ? { expr: null } :
|
|
373
|
+
isDefault ? { expr: null } : parseExprListTokens(caseTokens),
|
|
335
374
|
),
|
|
336
375
|
);
|
|
337
376
|
} else {
|
package/src/parser/types.js
CHANGED
package/src/resolver.js
CHANGED
|
@@ -39,8 +39,7 @@ export function isBuiltinPackage(importPath) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// Go-source stdlib: `stdlib/<import path>/*.go`, written in GoFront Go
|
|
42
|
-
// and compiled into the caller's target.
|
|
43
|
-
// in STDLIB_PACKAGES is an overlay: its declarations extend that namespace.
|
|
42
|
+
// and compiled into the caller's target.
|
|
44
43
|
const STDLIB_SOURCE_ROOT = fileURLToPath(
|
|
45
44
|
new URL("../stdlib/", import.meta.url),
|
|
46
45
|
);
|