gofront 1.8.0 → 1.9.1
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 +6 -5
- package/src/backend/js/expressions.js +1157 -408
- package/src/backend/js/index.js +327 -153
- package/src/backend/js/natives.js +383 -0
- package/src/backend/js/runtime.js +559 -2552
- package/src/backend/js/statements.js +670 -158
- package/src/backend/js/stdlib/index.js +45 -93
- package/src/backend/js/templ.js +32 -45
- package/src/backend/js/type-info.js +298 -19
- package/src/backend/shared/fixed-float.js +20 -0
- package/src/backend/shared/fmt-types.js +91 -170
- package/src/backend/shared/host-unicode.js +36 -0
- package/src/backend/wasm/boundary.js +490 -965
- package/src/backend/wasm/emit-builtins.js +884 -256
- package/src/backend/wasm/emit-exprs.js +185 -77
- package/src/backend/wasm/emit-maps.js +17 -367
- package/src/backend/wasm/emit-shared.js +4 -1
- package/src/backend/wasm/emit-stmts.js +524 -240
- package/src/backend/wasm/emit.js +176 -38
- package/src/backend/wasm/globals.js +48 -8
- package/src/backend/wasm/glue.js +2 -4
- package/src/backend/wasm/index.js +44 -530
- package/src/backend/wasm/method-expr.js +12 -1
- package/src/backend/wasm/module-emitter.js +299 -770
- package/src/backend/wasm/monomorph.js +139 -8
- package/src/backend/wasm/natives.js +600 -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 +97 -20
- package/src/backend/wasm/variadic.js +9 -4
- package/src/cli/cli-core.js +5 -1
- package/src/cli/test-runner.js +115 -342
- package/src/compiler.js +113 -130
- package/src/lexer/lexer.js +106 -11
- package/src/lexer/templ-lexer.js +61 -18
- package/src/lexer/tokens.js +1 -0
- package/src/link/stdlib.js +355 -132
- package/src/link/type-names.js +204 -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 +62 -5
- package/src/lower/errors-as.js +196 -0
- package/src/lower/fmt-formatters.js +929 -0
- package/src/lower/functions.js +225 -7
- package/src/lower/gen-types.js +293 -0
- package/src/lower/index.js +14 -2
- package/src/lower/json-codecs.js +334 -245
- 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 +30 -5
- package/src/parser/expressions.js +111 -57
- package/src/parser/statements.js +108 -0
- package/src/parser/templ.js +44 -5
- package/src/parser/types.js +12 -0
- package/src/resolver.js +1 -2
- package/src/typechecker/assignability.js +214 -19
- package/src/typechecker/const-fold.js +70 -27
- package/src/typechecker/constants.js +156 -8
- package/src/typechecker/expressions.js +490 -108
- package/src/typechecker/index.js +346 -42
- package/src/typechecker/init-order.js +136 -40
- package/src/typechecker/operand-text.js +41 -0
- package/src/typechecker/resolve.js +490 -65
- package/src/typechecker/statements.js +288 -27
- 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 +287 -575
- 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,18 @@ 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, a type-switch
|
|
231
|
+
// `case func():` or a type argument `f[func(int)]`), `,`, `)`, `]` and
|
|
232
|
+
// `:` also end the type
|
|
233
|
+
if (
|
|
234
|
+
inTypeExpr &&
|
|
235
|
+
(this.check(T.COMMA) ||
|
|
236
|
+
this.check(T.RPAREN) ||
|
|
237
|
+
this.check(T.RBRACKET) ||
|
|
238
|
+
this.check(T.COLON) ||
|
|
239
|
+
this.check(T.STRING) ||
|
|
240
|
+
this.check(T.ASSIGN))
|
|
241
|
+
)
|
|
217
242
|
return null;
|
|
218
243
|
// Multiple return values: (int, string) or named (result int, ok bool)
|
|
219
244
|
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
|
}
|
|
@@ -332,48 +402,20 @@ export const expressionParserMethods = {
|
|
|
332
402
|
return { kind: "CallExpr", func: fn, args };
|
|
333
403
|
},
|
|
334
404
|
|
|
335
|
-
// Returns true if the current position
|
|
336
|
-
// e.g. []int{1,2,3}
|
|
337
|
-
//
|
|
405
|
+
// Returns true if the current position starts a composite literal argument,
|
|
406
|
+
// e.g. []int{1,2,3}, map[string]int{"a":1} or []func() int{f}: the type is
|
|
407
|
+
// parsed speculatively and must be followed by `{`. The position is
|
|
408
|
+
// always restored.
|
|
338
409
|
looksLikeCompositeLitArg() {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
depth++;
|
|
348
|
-
i++;
|
|
349
|
-
continue;
|
|
350
|
-
}
|
|
351
|
-
if (tt === T.RBRACKET) {
|
|
352
|
-
depth--;
|
|
353
|
-
i++;
|
|
354
|
-
continue;
|
|
355
|
-
}
|
|
356
|
-
if (depth > 0) {
|
|
357
|
-
i++;
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
if (
|
|
361
|
-
tt === T.MAP ||
|
|
362
|
-
tt === T.IDENT ||
|
|
363
|
-
tt === T.DOT || // qualified types: []pkg.Type{...}
|
|
364
|
-
tt === T.STAR ||
|
|
365
|
-
tt === T.INT ||
|
|
366
|
-
tt === T.FLOAT ||
|
|
367
|
-
tt === T.STRING
|
|
368
|
-
) {
|
|
369
|
-
i++;
|
|
370
|
-
continue;
|
|
371
|
-
}
|
|
372
|
-
break;
|
|
410
|
+
const start = this.pos;
|
|
411
|
+
try {
|
|
412
|
+
this.parseType();
|
|
413
|
+
return this.check(T.LBRACE);
|
|
414
|
+
} catch {
|
|
415
|
+
return false;
|
|
416
|
+
} finally {
|
|
417
|
+
this.pos = start;
|
|
373
418
|
}
|
|
374
|
-
// Skip whitespace tokens (shouldn't exist post-lex, but be safe)
|
|
375
|
-
while (i < src.length && src[i].type === T.SEMICOLON) i++;
|
|
376
|
-
return i < src.length && src[i].type === T.LBRACE;
|
|
377
419
|
},
|
|
378
420
|
|
|
379
421
|
parseCompositeLit(typeExpr) {
|
|
@@ -424,7 +466,9 @@ export const expressionParserMethods = {
|
|
|
424
466
|
// Literals
|
|
425
467
|
if (t.type === T.INT) {
|
|
426
468
|
this.advance();
|
|
427
|
-
|
|
469
|
+
const lit = { kind: "BasicLit", litKind: "INT", value: t.value };
|
|
470
|
+
if (t.rune) lit.rune = true;
|
|
471
|
+
return lit;
|
|
428
472
|
}
|
|
429
473
|
if (t.type === T.FLOAT) {
|
|
430
474
|
this.advance();
|
|
@@ -519,6 +563,16 @@ export const expressionParserMethods = {
|
|
|
519
563
|
return this.parseCompositeLit(typeExpr);
|
|
520
564
|
}
|
|
521
565
|
|
|
566
|
+
// Interface conversion: interface{ M() }(v). `interface{…}{` is not a
|
|
567
|
+
// composite literal in Go, so only the conversion form is accepted.
|
|
568
|
+
if (t.type === T.INTERFACE) {
|
|
569
|
+
const targetType = this.parseInterfaceType();
|
|
570
|
+
this.expect(T.LPAREN);
|
|
571
|
+
const expr = this.parseExpr();
|
|
572
|
+
this.expect(T.RPAREN);
|
|
573
|
+
return { kind: "TypeConversion", targetType, expr };
|
|
574
|
+
}
|
|
575
|
+
|
|
522
576
|
if (t.type === T.CHAN) {
|
|
523
577
|
this.err("channels are not supported in GoFront");
|
|
524
578
|
}
|
package/src/parser/statements.js
CHANGED
|
@@ -2,6 +2,57 @@
|
|
|
2
2
|
|
|
3
3
|
import { T } from "../lexer/tokens.js";
|
|
4
4
|
|
|
5
|
+
// Statement keywords: none of them can appear inside a composite literal or a
|
|
6
|
+
// statement header's expressions.
|
|
7
|
+
const STMT_KEYWORDS = new Set([
|
|
8
|
+
T.VAR,
|
|
9
|
+
T.CONST,
|
|
10
|
+
T.TYPE,
|
|
11
|
+
T.IF,
|
|
12
|
+
T.ELSE,
|
|
13
|
+
T.FOR,
|
|
14
|
+
T.RETURN,
|
|
15
|
+
T.BREAK,
|
|
16
|
+
T.CONTINUE,
|
|
17
|
+
T.SWITCH,
|
|
18
|
+
T.CASE,
|
|
19
|
+
T.DEFAULT,
|
|
20
|
+
T.SELECT,
|
|
21
|
+
T.DEFER,
|
|
22
|
+
T.GO,
|
|
23
|
+
T.FALLTHROUGH,
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
// Tokens that only appear in statements: a brace pair holding one at its top
|
|
27
|
+
// level is a block, not a composite literal.
|
|
28
|
+
const STMT_ONLY_TOKENS = new Set([
|
|
29
|
+
...STMT_KEYWORDS,
|
|
30
|
+
T.SEMICOLON,
|
|
31
|
+
T.ASSIGN,
|
|
32
|
+
T.DEFINE,
|
|
33
|
+
T.PLUS_ASSIGN,
|
|
34
|
+
T.MINUS_ASSIGN,
|
|
35
|
+
T.STAR_ASSIGN,
|
|
36
|
+
T.SLASH_ASSIGN,
|
|
37
|
+
T.PERCENT_ASSIGN,
|
|
38
|
+
T.AMP_ASSIGN,
|
|
39
|
+
T.PIPE_ASSIGN,
|
|
40
|
+
T.AND_NOT_ASSIGN,
|
|
41
|
+
T.CARET_ASSIGN,
|
|
42
|
+
T.LSHIFT_ASSIGN,
|
|
43
|
+
T.RSHIFT_ASSIGN,
|
|
44
|
+
T.INC,
|
|
45
|
+
T.DEC,
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
const OPEN_BRACKETS = new Set([T.LBRACE, T.LPAREN, T.LBRACKET]);
|
|
49
|
+
const CLOSE_BRACKETS = new Set([T.RBRACE, T.RPAREN, T.RBRACKET]);
|
|
50
|
+
|
|
51
|
+
function bracketDelta(tt) {
|
|
52
|
+
if (OPEN_BRACKETS.has(tt)) return 1;
|
|
53
|
+
return CLOSE_BRACKETS.has(tt) ? -1 : 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
5
56
|
// Method-name dispatch for pure-delegation cases in _parseStmt.
|
|
6
57
|
const PARSE_STMT_DELEGATE = {
|
|
7
58
|
[T.VAR]: "parseVarDecl",
|
|
@@ -24,6 +75,7 @@ const ASSIGN_OPS = new Set([
|
|
|
24
75
|
T.PERCENT_ASSIGN,
|
|
25
76
|
T.AMP_ASSIGN,
|
|
26
77
|
T.PIPE_ASSIGN,
|
|
78
|
+
T.AND_NOT_ASSIGN,
|
|
27
79
|
T.CARET_ASSIGN,
|
|
28
80
|
T.LSHIFT_ASSIGN,
|
|
29
81
|
T.RSHIFT_ASSIGN,
|
|
@@ -129,6 +181,7 @@ export const statementParserMethods = {
|
|
|
129
181
|
let cond;
|
|
130
182
|
// if init; cond { } — use noCompLit to avoid "{" being parsed as composite literal
|
|
131
183
|
const first = this.parseSimpleStmtRaw(true);
|
|
184
|
+
this._rejectHeaderCompositeLit("if", 0, first);
|
|
132
185
|
// Only treat as "init; cond" if semicolon is followed by a non-brace token
|
|
133
186
|
if (this.check(T.SEMICOLON) && this.peek2().type !== T.LBRACE) {
|
|
134
187
|
this.advance(); // consume ;
|
|
@@ -165,6 +218,7 @@ export const statementParserMethods = {
|
|
|
165
218
|
let first = null;
|
|
166
219
|
if (!this.check(T.SEMICOLON)) {
|
|
167
220
|
first = this.parseSimpleStmtRaw(true);
|
|
221
|
+
this._rejectHeaderCompositeLit("for", 1, first);
|
|
168
222
|
|
|
169
223
|
if (this.check(T.LBRACE)) {
|
|
170
224
|
// for k, v := range expr { } — range stmt ends right before {
|
|
@@ -200,6 +254,59 @@ export const statementParserMethods = {
|
|
|
200
254
|
return { kind: "ForStmt", init: first, cond, post, body };
|
|
201
255
|
},
|
|
202
256
|
|
|
257
|
+
// `if s := &T{f: 1}; s.f > 0 {`: in a header, `{` after a type name opens
|
|
258
|
+
// the body, so Go requires the literal to be parenthesized. Reported when
|
|
259
|
+
// the brace pair holds no statement tokens, is followed by an explicit `;`,
|
|
260
|
+
// and the header then continues to its body `{` (with at most `semis`
|
|
261
|
+
// further `;`, one for a `for` post statement). A bare expression or a
|
|
262
|
+
// range clause before the `{` (`if ok { f() };`) means it is the body.
|
|
263
|
+
_rejectHeaderCompositeLit(keyword, semis, first) {
|
|
264
|
+
if (!this.check(T.LBRACE)) return;
|
|
265
|
+
if (first?.kind === "ExprStmt" || this.isRangeStmt(first)) return;
|
|
266
|
+
const prev = this.tokens[this.pos - 1]?.type;
|
|
267
|
+
if (prev !== T.IDENT && prev !== T.RBRACKET) return;
|
|
268
|
+
const close = this._closingBrace(this.pos);
|
|
269
|
+
if (close < 0) return;
|
|
270
|
+
const semi = this.tokens[close + 1];
|
|
271
|
+
if (semi?.type !== T.SEMICOLON || !this._isExplicitSemi(semi)) return;
|
|
272
|
+
if (!this._headerContinues(close + 2, semis)) return;
|
|
273
|
+
this.err(`composite literal in ${keyword} header must be parenthesized`);
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
// Index of the `}` matching the `{` at `open`, or -1 when the pair holds a
|
|
277
|
+
// statement-only token at its top level (then it is a block).
|
|
278
|
+
_closingBrace(open) {
|
|
279
|
+
let depth = 0;
|
|
280
|
+
for (let i = open; i < this.tokens.length; i++) {
|
|
281
|
+
const tt = this.tokens[i].type;
|
|
282
|
+
depth += bracketDelta(tt);
|
|
283
|
+
if (depth === 0) return tt === T.RBRACE ? i : -1;
|
|
284
|
+
if (tt === T.EOF || (depth === 1 && STMT_ONLY_TOKENS.has(tt))) return -1;
|
|
285
|
+
}
|
|
286
|
+
return -1;
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
_isExplicitSemi(tok) {
|
|
290
|
+
if (!this.source) return false;
|
|
291
|
+
this._sourceLines ??= this.source.split("\n");
|
|
292
|
+
return this._sourceLines[tok.line - 1]?.[tok.col - 1] === ";";
|
|
293
|
+
},
|
|
294
|
+
|
|
295
|
+
// Whether the tokens from `i` reach a top-level `{` (the body) without a
|
|
296
|
+
// statement keyword and with at most `semis` semicolons on the way.
|
|
297
|
+
_headerContinues(i, semis) {
|
|
298
|
+
let depth = 0;
|
|
299
|
+
for (; i < this.tokens.length; i++) {
|
|
300
|
+
const tt = this.tokens[i].type;
|
|
301
|
+
if (tt === T.LBRACE && depth === 0) return true;
|
|
302
|
+
depth += bracketDelta(tt);
|
|
303
|
+
if (tt === T.SEMICOLON && depth === 0) semis--;
|
|
304
|
+
if (depth < 0 || semis < 0) return false;
|
|
305
|
+
if (tt === T.EOF || STMT_KEYWORDS.has(tt)) return false;
|
|
306
|
+
}
|
|
307
|
+
return false;
|
|
308
|
+
},
|
|
309
|
+
|
|
203
310
|
isRangeStmt(stmt) {
|
|
204
311
|
if (!stmt) return false;
|
|
205
312
|
if (stmt.kind !== "DefineStmt" && stmt.kind !== "AssignStmt") return false;
|
|
@@ -212,6 +319,7 @@ export const statementParserMethods = {
|
|
|
212
319
|
let tag = null;
|
|
213
320
|
if (!this.check(T.LBRACE)) {
|
|
214
321
|
const first = this.parseSimpleStmtRaw(true);
|
|
322
|
+
this._rejectHeaderCompositeLit("switch", 0, first);
|
|
215
323
|
if (this.match(T.SEMICOLON)) {
|
|
216
324
|
init = first;
|
|
217
325
|
if (!this.check(T.LBRACE))
|