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.
Files changed (194) hide show
  1. package/README.md +14 -14
  2. package/package.json +10 -38
  3. package/src/backend/js/expressions.js +2060 -389
  4. package/src/backend/js/index.js +586 -174
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +936 -1644
  7. package/src/backend/js/statements.js +1349 -132
  8. package/src/backend/js/stdlib/index.js +51 -94
  9. package/src/backend/js/templ.js +67 -118
  10. package/src/backend/js/type-info.js +436 -0
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +80 -0
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +753 -872
  15. package/src/backend/wasm/emit-builtins.js +914 -237
  16. package/src/backend/wasm/emit-exprs.js +1456 -195
  17. package/src/backend/wasm/emit-maps.js +78 -380
  18. package/src/backend/wasm/emit-stmts.js +1659 -214
  19. package/src/backend/wasm/emit.js +614 -88
  20. package/src/backend/wasm/encode.js +43 -13
  21. package/src/backend/wasm/globals.js +491 -0
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +406 -3195
  24. package/src/backend/wasm/method-expr.js +392 -0
  25. package/src/backend/wasm/module-emitter.js +1615 -0
  26. package/src/backend/wasm/monomorph.js +164 -9
  27. package/src/backend/wasm/natives.js +596 -0
  28. package/src/backend/wasm/prescan.js +249 -0
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -37
  31. package/src/backend/wasm/stdlib/simd.js +293 -28
  32. package/src/backend/wasm/types.js +260 -27
  33. package/src/backend/wasm/variadic.js +93 -0
  34. package/src/backend/wasm/wat.js +47 -20
  35. package/src/{cli-core.js → cli/cli-core.js} +241 -11
  36. package/src/{test-runner.js → cli/test-runner.js} +188 -122
  37. package/src/{vendor.js → cli/vendor.js} +3 -4
  38. package/src/compiler.js +202 -386
  39. package/src/index.js +15 -169
  40. package/src/{lexer.js → lexer/lexer.js} +203 -62
  41. package/src/{templ-lexer.js → lexer/templ-lexer.js} +61 -18
  42. package/src/{tokens.js → lexer/tokens.js} +2 -0
  43. package/src/link/sourcemap.js +97 -0
  44. package/src/link/stdlib.js +737 -0
  45. package/src/link/type-names.js +148 -0
  46. package/src/link/wasm.js +318 -0
  47. package/src/lower/bounds.js +547 -0
  48. package/src/lower/boxing.js +1 -1
  49. package/src/lower/captures.js +267 -64
  50. package/src/lower/embedding.js +32 -3
  51. package/src/lower/errors-as.js +196 -0
  52. package/src/lower/fmt-formatters.js +905 -0
  53. package/src/lower/functions.js +2 -0
  54. package/src/lower/gen-types.js +274 -0
  55. package/src/lower/index.js +33 -19
  56. package/src/lower/json-codecs.js +1379 -0
  57. package/src/lower/ownership.js +47 -8
  58. package/src/lower/range.js +86 -1
  59. package/src/lower/reachability.js +150 -0
  60. package/src/lower/walk.js +12 -0
  61. package/src/parser/declarations.js +83 -15
  62. package/src/parser/expressions.js +110 -18
  63. package/src/parser/index.js +2 -1
  64. package/src/parser/statements.js +38 -22
  65. package/src/{templ-parser.js → parser/templ.js} +179 -17
  66. package/src/parser/types.js +70 -9
  67. package/src/resolver.js +21 -1
  68. package/src/typechecker/assignability.js +291 -19
  69. package/src/typechecker/const-fold.js +399 -0
  70. package/src/typechecker/constants.js +282 -0
  71. package/src/typechecker/expressions.js +1220 -183
  72. package/src/typechecker/index.js +659 -92
  73. package/src/typechecker/init-order.js +426 -0
  74. package/src/typechecker/operand-text.js +40 -0
  75. package/src/typechecker/resolve.js +613 -62
  76. package/src/typechecker/statements.js +212 -20
  77. package/src/typechecker/stdlib/excluded.js +902 -0
  78. package/src/typechecker/stdlib/index.js +38 -0
  79. package/src/typechecker/stdlib/simd.js +65 -15
  80. package/src/typechecker/termination.js +11 -2
  81. package/src/typechecker/types.js +268 -514
  82. package/stdlib/bytes/buffer.go +479 -0
  83. package/stdlib/bytes/bytes.go +1010 -0
  84. package/stdlib/bytes/reader.go +161 -0
  85. package/stdlib/cmp/cmp.go +65 -0
  86. package/stdlib/context/context.go +115 -0
  87. package/stdlib/crypto/rand/rand.go +34 -0
  88. package/stdlib/crypto/sha256/sha256.go +249 -0
  89. package/stdlib/encoding/base64/base64.go +331 -0
  90. package/stdlib/encoding/binary/binary.go +332 -0
  91. package/stdlib/encoding/hex/hex.go +140 -0
  92. package/stdlib/encoding/json/errors.go +31 -0
  93. package/stdlib/encoding/json/indent.go +71 -0
  94. package/stdlib/encoding/json/json.go +62 -0
  95. package/stdlib/encoding/json/number.go +12 -0
  96. package/stdlib/encoding/json/stream.go +173 -0
  97. package/stdlib/errors/errors.go +174 -0
  98. package/stdlib/fmt/errors.go +110 -0
  99. package/stdlib/fmt/format.go +732 -0
  100. package/stdlib/fmt/print.go +1232 -0
  101. package/stdlib/gom/gom.go +293 -0
  102. package/stdlib/hash/hash.go +53 -0
  103. package/stdlib/html/html.go +251 -0
  104. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  105. package/stdlib/internal/jsonrt/dec.go +871 -0
  106. package/stdlib/internal/jsonrt/enc.go +516 -0
  107. package/stdlib/internal/jsonrt/indent.go +135 -0
  108. package/stdlib/internal/jsonrt/number.go +27 -0
  109. package/stdlib/internal/jsonrt/reader.go +73 -0
  110. package/stdlib/internal/jsonrt/scanner.go +658 -0
  111. package/stdlib/io/io.go +502 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/maps.go +131 -0
  114. package/stdlib/math/bits/bits.go +507 -0
  115. package/stdlib/math/extra.go +272 -0
  116. package/stdlib/math/math.go +439 -0
  117. package/stdlib/math/rand/rand.go +297 -0
  118. package/stdlib/os/os.go +93 -0
  119. package/stdlib/path/filepath/filepath.go +47 -0
  120. package/stdlib/path/path.go +394 -0
  121. package/stdlib/regexp/regexp.go +231 -0
  122. package/stdlib/slices/slices.go +526 -0
  123. package/stdlib/slices/sort.go +209 -0
  124. package/stdlib/slices/zsort_cmp.go +480 -0
  125. package/stdlib/slices/zsort_ordered.go +482 -0
  126. package/stdlib/sort/sort.go +500 -0
  127. package/stdlib/sort/zsort_func.go +480 -0
  128. package/stdlib/sort/zsort_iface.go +480 -0
  129. package/stdlib/strconv/float.go +1699 -0
  130. package/stdlib/strconv/isprint.go +752 -0
  131. package/stdlib/strconv/quote.go +526 -0
  132. package/stdlib/strconv/strconv.go +616 -0
  133. package/stdlib/strings/builder.go +95 -0
  134. package/stdlib/strings/reader.go +163 -0
  135. package/stdlib/strings/replacer.go +102 -0
  136. package/stdlib/strings/strings.go +807 -0
  137. package/stdlib/sync/sync.go +268 -0
  138. package/stdlib/testing/async_js.go +61 -0
  139. package/stdlib/testing/benchmark.go +297 -0
  140. package/stdlib/testing/testing.go +523 -0
  141. package/stdlib/time/duration.go +233 -0
  142. package/stdlib/time/format.go +745 -0
  143. package/stdlib/time/parse.go +790 -0
  144. package/stdlib/time/sleep_js.go +63 -0
  145. package/stdlib/time/time.go +834 -0
  146. package/stdlib/unicode/unicode.go +250 -0
  147. package/stdlib/unicode/utf8/utf8.go +464 -0
  148. package/src/backend/js/stdlib/base64.js +0 -12
  149. package/src/backend/js/stdlib/binary.js +0 -102
  150. package/src/backend/js/stdlib/bits.js +0 -46
  151. package/src/backend/js/stdlib/builder.js +0 -63
  152. package/src/backend/js/stdlib/bytes.js +0 -122
  153. package/src/backend/js/stdlib/errors.js +0 -44
  154. package/src/backend/js/stdlib/fmt.js +0 -178
  155. package/src/backend/js/stdlib/gom.js +0 -155
  156. package/src/backend/js/stdlib/hex.js +0 -17
  157. package/src/backend/js/stdlib/html.js +0 -20
  158. package/src/backend/js/stdlib/io.js +0 -137
  159. package/src/backend/js/stdlib/json.js +0 -972
  160. package/src/backend/js/stdlib/maps.js +0 -45
  161. package/src/backend/js/stdlib/math.js +0 -69
  162. package/src/backend/js/stdlib/os.js +0 -13
  163. package/src/backend/js/stdlib/path.js +0 -48
  164. package/src/backend/js/stdlib/rand.js +0 -32
  165. package/src/backend/js/stdlib/regexp.js +0 -54
  166. package/src/backend/js/stdlib/sha256.js +0 -12
  167. package/src/backend/js/stdlib/shared.js +0 -17
  168. package/src/backend/js/stdlib/simd.js +0 -24
  169. package/src/backend/js/stdlib/slices.js +0 -77
  170. package/src/backend/js/stdlib/sort.js +0 -30
  171. package/src/backend/js/stdlib/strconv.js +0 -74
  172. package/src/backend/js/stdlib/strings.js +0 -222
  173. package/src/backend/js/stdlib/sync.js +0 -13
  174. package/src/backend/js/stdlib/testing.js +0 -34
  175. package/src/backend/js/stdlib/time.js +0 -128
  176. package/src/backend/js/stdlib/unicode.js +0 -25
  177. package/src/backend/js/stdlib/utf8.js +0 -26
  178. package/src/backend/wasm/const-fold.js +0 -205
  179. package/src/backend/wasm/emit-stdlib.js +0 -13
  180. package/src/backend/wasm/stdlib/bytes.js +0 -5039
  181. package/src/backend/wasm/stdlib/core.js +0 -8903
  182. package/src/backend/wasm/stdlib/io.js +0 -2161
  183. package/src/backend/wasm/stdlib/packages.js +0 -2400
  184. package/src/backend/wasm/stdlib/pure.js +0 -720
  185. package/src/backend/wasm/stdlib/time.js +0 -1200
  186. package/src/typechecker/stdlib/core.js +0 -801
  187. package/src/typechecker/stdlib/extended.js +0 -914
  188. package/src/typechecker/stdlib.js +0 -22
  189. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  190. /package/src/{colors.js → cli/colors.js} +0 -0
  191. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  192. /package/src/{minifier.js → cli/minifier.js} +0 -0
  193. /package/src/{project-config.js → cli/project-config.js} +0 -0
  194. /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 { kind: "UnaryExpr", op, operand: this.parseUnary(noCompLit) };
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
- if (expr.kind === "Ident" && this.looksLikeTypeArgList()) {
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.type === T.LPAREN || after.type === T.LBRACE) return true;
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) / []rune(s) used as a call argument — it's a type conversion
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.advance();
301
- const convExpr = this.parseExpr();
302
- this.expect(T.RPAREN);
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
- elems.push(this.parseCompositeLit(null)); // null typeExpr = inferred
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)) {
@@ -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
  };
@@ -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
- return { kind: "LabeledStmt", label, body: this.parseStmt() };
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
- const first = this.parseSimpleStmtRaw(true);
154
-
155
- if (this.check(T.LBRACE)) {
156
- // for k, v := range expr { } — range stmt ends right before {
157
- if (this.isRangeStmt(first)) {
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: first,
161
- cond: null,
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 { Parser } from "./parser/index.js";
20
- import { TT } from "./templ-lexer.js";
21
- import { T } from "./tokens.js";
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 { kind: "TemplExpr", tokens: t.value };
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 { kind: "TemplComponent", tokens: t.value };
254
+ return this.templComponent(t.value);
117
255
  case TT.TEMPL_CHILDREN:
118
256
  this.advance();
119
- return { kind: "TemplChildren" };
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, attrs } = openTok.value;
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 { kind: "TemplIf", condTokens, then, else_ };
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 { kind: "TemplFor", stmtTokens, body };
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
- cases.push({
213
- kind: "TemplCase",
214
- caseTokens: isDefault ? null : caseTok.value,
215
- body,
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 { kind: "TemplSwitch", exprTokens, cases };
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.