gofront 1.7.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 +14 -14
- package/package.json +10 -38
- package/src/backend/js/expressions.js +2060 -389
- package/src/backend/js/index.js +586 -174
- package/src/backend/js/natives.js +383 -0
- package/src/backend/js/runtime.js +936 -1644
- package/src/backend/js/statements.js +1349 -132
- package/src/backend/js/stdlib/index.js +51 -94
- package/src/backend/js/templ.js +67 -118
- package/src/backend/js/type-info.js +436 -0
- package/src/backend/shared/fixed-float.js +20 -0
- package/src/backend/shared/fmt-types.js +80 -0
- package/src/backend/shared/host-unicode.js +36 -0
- package/src/backend/wasm/boundary.js +753 -872
- package/src/backend/wasm/emit-builtins.js +914 -237
- package/src/backend/wasm/emit-exprs.js +1456 -195
- package/src/backend/wasm/emit-maps.js +78 -380
- package/src/backend/wasm/emit-stmts.js +1659 -214
- package/src/backend/wasm/emit.js +614 -88
- package/src/backend/wasm/encode.js +43 -13
- package/src/backend/wasm/globals.js +491 -0
- package/src/backend/wasm/glue.js +2 -4
- package/src/backend/wasm/index.js +406 -3195
- package/src/backend/wasm/method-expr.js +392 -0
- package/src/backend/wasm/module-emitter.js +1615 -0
- package/src/backend/wasm/monomorph.js +164 -9
- package/src/backend/wasm/natives.js +596 -0
- package/src/backend/wasm/prescan.js +249 -0
- package/src/backend/wasm/prune-funcs.js +89 -0
- package/src/backend/wasm/stdlib/index.js +2 -37
- package/src/backend/wasm/stdlib/simd.js +293 -28
- package/src/backend/wasm/types.js +260 -27
- package/src/backend/wasm/variadic.js +93 -0
- package/src/backend/wasm/wat.js +47 -20
- package/src/{cli-core.js → cli/cli-core.js} +241 -11
- package/src/{test-runner.js → cli/test-runner.js} +188 -122
- package/src/{vendor.js → cli/vendor.js} +3 -4
- package/src/compiler.js +202 -386
- package/src/index.js +15 -169
- package/src/{lexer.js → lexer/lexer.js} +203 -62
- package/src/{templ-lexer.js → lexer/templ-lexer.js} +61 -18
- package/src/{tokens.js → lexer/tokens.js} +2 -0
- package/src/link/sourcemap.js +97 -0
- package/src/link/stdlib.js +737 -0
- package/src/link/type-names.js +148 -0
- package/src/link/wasm.js +318 -0
- package/src/lower/bounds.js +547 -0
- package/src/lower/boxing.js +1 -1
- package/src/lower/captures.js +267 -64
- 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 +33 -19
- package/src/lower/json-codecs.js +1379 -0
- package/src/lower/ownership.js +47 -8
- package/src/lower/range.js +86 -1
- package/src/lower/reachability.js +150 -0
- package/src/lower/walk.js +12 -0
- package/src/parser/declarations.js +83 -15
- package/src/parser/expressions.js +110 -18
- package/src/parser/index.js +2 -1
- package/src/parser/statements.js +38 -22
- package/src/{templ-parser.js → parser/templ.js} +179 -17
- package/src/parser/types.js +70 -9
- package/src/resolver.js +21 -1
- package/src/typechecker/assignability.js +291 -19
- package/src/typechecker/const-fold.js +399 -0
- package/src/typechecker/constants.js +282 -0
- package/src/typechecker/expressions.js +1220 -183
- package/src/typechecker/index.js +659 -92
- package/src/typechecker/init-order.js +426 -0
- package/src/typechecker/operand-text.js +40 -0
- package/src/typechecker/resolve.js +613 -62
- package/src/typechecker/statements.js +212 -20
- package/src/typechecker/stdlib/excluded.js +902 -0
- package/src/typechecker/stdlib/index.js +38 -0
- package/src/typechecker/stdlib/simd.js +65 -15
- package/src/typechecker/termination.js +11 -2
- package/src/typechecker/types.js +268 -514
- package/stdlib/bytes/buffer.go +479 -0
- package/stdlib/bytes/bytes.go +1010 -0
- package/stdlib/bytes/reader.go +161 -0
- package/stdlib/cmp/cmp.go +65 -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/errors.go +31 -0
- package/stdlib/encoding/json/indent.go +71 -0
- package/stdlib/encoding/json/json.go +62 -0
- package/stdlib/encoding/json/number.go +12 -0
- package/stdlib/encoding/json/stream.go +173 -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/hash/hash.go +53 -0
- package/stdlib/html/html.go +251 -0
- package/stdlib/internal/fmtrt/fmtrt.go +163 -0
- package/stdlib/internal/jsonrt/dec.go +871 -0
- package/stdlib/internal/jsonrt/enc.go +516 -0
- package/stdlib/internal/jsonrt/indent.go +135 -0
- package/stdlib/internal/jsonrt/number.go +27 -0
- package/stdlib/internal/jsonrt/reader.go +73 -0
- package/stdlib/internal/jsonrt/scanner.go +658 -0
- package/stdlib/io/io.go +502 -0
- package/stdlib/iter/iter.go +18 -0
- package/stdlib/maps/maps.go +131 -0
- package/stdlib/math/bits/bits.go +507 -0
- package/stdlib/math/extra.go +272 -0
- 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 +500 -0
- 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 +526 -0
- 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 +250 -0
- package/stdlib/unicode/utf8/utf8.go +464 -0
- 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 -46
- package/src/backend/js/stdlib/builder.js +0 -63
- package/src/backend/js/stdlib/bytes.js +0 -122
- package/src/backend/js/stdlib/errors.js +0 -44
- package/src/backend/js/stdlib/fmt.js +0 -178
- 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 -972
- package/src/backend/js/stdlib/maps.js +0 -45
- package/src/backend/js/stdlib/math.js +0 -69
- 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 -24
- package/src/backend/js/stdlib/slices.js +0 -77
- package/src/backend/js/stdlib/sort.js +0 -30
- package/src/backend/js/stdlib/strconv.js +0 -74
- package/src/backend/js/stdlib/strings.js +0 -222
- package/src/backend/js/stdlib/sync.js +0 -13
- package/src/backend/js/stdlib/testing.js +0 -34
- package/src/backend/js/stdlib/time.js +0 -128
- package/src/backend/js/stdlib/unicode.js +0 -25
- package/src/backend/js/stdlib/utf8.js +0 -26
- package/src/backend/wasm/const-fold.js +0 -205
- package/src/backend/wasm/emit-stdlib.js +0 -13
- package/src/backend/wasm/stdlib/bytes.js +0 -5039
- package/src/backend/wasm/stdlib/core.js +0 -8903
- package/src/backend/wasm/stdlib/io.js +0 -2161
- package/src/backend/wasm/stdlib/packages.js +0 -2400
- package/src/backend/wasm/stdlib/pure.js +0 -720
- package/src/backend/wasm/stdlib/time.js +0 -1200
- package/src/typechecker/stdlib/core.js +0 -801
- package/src/typechecker/stdlib/extended.js +0 -914
- package/src/typechecker/stdlib.js +0 -22
- /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
- /package/src/{colors.js → cli/colors.js} +0 -0
- /package/src/{dev-server.js → cli/dev-server.js} +0 -0
- /package/src/{minifier.js → cli/minifier.js} +0 -0
- /package/src/{project-config.js → cli/project-config.js} +0 -0
- /package/src/{pwa.js → cli/pwa.js} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Parser expression methods — installed as a mixin on Parser.prototype.
|
|
2
2
|
|
|
3
|
-
import { T } from "../tokens.js";
|
|
3
|
+
import { T } from "../lexer/tokens.js";
|
|
4
4
|
|
|
5
5
|
// Binary operator precedence (higher = binds tighter, matching Go spec)
|
|
6
6
|
const PREC = {
|
|
@@ -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 ||
|
|
@@ -108,9 +120,9 @@ function _isTypeIntroducerToken(tt) {
|
|
|
108
120
|
}
|
|
109
121
|
|
|
110
122
|
export const expressionParserMethods = {
|
|
111
|
-
parseExprList() {
|
|
112
|
-
const exprs = [this.parseExpr()];
|
|
113
|
-
while (this.match(T.COMMA)) exprs.push(this.parseExpr());
|
|
123
|
+
parseExprList(noCompLit = false) {
|
|
124
|
+
const exprs = [this.parseExpr(0, noCompLit)];
|
|
125
|
+
while (this.match(T.COMMA)) exprs.push(this.parseExpr(0, noCompLit));
|
|
114
126
|
return exprs;
|
|
115
127
|
},
|
|
116
128
|
|
|
@@ -132,7 +144,13 @@ export const expressionParserMethods = {
|
|
|
132
144
|
const t = this.peek();
|
|
133
145
|
if (UNARY_OPS.has(t.type)) {
|
|
134
146
|
const op = this.advance().value;
|
|
135
|
-
return {
|
|
147
|
+
return {
|
|
148
|
+
kind: "UnaryExpr",
|
|
149
|
+
op,
|
|
150
|
+
operand: this.parseUnary(noCompLit),
|
|
151
|
+
_line: t.line,
|
|
152
|
+
_col: t.col,
|
|
153
|
+
};
|
|
136
154
|
}
|
|
137
155
|
if (t.type === T.AWAIT) {
|
|
138
156
|
this.advance();
|
|
@@ -166,7 +184,21 @@ export const expressionParserMethods = {
|
|
|
166
184
|
}
|
|
167
185
|
} else if (t.type === T.LBRACKET) {
|
|
168
186
|
// Check if this looks like generic type args: Ident[Type](...) or Ident[Type]{...}
|
|
169
|
-
|
|
187
|
+
// `pkg.F[int](x)` / `pkg.T[X]{...}` instantiate through a qualifier.
|
|
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) {
|
|
170
202
|
this.advance(); // consume [
|
|
171
203
|
const typeArgs = [this.parseType()];
|
|
172
204
|
while (this.match(T.COMMA)) typeArgs.push(this.parseType());
|
|
@@ -222,15 +254,59 @@ export const expressionParserMethods = {
|
|
|
222
254
|
);
|
|
223
255
|
},
|
|
224
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
|
+
|
|
225
300
|
// Lookahead: does the [ ... ] after an Ident look like generic type args?
|
|
226
301
|
// Returns true if: only type-like tokens inside brackets, AND followed by a valid continuation
|
|
227
|
-
looksLikeTypeArgList() {
|
|
302
|
+
looksLikeTypeArgList(noCompLit = false) {
|
|
228
303
|
let i = this.pos + 1; // skip [
|
|
229
304
|
const src = this.tokens;
|
|
230
305
|
let depth = 1;
|
|
231
306
|
let hasComma = false;
|
|
232
307
|
let hasTypeKeyword = false;
|
|
233
308
|
let hasUpperIdent = false;
|
|
309
|
+
let braces = 0;
|
|
234
310
|
while (i < src.length && depth > 0) {
|
|
235
311
|
const tt = src[i].type;
|
|
236
312
|
if (tt === T.LBRACKET) {
|
|
@@ -241,9 +317,18 @@ export const expressionParserMethods = {
|
|
|
241
317
|
if (tt === T.RBRACKET) {
|
|
242
318
|
depth--;
|
|
243
319
|
if (depth === 0) break;
|
|
320
|
+
// `a[i].F` selects from an index result: a value, never a type.
|
|
321
|
+
if (src[i + 1]?.type === T.DOT) return false;
|
|
244
322
|
i++;
|
|
245
323
|
continue;
|
|
246
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;
|
|
247
332
|
// Reject literals — it's an index expression
|
|
248
333
|
if (tt === T.INT || tt === T.FLOAT || tt === T.STRING) return false;
|
|
249
334
|
// Reject binary operators that wouldn't appear in type args
|
|
@@ -268,7 +353,7 @@ export const expressionParserMethods = {
|
|
|
268
353
|
const after = src[i + 1];
|
|
269
354
|
if (!after) return false;
|
|
270
355
|
// Call or composite lit — always valid as type args
|
|
271
|
-
if (after
|
|
356
|
+
if (_opensArgs(after, noCompLit)) return true;
|
|
272
357
|
// Function value in argument list: Identity[int], 99) — only if clearly a type
|
|
273
358
|
if (after.type === T.COMMA || after.type === T.RPAREN) {
|
|
274
359
|
return hasComma || hasTypeKeyword || hasUpperIdent;
|
|
@@ -294,17 +379,15 @@ export const expressionParserMethods = {
|
|
|
294
379
|
(t.type === T.MAP || isSliceOrArrayType) &&
|
|
295
380
|
!this.looksLikeCompositeLitArg()
|
|
296
381
|
) {
|
|
382
|
+
const start = this.pos;
|
|
297
383
|
const typeNode = this.parseType();
|
|
298
|
-
// []byte(s) / []
|
|
384
|
+
// `[]byte(s)` / `[]int(nil)` is a conversion: an ordinary
|
|
385
|
+
// operand that may be compared, spread or selected from.
|
|
299
386
|
if (this.check(T.LPAREN)) {
|
|
300
|
-
this.
|
|
301
|
-
const
|
|
302
|
-
this.
|
|
303
|
-
args.push(
|
|
304
|
-
kind: "TypeConversion",
|
|
305
|
-
targetType: typeNode,
|
|
306
|
-
expr: convExpr,
|
|
307
|
-
});
|
|
387
|
+
this.pos = start;
|
|
388
|
+
const arg = this.parseExpr();
|
|
389
|
+
if (this.match(T.ELLIPSIS)) arg._spread = true;
|
|
390
|
+
args.push(arg);
|
|
308
391
|
} else {
|
|
309
392
|
args.push({ kind: "TypeExpr", type: typeNode });
|
|
310
393
|
}
|
|
@@ -369,7 +452,16 @@ export const expressionParserMethods = {
|
|
|
369
452
|
while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
|
|
370
453
|
// Implicit composite literal: {X: 1} inside []Point{{X:1}} or map[string]Point{"a": {X:1}}
|
|
371
454
|
if (this.check(T.LBRACE)) {
|
|
372
|
-
|
|
455
|
+
const lit = this.parseCompositeLit(null); // null typeExpr = inferred
|
|
456
|
+
if (this.match(T.COLON)) {
|
|
457
|
+
// Elided key type: map[P]int{{1}: 2}
|
|
458
|
+
const val = this.check(T.LBRACE)
|
|
459
|
+
? this.parseCompositeLit(null)
|
|
460
|
+
: this.parseExpr();
|
|
461
|
+
elems.push({ kind: "KeyValueExpr", key: lit, value: val });
|
|
462
|
+
} else {
|
|
463
|
+
elems.push(lit);
|
|
464
|
+
}
|
|
373
465
|
} else {
|
|
374
466
|
const first = this.parseExpr();
|
|
375
467
|
if (this.match(T.COLON)) {
|
package/src/parser/index.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// CallExpr, SelectorExpr, IndexExpr, SliceExpr
|
|
18
18
|
// CompositeLit, KeyValueExpr, FuncLit, AwaitExpr
|
|
19
19
|
|
|
20
|
-
import { T } from "../tokens.js";
|
|
20
|
+
import { T } from "../lexer/tokens.js";
|
|
21
21
|
import { declarationParseMethods } from "./declarations.js";
|
|
22
22
|
import { expressionParserMethods } from "./expressions.js";
|
|
23
23
|
import { statementParserMethods } from "./statements.js";
|
|
@@ -131,6 +131,7 @@ export class Parser {
|
|
|
131
131
|
decls,
|
|
132
132
|
target: this.tokens?.target ?? null,
|
|
133
133
|
_targetDirective: this.tokens?.targetDirective ?? null,
|
|
134
|
+
_embedDirectives: this.tokens?.embedDirectives ?? [],
|
|
134
135
|
_filename: this.filename,
|
|
135
136
|
_source: this.source,
|
|
136
137
|
};
|
package/src/parser/statements.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Parser statement methods — installed as a mixin on Parser.prototype.
|
|
2
2
|
|
|
3
|
-
import { T } from "../tokens.js";
|
|
3
|
+
import { T } from "../lexer/tokens.js";
|
|
4
4
|
|
|
5
5
|
// Method-name dispatch for pure-delegation cases in _parseStmt.
|
|
6
6
|
const PARSE_STMT_DELEGATE = {
|
|
@@ -24,6 +24,7 @@ const ASSIGN_OPS = new Set([
|
|
|
24
24
|
T.PERCENT_ASSIGN,
|
|
25
25
|
T.AMP_ASSIGN,
|
|
26
26
|
T.PIPE_ASSIGN,
|
|
27
|
+
T.AND_NOT_ASSIGN,
|
|
27
28
|
T.CARET_ASSIGN,
|
|
28
29
|
T.LSHIFT_ASSIGN,
|
|
29
30
|
T.RSHIFT_ASSIGN,
|
|
@@ -78,6 +79,13 @@ export const statementParserMethods = {
|
|
|
78
79
|
this.advance();
|
|
79
80
|
this.semi();
|
|
80
81
|
return { kind: "BranchStmt", keyword: "fallthrough" };
|
|
82
|
+
case T.GOTO: {
|
|
83
|
+
// Parsed so the checker can reject it with a targeted message.
|
|
84
|
+
this.advance();
|
|
85
|
+
const label = this.expect(T.IDENT).value;
|
|
86
|
+
this.semi();
|
|
87
|
+
return { kind: "BranchStmt", keyword: "goto", label };
|
|
88
|
+
}
|
|
81
89
|
case T.GO:
|
|
82
90
|
return this.err("goroutines are not supported in GoFront");
|
|
83
91
|
case T.SELECT:
|
|
@@ -88,7 +96,12 @@ export const statementParserMethods = {
|
|
|
88
96
|
if (this.check2(T.COLON)) {
|
|
89
97
|
const label = this.advance().value;
|
|
90
98
|
this.advance(); // consume COLON
|
|
91
|
-
|
|
99
|
+
// A label may precede the end of a block: the statement is empty.
|
|
100
|
+
const body =
|
|
101
|
+
this.check(T.RBRACE) || this.check(T.CASE) || this.check(T.DEFAULT)
|
|
102
|
+
? { kind: "Block", stmts: [] }
|
|
103
|
+
: this.parseStmt();
|
|
104
|
+
return { kind: "LabeledStmt", label, body };
|
|
92
105
|
}
|
|
93
106
|
return this.parseSimpleStmt();
|
|
94
107
|
default:
|
|
@@ -150,27 +163,30 @@ export const statementParserMethods = {
|
|
|
150
163
|
};
|
|
151
164
|
}
|
|
152
165
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (this.
|
|
166
|
+
let first = null;
|
|
167
|
+
if (!this.check(T.SEMICOLON)) {
|
|
168
|
+
first = this.parseSimpleStmtRaw(true);
|
|
169
|
+
|
|
170
|
+
if (this.check(T.LBRACE)) {
|
|
171
|
+
// for k, v := range expr { } — range stmt ends right before {
|
|
172
|
+
if (this.isRangeStmt(first)) {
|
|
173
|
+
return {
|
|
174
|
+
kind: "ForStmt",
|
|
175
|
+
init: first,
|
|
176
|
+
cond: null,
|
|
177
|
+
post: null,
|
|
178
|
+
body: this.parseBlock(),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
// for cond { }
|
|
158
182
|
return {
|
|
159
183
|
kind: "ForStmt",
|
|
160
|
-
init:
|
|
161
|
-
cond:
|
|
184
|
+
init: null,
|
|
185
|
+
cond: this.stmtToExpr(first),
|
|
162
186
|
post: null,
|
|
163
187
|
body: this.parseBlock(),
|
|
164
188
|
};
|
|
165
189
|
}
|
|
166
|
-
// for cond { }
|
|
167
|
-
return {
|
|
168
|
-
kind: "ForStmt",
|
|
169
|
-
init: null,
|
|
170
|
-
cond: this.stmtToExpr(first),
|
|
171
|
-
post: null,
|
|
172
|
-
body: this.parseBlock(),
|
|
173
|
-
};
|
|
174
190
|
}
|
|
175
191
|
|
|
176
192
|
this.expect(T.SEMICOLON);
|
|
@@ -180,7 +196,7 @@ export const statementParserMethods = {
|
|
|
180
196
|
if (!this.check(T.SEMICOLON)) cond = this.parseExpr(0, true);
|
|
181
197
|
this.expect(T.SEMICOLON);
|
|
182
198
|
let post = null;
|
|
183
|
-
if (!this.check(T.LBRACE)) post = this.parseSimpleStmtRaw();
|
|
199
|
+
if (!this.check(T.LBRACE)) post = this.parseSimpleStmtRaw(true);
|
|
184
200
|
const body = this.parseBlock();
|
|
185
201
|
return { kind: "ForStmt", init: first, cond, post, body };
|
|
186
202
|
},
|
|
@@ -290,9 +306,9 @@ export const statementParserMethods = {
|
|
|
290
306
|
// a, b, c := ... or x, y = ...
|
|
291
307
|
if (t.type === T.COMMA) {
|
|
292
308
|
const lhs = [first];
|
|
293
|
-
while (this.match(T.COMMA)) lhs.push(this.parseExpr());
|
|
309
|
+
while (this.match(T.COMMA)) lhs.push(this.parseExpr(0, noCompLit));
|
|
294
310
|
const op = this.advance(); // := or =
|
|
295
|
-
const rhs = this.parseExprList();
|
|
311
|
+
const rhs = this.parseExprList(noCompLit);
|
|
296
312
|
if (op.type === T.DEFINE) return { kind: "DefineStmt", lhs, rhs };
|
|
297
313
|
return { kind: "AssignStmt", lhs, op: "=", rhs };
|
|
298
314
|
}
|
|
@@ -300,14 +316,14 @@ export const statementParserMethods = {
|
|
|
300
316
|
// x := expr
|
|
301
317
|
if (t.type === T.DEFINE) {
|
|
302
318
|
this.advance();
|
|
303
|
-
const rhs = this.parseExprList();
|
|
319
|
+
const rhs = this.parseExprList(noCompLit);
|
|
304
320
|
return { kind: "DefineStmt", lhs: [first], rhs };
|
|
305
321
|
}
|
|
306
322
|
|
|
307
323
|
// x = expr or x += expr etc.
|
|
308
324
|
if (ASSIGN_OPS.has(t.type)) {
|
|
309
325
|
const op = this.advance().value;
|
|
310
|
-
const rhs = this.parseExprList();
|
|
326
|
+
const rhs = this.parseExprList(noCompLit);
|
|
311
327
|
return { kind: "AssignStmt", lhs: [first], op, rhs };
|
|
312
328
|
}
|
|
313
329
|
|
|
@@ -10,15 +10,33 @@
|
|
|
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)
|
|
19
|
+
//
|
|
20
|
+
// Go expressions embedded in templ (`{ expr }`, attribute values, `if`
|
|
21
|
+
// conditions, `switch` and `case` expressions, `@Comp(...)` calls) and `for`
|
|
22
|
+
// headers are also parsed here, so the code generator never runs the parser.
|
|
23
|
+
// The results sit on the node as non-enumerable properties (the generic AST
|
|
24
|
+
// walkers in rename/lowering visit enumerable keys only, and these ASTs have
|
|
25
|
+
// no type information):
|
|
26
|
+
// expr Expression AST, or null when there are no tokens to parse
|
|
27
|
+
// exprError The ParseError a malformed expression raised. The parser keeps
|
|
28
|
+
// it instead of throwing, so the error still surfaces when the
|
|
29
|
+
// code generator reads the node (after type-checking), as before
|
|
30
|
+
// raw TemplComponent only: `@templ.Raw(arg)`; `expr` is then `arg`
|
|
31
|
+
// forStmt TemplFor only: the parsed ForStmt, or null when the header does
|
|
32
|
+
// not parse as a Go `for` clause
|
|
33
|
+
// forError TemplFor only: the ParseError the header raised (kept, like
|
|
34
|
+
// exprError, for the code generator to throw)
|
|
35
|
+
// attr.expr / attr.exprError on `expr` and `cond-bool` attributes
|
|
18
36
|
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
37
|
+
import { TT } from "../lexer/templ-lexer.js";
|
|
38
|
+
import { T, Token } from "../lexer/tokens.js";
|
|
39
|
+
import { Parser } from "./index.js";
|
|
22
40
|
|
|
23
41
|
const VOID_ELEMENTS = new Set([
|
|
24
42
|
"area",
|
|
@@ -37,6 +55,123 @@ const VOID_ELEMENTS = new Set([
|
|
|
37
55
|
"wbr",
|
|
38
56
|
]);
|
|
39
57
|
|
|
58
|
+
function attach(node, props) {
|
|
59
|
+
for (const [name, value] of Object.entries(props)) {
|
|
60
|
+
Object.defineProperty(node, name, {
|
|
61
|
+
value,
|
|
62
|
+
enumerable: false,
|
|
63
|
+
writable: true,
|
|
64
|
+
configurable: true,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return node;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Parses `tokens` as one Go expression, ending at a synthetic EOF token.
|
|
71
|
+
function parseExprTokens(tokens, allowEmpty = true) {
|
|
72
|
+
if (allowEmpty && (!tokens || tokens.length === 0)) return { expr: null };
|
|
73
|
+
try {
|
|
74
|
+
const eofTok = new Token(T.EOF, "", 1, 1);
|
|
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 };
|
|
81
|
+
} catch (exprError) {
|
|
82
|
+
return { expr: null, exprError };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
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
|
+
|
|
114
|
+
// Parses an `if` condition; the lexer may leave the `if` keyword in front.
|
|
115
|
+
function parseCondTokens(tokens) {
|
|
116
|
+
if (!tokens || tokens.length === 0) return { expr: null };
|
|
117
|
+
return parseExprTokens(
|
|
118
|
+
tokens[0]?.type === T.IF ? tokens.slice(1) : tokens,
|
|
119
|
+
false,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function isTemplRaw(toks) {
|
|
124
|
+
return (
|
|
125
|
+
toks.length >= 4 &&
|
|
126
|
+
toks[0]?.value === "templ" &&
|
|
127
|
+
toks[1]?.type === T.DOT &&
|
|
128
|
+
toks[2]?.value === "Raw" &&
|
|
129
|
+
toks[3]?.type === T.LPAREN
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Strips the trailing semicolons the Go lexer inserts at a newline.
|
|
134
|
+
function trimSemicolons(tokens) {
|
|
135
|
+
let end = tokens.length;
|
|
136
|
+
while (end > 0 && tokens[end - 1].type === T.SEMICOLON) end--;
|
|
137
|
+
return tokens.slice(0, end);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Parses a `for` header (the `for` keyword plus the clause, no body) as a
|
|
141
|
+
// ForStmt, with a fake empty body `{ }` so the parser can complete it. A header
|
|
142
|
+
// that is not a valid Go `for` clause gives `forStmt: null` plus its error.
|
|
143
|
+
function parseForHeader(tokens) {
|
|
144
|
+
const eofTok = new Token(T.EOF, "", 1, 1);
|
|
145
|
+
const lbrace = new Token(T.LBRACE, "{", 1, 1);
|
|
146
|
+
const rbrace = new Token(T.RBRACE, "}", 1, 1);
|
|
147
|
+
const semi = new Token(T.SEMICOLON, ";", 1, 1);
|
|
148
|
+
const withFor =
|
|
149
|
+
tokens[0]?.type === T.FOR
|
|
150
|
+
? [...tokens, lbrace, rbrace, semi, eofTok]
|
|
151
|
+
: [
|
|
152
|
+
new Token(T.FOR, "for", 1, 1),
|
|
153
|
+
...tokens,
|
|
154
|
+
lbrace,
|
|
155
|
+
rbrace,
|
|
156
|
+
semi,
|
|
157
|
+
eofTok,
|
|
158
|
+
];
|
|
159
|
+
try {
|
|
160
|
+
return { forStmt: new Parser(withFor).parseFor() };
|
|
161
|
+
} catch (forError) {
|
|
162
|
+
return { forStmt: null, forError };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Parses the `expr` and `cond-bool` attribute expressions of an element.
|
|
167
|
+
function parseAttrExprs(attrs) {
|
|
168
|
+
for (const attr of attrs) {
|
|
169
|
+
if (attr.kind === "expr" || attr.kind === "cond-bool")
|
|
170
|
+
attach(attr, parseExprTokens(attr.tokens));
|
|
171
|
+
}
|
|
172
|
+
return attrs;
|
|
173
|
+
}
|
|
174
|
+
|
|
40
175
|
export class TemplParser extends Parser {
|
|
41
176
|
// ── Top-level declaration override ───────────────────────────
|
|
42
177
|
|
|
@@ -101,7 +236,7 @@ export class TemplParser extends Parser {
|
|
|
101
236
|
return {
|
|
102
237
|
kind: "TemplElement",
|
|
103
238
|
tag: t.value.tag,
|
|
104
|
-
attrs: t.value.attrs,
|
|
239
|
+
attrs: parseAttrExprs(t.value.attrs),
|
|
105
240
|
children: [],
|
|
106
241
|
};
|
|
107
242
|
}
|
|
@@ -110,13 +245,19 @@ export class TemplParser extends Parser {
|
|
|
110
245
|
return { kind: "TemplText", value: t.value };
|
|
111
246
|
case TT.TEMPL_EXPR:
|
|
112
247
|
this.advance();
|
|
113
|
-
return
|
|
248
|
+
return attach(
|
|
249
|
+
{ kind: "TemplExpr", tokens: t.value },
|
|
250
|
+
parseExprTokens(t.value),
|
|
251
|
+
);
|
|
114
252
|
case TT.TEMPL_COMP:
|
|
115
253
|
this.advance();
|
|
116
|
-
return
|
|
254
|
+
return this.templComponent(t.value);
|
|
117
255
|
case TT.TEMPL_CHILDREN:
|
|
118
256
|
this.advance();
|
|
119
|
-
return
|
|
257
|
+
return attach(
|
|
258
|
+
{ kind: "TemplChildren", name: t.value },
|
|
259
|
+
parseExprTokens([new Token(T.IDENT, t.value, t.line, t.col)]),
|
|
260
|
+
);
|
|
120
261
|
case TT.TEMPL_IF: {
|
|
121
262
|
this.advance();
|
|
122
263
|
return this.parseTemplIf(t);
|
|
@@ -136,10 +277,20 @@ export class TemplParser extends Parser {
|
|
|
136
277
|
}
|
|
137
278
|
}
|
|
138
279
|
|
|
280
|
+
// `@templ.Raw(arg)` parses `arg`; any other component parses the whole call.
|
|
281
|
+
templComponent(tokens) {
|
|
282
|
+
const raw = isTemplRaw(tokens);
|
|
283
|
+
const parsed = parseExprTokens(
|
|
284
|
+
raw ? trimSemicolons(tokens).slice(4, -1) : tokens,
|
|
285
|
+
);
|
|
286
|
+
return attach({ kind: "TemplComponent", tokens }, { raw, ...parsed });
|
|
287
|
+
}
|
|
288
|
+
|
|
139
289
|
// Parse children of an element until the matching close tag.
|
|
140
290
|
parseHtmlElement() {
|
|
141
291
|
const openTok = this.advance(); // HTML_OPEN
|
|
142
|
-
const { tag
|
|
292
|
+
const { tag } = openTok.value;
|
|
293
|
+
const attrs = parseAttrExprs(openTok.value.attrs);
|
|
143
294
|
|
|
144
295
|
// Void elements have no children and no close tag even without />
|
|
145
296
|
if (VOID_ELEMENTS.has(tag)) {
|
|
@@ -186,14 +337,20 @@ export class TemplParser extends Parser {
|
|
|
186
337
|
else_ = this.parseHtmlChildren(); // else body until RBRACE
|
|
187
338
|
}
|
|
188
339
|
}
|
|
189
|
-
return
|
|
340
|
+
return attach(
|
|
341
|
+
{ kind: "TemplIf", condTokens, then, else_ },
|
|
342
|
+
parseCondTokens(condTokens),
|
|
343
|
+
);
|
|
190
344
|
}
|
|
191
345
|
|
|
192
346
|
// Parse a TemplFor node. `tok` is the TEMPL_FOR token (already advanced).
|
|
193
347
|
parseTemplFor(tok) {
|
|
194
348
|
const stmtTokens = tok.value;
|
|
195
349
|
const body = this.parseHtmlChildren();
|
|
196
|
-
return
|
|
350
|
+
return attach(
|
|
351
|
+
{ kind: "TemplFor", stmtTokens, body },
|
|
352
|
+
parseForHeader(trimSemicolons(stmtTokens)),
|
|
353
|
+
);
|
|
197
354
|
}
|
|
198
355
|
|
|
199
356
|
// Parse a TemplSwitch node. `tok` is the TEMPL_SWITCH token (already advanced).
|
|
@@ -209,17 +366,22 @@ export class TemplParser extends Parser {
|
|
|
209
366
|
const isDefault = this.check(TT.TEMPL_DEFAULT);
|
|
210
367
|
const caseTok = this.advance();
|
|
211
368
|
const body = this.parseTemplCaseBody();
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
369
|
+
const caseTokens = isDefault ? null : caseTok.value;
|
|
370
|
+
cases.push(
|
|
371
|
+
attach(
|
|
372
|
+
{ kind: "TemplCase", caseTokens, body },
|
|
373
|
+
isDefault ? { expr: null } : parseExprListTokens(caseTokens),
|
|
374
|
+
),
|
|
375
|
+
);
|
|
217
376
|
} else {
|
|
218
377
|
this.advance(); // skip unexpected
|
|
219
378
|
}
|
|
220
379
|
}
|
|
221
380
|
if (this.check(TT.TEMPL_END)) this.advance(); // consume closing }
|
|
222
|
-
return
|
|
381
|
+
return attach(
|
|
382
|
+
{ kind: "TemplSwitch", exprTokens, cases },
|
|
383
|
+
parseExprTokens(exprTokens),
|
|
384
|
+
);
|
|
223
385
|
}
|
|
224
386
|
|
|
225
387
|
// Parse HTML nodes for a switch case body — stops at CASE/DEFAULT/END/RBRACE.
|