gofront 1.6.1 → 1.8.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 +45 -49
- package/package.json +12 -8
- package/src/backend/js/expressions.js +1627 -148
- package/src/backend/js/fmt-runtime.js +573 -0
- package/src/backend/js/index.js +508 -102
- package/src/backend/js/runtime.js +1997 -106
- package/src/backend/js/statements.js +931 -87
- package/src/backend/js/stdlib/bits.js +87 -14
- package/src/backend/js/stdlib/builder.js +41 -6
- package/src/backend/js/stdlib/bytes.js +253 -7
- package/src/backend/js/stdlib/errors.js +7 -1
- package/src/backend/js/stdlib/fmt.js +349 -10
- package/src/backend/js/stdlib/index.js +19 -2
- package/src/backend/js/stdlib/json.js +1167 -0
- package/src/backend/js/stdlib/maps.js +0 -8
- package/src/backend/js/stdlib/math.js +17 -1
- package/src/backend/js/stdlib/simd.js +34 -0
- package/src/backend/js/stdlib/slices.js +36 -6
- package/src/backend/js/stdlib/strconv.js +10 -5
- package/src/backend/js/stdlib/strings.js +83 -4
- package/src/backend/js/stdlib/testing.js +7 -0
- package/src/backend/js/stdlib/time.js +79 -55
- package/src/backend/js/stdlib/unicode.js +1 -1
- package/src/backend/js/stdlib/utf8.js +1 -1
- package/src/backend/js/templ.js +38 -76
- package/src/backend/js/type-info.js +272 -0
- package/src/backend/shared/fmt-types.js +188 -0
- package/src/backend/wasm/boundary.js +446 -72
- package/src/backend/wasm/emit-builtins.js +320 -13
- package/src/backend/wasm/emit-exprs.js +1404 -199
- package/src/backend/wasm/emit-maps.js +61 -13
- package/src/backend/wasm/emit-stmts.js +1359 -164
- package/src/backend/wasm/emit.js +518 -74
- package/src/backend/wasm/encode.js +128 -13
- package/src/backend/wasm/globals.js +452 -0
- package/src/backend/wasm/index.js +194 -2489
- package/src/backend/wasm/method-expr.js +381 -0
- package/src/backend/wasm/module-emitter.js +2240 -0
- package/src/backend/wasm/monomorph.js +62 -4
- package/src/backend/wasm/optimize.js +4 -2
- package/src/backend/wasm/prescan.js +397 -0
- package/src/backend/wasm/stdlib/bytes.js +1804 -127
- package/src/backend/wasm/stdlib/core.js +1574 -199
- package/src/backend/wasm/stdlib/index.js +3 -5
- package/src/backend/wasm/stdlib/io.js +1567 -150
- package/src/backend/wasm/stdlib/packages.js +2 -0
- package/src/backend/wasm/stdlib/simd.js +499 -0
- package/src/backend/wasm/stdlib/time.js +1041 -12
- package/src/backend/wasm/types.js +221 -15
- package/src/backend/wasm/variadic.js +93 -0
- package/src/backend/wasm/wat.js +84 -20
- package/src/{cli-core.js → cli/cli-core.js} +236 -10
- package/src/{test-runner.js → cli/test-runner.js} +332 -39
- package/src/{vendor.js → cli/vendor.js} +89 -15
- package/src/compiler.js +107 -269
- package/src/dts-parser.js +71 -14
- package/src/index.js +15 -169
- package/src/{lexer.js → lexer/lexer.js} +102 -55
- package/src/{tokens.js → lexer/tokens.js} +1 -0
- package/src/link/sourcemap.js +97 -0
- package/src/link/stdlib.js +515 -0
- package/src/link/wasm.js +294 -0
- package/src/lower/bounds.js +394 -0
- package/src/lower/boxing.js +1 -6
- package/src/lower/captures.js +176 -32
- package/src/lower/embedding.js +2 -1
- package/src/lower/index.js +26 -18
- package/src/lower/json-codecs.js +1398 -0
- package/src/lower/ownership.js +22 -1
- package/src/lower/range.js +86 -1
- package/src/lower/reachability.js +132 -0
- package/src/parser/declarations.js +58 -13
- package/src/parser/expressions.js +29 -7
- package/src/parser/index.js +2 -1
- package/src/parser/statements.js +37 -22
- package/src/parser/struct-tag.js +144 -0
- package/src/{templ-parser.js → parser/templ.js} +138 -15
- package/src/parser/types.js +130 -13
- package/src/resolver.js +22 -1
- package/src/typechecker/assignability.js +215 -11
- package/src/typechecker/const-fold.js +356 -0
- package/src/typechecker/constants.js +256 -0
- package/src/typechecker/expressions.js +960 -99
- package/src/typechecker/index.js +491 -72
- package/src/typechecker/init-order.js +330 -0
- package/src/typechecker/resolve.js +596 -52
- package/src/typechecker/statements.js +157 -17
- package/src/typechecker/stdlib/core.js +333 -55
- package/src/typechecker/stdlib/excluded.js +898 -0
- package/src/typechecker/stdlib/extended.js +359 -26
- package/src/typechecker/{stdlib.js → stdlib/index.js} +6 -4
- package/src/typechecker/stdlib/simd.js +123 -0
- package/src/typechecker/termination.js +1 -1
- package/src/typechecker/types.js +165 -26
- package/stdlib/bytes/iter.go +192 -0
- package/stdlib/bytes/search.go +240 -0
- package/stdlib/bytes/split.go +218 -0
- package/stdlib/cmp/cmp.go +65 -0
- package/stdlib/encoding/json/errors.go +31 -0
- package/stdlib/encoding/json/indent.go +71 -0
- package/stdlib/encoding/json/number.go +12 -0
- package/stdlib/encoding/json/stream.go +173 -0
- package/stdlib/errors/overlay.go +12 -0
- package/stdlib/hash/hash.go +53 -0
- package/stdlib/internal/jsonrt/dec.go +738 -0
- package/stdlib/internal/jsonrt/enc.go +366 -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 +649 -0
- package/stdlib/io/io.go +346 -0
- package/stdlib/iter/iter.go +18 -0
- package/stdlib/maps/iter.go +65 -0
- package/stdlib/math/extra.go +270 -0
- package/stdlib/slices/extra.go +73 -0
- package/stdlib/slices/iter.go +113 -0
- package/stdlib/sort/sort.go +561 -0
- package/stdlib/strconv/quote.go +341 -0
- package/stdlib/strings/extra.go +35 -0
- package/stdlib/strings/iter.go +122 -0
- package/stdlib/sync/extra.go +105 -0
- package/stdlib/unicode/unicode.go +112 -0
- package/stdlib/unicode/utf8/bytes.go +239 -0
- package/src/backend/wasm/const-fold.js +0 -205
- package/src/backend/wasm/emit-stdlib.js +0 -13
- /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
- /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// src/parser/struct-tag.js
|
|
2
|
+
// Parse Go struct field tags matching Go's reflect.StructTag specification.
|
|
3
|
+
|
|
4
|
+
function unquoteTagValue(s) {
|
|
5
|
+
if (s.length < 2 || s[0] !== '"' || s[s.length - 1] !== '"') return s;
|
|
6
|
+
const inner = s.slice(1, -1);
|
|
7
|
+
let res = "";
|
|
8
|
+
for (let i = 0; i < inner.length; i++) {
|
|
9
|
+
if (inner[i] === "\\" && i + 1 < inner.length) {
|
|
10
|
+
i++;
|
|
11
|
+
const esc = inner[i];
|
|
12
|
+
switch (esc) {
|
|
13
|
+
case "n":
|
|
14
|
+
res += "\n";
|
|
15
|
+
break;
|
|
16
|
+
case "t":
|
|
17
|
+
res += "\t";
|
|
18
|
+
break;
|
|
19
|
+
case "r":
|
|
20
|
+
res += "\r";
|
|
21
|
+
break;
|
|
22
|
+
case '"':
|
|
23
|
+
res += '"';
|
|
24
|
+
break;
|
|
25
|
+
case "\\":
|
|
26
|
+
res += "\\";
|
|
27
|
+
break;
|
|
28
|
+
case "a":
|
|
29
|
+
res += "\x07";
|
|
30
|
+
break;
|
|
31
|
+
case "b":
|
|
32
|
+
res += "\b";
|
|
33
|
+
break;
|
|
34
|
+
case "f":
|
|
35
|
+
res += "\f";
|
|
36
|
+
break;
|
|
37
|
+
case "v":
|
|
38
|
+
res += "\v";
|
|
39
|
+
break;
|
|
40
|
+
case "0":
|
|
41
|
+
res += "\0";
|
|
42
|
+
break;
|
|
43
|
+
default:
|
|
44
|
+
res += `\\${esc}`;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
res += inner[i];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return res;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Parses a raw struct tag string into a Map of key -> unquoted string value,
|
|
56
|
+
* adhering to Go reflect.StructTag convention.
|
|
57
|
+
*
|
|
58
|
+
* @param {string | null | undefined} tag
|
|
59
|
+
* @returns {Map<string, string>}
|
|
60
|
+
*/
|
|
61
|
+
export function parseStructTag(tag) {
|
|
62
|
+
const map = new Map();
|
|
63
|
+
if (!tag || typeof tag !== "string") return map;
|
|
64
|
+
|
|
65
|
+
let str = tag;
|
|
66
|
+
while (str.length > 0) {
|
|
67
|
+
// Skip leading spaces
|
|
68
|
+
let i = 0;
|
|
69
|
+
while (i < str.length && str.charCodeAt(i) === 32) i++;
|
|
70
|
+
str = str.slice(i);
|
|
71
|
+
if (str.length === 0) break;
|
|
72
|
+
|
|
73
|
+
// Scan to colon: key must not contain space, quote, colon, or control characters
|
|
74
|
+
i = 0;
|
|
75
|
+
while (
|
|
76
|
+
i < str.length &&
|
|
77
|
+
str.charCodeAt(i) > 32 &&
|
|
78
|
+
str[i] !== ":" &&
|
|
79
|
+
str[i] !== '"' &&
|
|
80
|
+
str.charCodeAt(i) !== 127
|
|
81
|
+
) {
|
|
82
|
+
i++;
|
|
83
|
+
}
|
|
84
|
+
if (
|
|
85
|
+
i === 0 ||
|
|
86
|
+
i + 1 >= str.length ||
|
|
87
|
+
str[i] !== ":" ||
|
|
88
|
+
str[i + 1] !== '"'
|
|
89
|
+
) {
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
const key = str.slice(0, i);
|
|
93
|
+
str = str.slice(i + 1); // skip colon, now points to opening '"'
|
|
94
|
+
|
|
95
|
+
// Scan quoted string to find closing '"'
|
|
96
|
+
i = 1;
|
|
97
|
+
while (i < str.length && str[i] !== '"') {
|
|
98
|
+
if (str[i] === "\\") i++; // skip escaped char
|
|
99
|
+
i++;
|
|
100
|
+
}
|
|
101
|
+
if (i >= str.length) break;
|
|
102
|
+
|
|
103
|
+
const qval = str.slice(0, i + 1);
|
|
104
|
+
str = str.slice(i + 1);
|
|
105
|
+
|
|
106
|
+
if (!map.has(key)) {
|
|
107
|
+
map.set(key, unquoteTagValue(qval));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return map;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Parses the "json" key from a struct tag string or parsed Map according to encoding/json rules.
|
|
115
|
+
*
|
|
116
|
+
* @param {string | Map<string, string> | null | undefined} tag
|
|
117
|
+
* @returns {{ name: string | null, omitempty: boolean, ignored: boolean, options: string[] }}
|
|
118
|
+
*/
|
|
119
|
+
export function parseJsonTag(tag) {
|
|
120
|
+
if (!tag) {
|
|
121
|
+
return { name: null, omitempty: false, ignored: false, options: [] };
|
|
122
|
+
}
|
|
123
|
+
const map = typeof tag === "string" ? parseStructTag(tag) : tag;
|
|
124
|
+
if (!map.has("json")) {
|
|
125
|
+
return { name: null, omitempty: false, ignored: false, options: [] };
|
|
126
|
+
}
|
|
127
|
+
const raw = map.get("json");
|
|
128
|
+
const parts = raw.split(",");
|
|
129
|
+
const rawName = parts[0];
|
|
130
|
+
const options = parts.slice(1);
|
|
131
|
+
const omitempty = options.includes("omitempty");
|
|
132
|
+
|
|
133
|
+
if (rawName === "-" && parts.length === 1) {
|
|
134
|
+
return { name: "-", omitempty: false, ignored: true, options };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const name = rawName === "" ? null : rawName;
|
|
138
|
+
return {
|
|
139
|
+
name,
|
|
140
|
+
omitempty,
|
|
141
|
+
ignored: false,
|
|
142
|
+
options,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
@@ -15,10 +15,27 @@
|
|
|
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
|
+
//
|
|
19
|
+
// Go expressions embedded in templ (`{ expr }`, attribute values, `if`
|
|
20
|
+
// conditions, `switch` and `case` expressions, `@Comp(...)` calls) and `for`
|
|
21
|
+
// headers are also parsed here, so the code generator never runs the parser.
|
|
22
|
+
// The results sit on the node as non-enumerable properties (the generic AST
|
|
23
|
+
// walkers in rename/lowering visit enumerable keys only, and these ASTs have
|
|
24
|
+
// no type information):
|
|
25
|
+
// expr Expression AST, or null when there are no tokens to parse
|
|
26
|
+
// exprError The ParseError a malformed expression raised. The parser keeps
|
|
27
|
+
// it instead of throwing, so the error still surfaces when the
|
|
28
|
+
// code generator reads the node (after type-checking), as before
|
|
29
|
+
// raw TemplComponent only: `@templ.Raw(arg)`; `expr` is then `arg`
|
|
30
|
+
// forStmt TemplFor only: the parsed ForStmt, or null when the header does
|
|
31
|
+
// not parse as a Go `for` clause
|
|
32
|
+
// forError TemplFor only: the ParseError the header raised (kept, like
|
|
33
|
+
// exprError, for the code generator to throw)
|
|
34
|
+
// attr.expr / attr.exprError on `expr` and `cond-bool` attributes
|
|
18
35
|
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
36
|
+
import { TT } from "../lexer/templ-lexer.js";
|
|
37
|
+
import { T, Token } from "../lexer/tokens.js";
|
|
38
|
+
import { Parser } from "./index.js";
|
|
22
39
|
|
|
23
40
|
const VOID_ELEMENTS = new Set([
|
|
24
41
|
"area",
|
|
@@ -37,6 +54,90 @@ const VOID_ELEMENTS = new Set([
|
|
|
37
54
|
"wbr",
|
|
38
55
|
]);
|
|
39
56
|
|
|
57
|
+
function attach(node, props) {
|
|
58
|
+
for (const [name, value] of Object.entries(props)) {
|
|
59
|
+
Object.defineProperty(node, name, {
|
|
60
|
+
value,
|
|
61
|
+
enumerable: false,
|
|
62
|
+
writable: true,
|
|
63
|
+
configurable: true,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return node;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Parses `tokens` as one Go expression, ending at a synthetic EOF token.
|
|
70
|
+
function parseExprTokens(tokens, allowEmpty = true) {
|
|
71
|
+
if (allowEmpty && (!tokens || tokens.length === 0)) return { expr: null };
|
|
72
|
+
try {
|
|
73
|
+
const eofTok = new Token(T.EOF, "", 1, 1);
|
|
74
|
+
return { expr: new Parser([...tokens, eofTok]).parseExpr() };
|
|
75
|
+
} catch (exprError) {
|
|
76
|
+
return { expr: null, exprError };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Parses an `if` condition; the lexer may leave the `if` keyword in front.
|
|
81
|
+
function parseCondTokens(tokens) {
|
|
82
|
+
if (!tokens || tokens.length === 0) return { expr: null };
|
|
83
|
+
return parseExprTokens(
|
|
84
|
+
tokens[0]?.type === T.IF ? tokens.slice(1) : tokens,
|
|
85
|
+
false,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function isTemplRaw(toks) {
|
|
90
|
+
return (
|
|
91
|
+
toks.length >= 4 &&
|
|
92
|
+
toks[0]?.value === "templ" &&
|
|
93
|
+
toks[1]?.type === T.DOT &&
|
|
94
|
+
toks[2]?.value === "Raw" &&
|
|
95
|
+
toks[3]?.type === T.LPAREN
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Strips the trailing semicolons the Go lexer inserts at a newline.
|
|
100
|
+
function trimSemicolons(tokens) {
|
|
101
|
+
let end = tokens.length;
|
|
102
|
+
while (end > 0 && tokens[end - 1].type === T.SEMICOLON) end--;
|
|
103
|
+
return tokens.slice(0, end);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Parses a `for` header (the `for` keyword plus the clause, no body) as a
|
|
107
|
+
// ForStmt, with a fake empty body `{ }` so the parser can complete it. A header
|
|
108
|
+
// that is not a valid Go `for` clause gives `forStmt: null` plus its error.
|
|
109
|
+
function parseForHeader(tokens) {
|
|
110
|
+
const eofTok = new Token(T.EOF, "", 1, 1);
|
|
111
|
+
const lbrace = new Token(T.LBRACE, "{", 1, 1);
|
|
112
|
+
const rbrace = new Token(T.RBRACE, "}", 1, 1);
|
|
113
|
+
const semi = new Token(T.SEMICOLON, ";", 1, 1);
|
|
114
|
+
const withFor =
|
|
115
|
+
tokens[0]?.type === T.FOR
|
|
116
|
+
? [...tokens, lbrace, rbrace, semi, eofTok]
|
|
117
|
+
: [
|
|
118
|
+
new Token(T.FOR, "for", 1, 1),
|
|
119
|
+
...tokens,
|
|
120
|
+
lbrace,
|
|
121
|
+
rbrace,
|
|
122
|
+
semi,
|
|
123
|
+
eofTok,
|
|
124
|
+
];
|
|
125
|
+
try {
|
|
126
|
+
return { forStmt: new Parser(withFor).parseFor() };
|
|
127
|
+
} catch (forError) {
|
|
128
|
+
return { forStmt: null, forError };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Parses the `expr` and `cond-bool` attribute expressions of an element.
|
|
133
|
+
function parseAttrExprs(attrs) {
|
|
134
|
+
for (const attr of attrs) {
|
|
135
|
+
if (attr.kind === "expr" || attr.kind === "cond-bool")
|
|
136
|
+
attach(attr, parseExprTokens(attr.tokens));
|
|
137
|
+
}
|
|
138
|
+
return attrs;
|
|
139
|
+
}
|
|
140
|
+
|
|
40
141
|
export class TemplParser extends Parser {
|
|
41
142
|
// ── Top-level declaration override ───────────────────────────
|
|
42
143
|
|
|
@@ -101,7 +202,7 @@ export class TemplParser extends Parser {
|
|
|
101
202
|
return {
|
|
102
203
|
kind: "TemplElement",
|
|
103
204
|
tag: t.value.tag,
|
|
104
|
-
attrs: t.value.attrs,
|
|
205
|
+
attrs: parseAttrExprs(t.value.attrs),
|
|
105
206
|
children: [],
|
|
106
207
|
};
|
|
107
208
|
}
|
|
@@ -110,10 +211,13 @@ export class TemplParser extends Parser {
|
|
|
110
211
|
return { kind: "TemplText", value: t.value };
|
|
111
212
|
case TT.TEMPL_EXPR:
|
|
112
213
|
this.advance();
|
|
113
|
-
return
|
|
214
|
+
return attach(
|
|
215
|
+
{ kind: "TemplExpr", tokens: t.value },
|
|
216
|
+
parseExprTokens(t.value),
|
|
217
|
+
);
|
|
114
218
|
case TT.TEMPL_COMP:
|
|
115
219
|
this.advance();
|
|
116
|
-
return
|
|
220
|
+
return this.templComponent(t.value);
|
|
117
221
|
case TT.TEMPL_CHILDREN:
|
|
118
222
|
this.advance();
|
|
119
223
|
return { kind: "TemplChildren" };
|
|
@@ -136,10 +240,18 @@ export class TemplParser extends Parser {
|
|
|
136
240
|
}
|
|
137
241
|
}
|
|
138
242
|
|
|
243
|
+
// `@templ.Raw(arg)` parses `arg`; any other component parses the whole call.
|
|
244
|
+
templComponent(tokens) {
|
|
245
|
+
const raw = isTemplRaw(tokens);
|
|
246
|
+
const parsed = parseExprTokens(raw ? tokens.slice(4, -1) : tokens);
|
|
247
|
+
return attach({ kind: "TemplComponent", tokens }, { raw, ...parsed });
|
|
248
|
+
}
|
|
249
|
+
|
|
139
250
|
// Parse children of an element until the matching close tag.
|
|
140
251
|
parseHtmlElement() {
|
|
141
252
|
const openTok = this.advance(); // HTML_OPEN
|
|
142
|
-
const { tag
|
|
253
|
+
const { tag } = openTok.value;
|
|
254
|
+
const attrs = parseAttrExprs(openTok.value.attrs);
|
|
143
255
|
|
|
144
256
|
// Void elements have no children and no close tag even without />
|
|
145
257
|
if (VOID_ELEMENTS.has(tag)) {
|
|
@@ -186,14 +298,20 @@ export class TemplParser extends Parser {
|
|
|
186
298
|
else_ = this.parseHtmlChildren(); // else body until RBRACE
|
|
187
299
|
}
|
|
188
300
|
}
|
|
189
|
-
return
|
|
301
|
+
return attach(
|
|
302
|
+
{ kind: "TemplIf", condTokens, then, else_ },
|
|
303
|
+
parseCondTokens(condTokens),
|
|
304
|
+
);
|
|
190
305
|
}
|
|
191
306
|
|
|
192
307
|
// Parse a TemplFor node. `tok` is the TEMPL_FOR token (already advanced).
|
|
193
308
|
parseTemplFor(tok) {
|
|
194
309
|
const stmtTokens = tok.value;
|
|
195
310
|
const body = this.parseHtmlChildren();
|
|
196
|
-
return
|
|
311
|
+
return attach(
|
|
312
|
+
{ kind: "TemplFor", stmtTokens, body },
|
|
313
|
+
parseForHeader(trimSemicolons(stmtTokens)),
|
|
314
|
+
);
|
|
197
315
|
}
|
|
198
316
|
|
|
199
317
|
// Parse a TemplSwitch node. `tok` is the TEMPL_SWITCH token (already advanced).
|
|
@@ -209,17 +327,22 @@ export class TemplParser extends Parser {
|
|
|
209
327
|
const isDefault = this.check(TT.TEMPL_DEFAULT);
|
|
210
328
|
const caseTok = this.advance();
|
|
211
329
|
const body = this.parseTemplCaseBody();
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
330
|
+
const caseTokens = isDefault ? null : caseTok.value;
|
|
331
|
+
cases.push(
|
|
332
|
+
attach(
|
|
333
|
+
{ kind: "TemplCase", caseTokens, body },
|
|
334
|
+
isDefault ? { expr: null } : parseExprTokens(caseTokens),
|
|
335
|
+
),
|
|
336
|
+
);
|
|
217
337
|
} else {
|
|
218
338
|
this.advance(); // skip unexpected
|
|
219
339
|
}
|
|
220
340
|
}
|
|
221
341
|
if (this.check(TT.TEMPL_END)) this.advance(); // consume closing }
|
|
222
|
-
return
|
|
342
|
+
return attach(
|
|
343
|
+
{ kind: "TemplSwitch", exprTokens, cases },
|
|
344
|
+
parseExprTokens(exprTokens),
|
|
345
|
+
);
|
|
223
346
|
}
|
|
224
347
|
|
|
225
348
|
// Parse HTML nodes for a switch case body — stops at CASE/DEFAULT/END/RBRACE.
|
package/src/parser/types.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Parser type-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
|
// Token types that directly map to parseTypeName().
|
|
6
6
|
const TYPE_NAME_TOKENS = new Set([
|
|
@@ -76,8 +76,10 @@ export const typeParserMethods = {
|
|
|
76
76
|
i++;
|
|
77
77
|
continue;
|
|
78
78
|
}
|
|
79
|
-
// Reject literals — it's an index/array expression
|
|
80
|
-
|
|
79
|
+
// Reject literals — it's an index/array expression. A literal nested
|
|
80
|
+
// deeper is an array length inside a type argument: Stack[[2]int].
|
|
81
|
+
if (depth === 1 && (tt === T.INT || tt === T.FLOAT || tt === T.STRING))
|
|
82
|
+
return false;
|
|
81
83
|
i++;
|
|
82
84
|
}
|
|
83
85
|
return depth === 0;
|
|
@@ -86,11 +88,17 @@ export const typeParserMethods = {
|
|
|
86
88
|
parseTypeParamList() {
|
|
87
89
|
this.expect(T.LBRACKET);
|
|
88
90
|
const params = [];
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
+
while (!this.check(T.RBRACKET) && !this.check(T.EOF)) {
|
|
92
|
+
const names = [this.expect(T.IDENT).value];
|
|
93
|
+
while (this.match(T.COMMA)) {
|
|
94
|
+
names.push(this.expect(T.IDENT).value);
|
|
95
|
+
}
|
|
91
96
|
const constraint = this.parseConstraint();
|
|
92
|
-
|
|
93
|
-
|
|
97
|
+
for (const name of names) {
|
|
98
|
+
params.push({ kind: "TypeParam", name, constraint });
|
|
99
|
+
}
|
|
100
|
+
if (!this.match(T.COMMA)) break;
|
|
101
|
+
}
|
|
94
102
|
this.expect(T.RBRACKET);
|
|
95
103
|
return params;
|
|
96
104
|
},
|
|
@@ -175,29 +183,138 @@ export const typeParserMethods = {
|
|
|
175
183
|
this.expect(T.LBRACE);
|
|
176
184
|
const fields = [];
|
|
177
185
|
while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
|
|
186
|
+
// Embedded pointer field: *Base or *pkg.Base
|
|
187
|
+
if (this.check(T.STAR)) {
|
|
188
|
+
this.advance();
|
|
189
|
+
let typeName = this.expect(T.IDENT).value;
|
|
190
|
+
if (this.check(T.DOT)) {
|
|
191
|
+
this.advance();
|
|
192
|
+
typeName += `.${this.expect(T.IDENT).value}`;
|
|
193
|
+
}
|
|
194
|
+
const type = {
|
|
195
|
+
kind: "PointerType",
|
|
196
|
+
base: this._embeddedTypeName(typeName, true),
|
|
197
|
+
};
|
|
198
|
+
let tag = null;
|
|
199
|
+
if (this.check(T.STRING)) {
|
|
200
|
+
tag = this.advance().value;
|
|
201
|
+
}
|
|
202
|
+
fields.push({
|
|
203
|
+
names: [],
|
|
204
|
+
type,
|
|
205
|
+
embedded: true,
|
|
206
|
+
embeddedName: type.base.name,
|
|
207
|
+
tag,
|
|
208
|
+
});
|
|
209
|
+
this.semi();
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
178
213
|
const names = this.parseIdentList();
|
|
179
|
-
// Check if this is
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const typeName = names[0]
|
|
214
|
+
// Check if this is a qualified embedded field: pkg.Base
|
|
215
|
+
if (names.length === 1 && this.check(T.DOT)) {
|
|
216
|
+
this.advance();
|
|
217
|
+
const typeName = `${names[0]}.${this.expect(T.IDENT).value}`;
|
|
218
|
+
const embeddedType = this._embeddedTypeName(typeName, true);
|
|
219
|
+
let tag = null;
|
|
220
|
+
if (this.check(T.STRING)) {
|
|
221
|
+
tag = this.advance().value;
|
|
222
|
+
}
|
|
223
|
+
fields.push({
|
|
224
|
+
names: [],
|
|
225
|
+
type: embeddedType,
|
|
226
|
+
embedded: true,
|
|
227
|
+
embeddedName: typeName,
|
|
228
|
+
tag,
|
|
229
|
+
});
|
|
230
|
+
this.semi();
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Embedded generic instance: Inner[T] (not an array field `a [N]T`)
|
|
235
|
+
if (
|
|
236
|
+
names.length === 1 &&
|
|
237
|
+
this.check(T.LBRACKET) &&
|
|
238
|
+
this._bracketEndsEmbeddedField()
|
|
239
|
+
) {
|
|
240
|
+
const embeddedType = this._embeddedTypeName(names[0], true);
|
|
241
|
+
let tag = null;
|
|
242
|
+
if (this.check(T.STRING)) {
|
|
243
|
+
tag = this.advance().value;
|
|
244
|
+
}
|
|
245
|
+
fields.push({
|
|
246
|
+
names: [],
|
|
247
|
+
type: embeddedType,
|
|
248
|
+
embedded: true,
|
|
249
|
+
embeddedName: names[0],
|
|
250
|
+
tag,
|
|
251
|
+
});
|
|
252
|
+
this.semi();
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Embedded field (just a type name, e.g. Base), optionally tagged
|
|
257
|
+
if (
|
|
258
|
+
names.length === 1 &&
|
|
259
|
+
(this.check(T.SEMICOLON) ||
|
|
260
|
+
this.check(T.RBRACE) ||
|
|
261
|
+
this.check(T.STRING))
|
|
262
|
+
) {
|
|
263
|
+
const typeName = names[0];
|
|
264
|
+
let tag = null;
|
|
265
|
+
if (this.check(T.STRING)) {
|
|
266
|
+
tag = this.advance().value;
|
|
267
|
+
}
|
|
183
268
|
fields.push({
|
|
184
269
|
names: [],
|
|
185
270
|
type: { kind: "TypeName", name: typeName },
|
|
186
271
|
embedded: true,
|
|
272
|
+
tag,
|
|
187
273
|
});
|
|
188
274
|
this.semi();
|
|
189
275
|
continue;
|
|
190
276
|
}
|
|
277
|
+
|
|
191
278
|
const type = this.parseType();
|
|
192
279
|
// Consume optional struct tag (backtick string), e.g. `json:"name"`
|
|
193
|
-
|
|
194
|
-
|
|
280
|
+
let tag = null;
|
|
281
|
+
if (this.check(T.STRING)) {
|
|
282
|
+
tag = this.advance().value;
|
|
283
|
+
}
|
|
284
|
+
fields.push({ names, type, tag });
|
|
195
285
|
this.semi();
|
|
196
286
|
}
|
|
197
287
|
this.expect(T.RBRACE);
|
|
198
288
|
return { kind: "StructType", fields };
|
|
199
289
|
},
|
|
200
290
|
|
|
291
|
+
// True when the `[...]` at the cursor is followed by the end of a field
|
|
292
|
+
// (`Inner[T]` embedded), rather than an element type (`a [N]T`).
|
|
293
|
+
_bracketEndsEmbeddedField() {
|
|
294
|
+
let depth = 0;
|
|
295
|
+
for (let i = this.pos; i < this.tokens.length; i++) {
|
|
296
|
+
const tt = this.tokens[i].type;
|
|
297
|
+
if (tt === T.LBRACKET) depth++;
|
|
298
|
+
else if (tt === T.RBRACKET && --depth === 0) {
|
|
299
|
+
const next = this.tokens[i + 1]?.type;
|
|
300
|
+
return next === T.SEMICOLON || next === T.RBRACE || next === T.STRING;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return false;
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
// Embedded field type: `name`, or `name[Args]` when type arguments follow.
|
|
307
|
+
_embeddedTypeName(name, allowArgs) {
|
|
308
|
+
if (allowArgs && this.check(T.LBRACKET)) {
|
|
309
|
+
this.expect(T.LBRACKET);
|
|
310
|
+
const typeArgs = [this.parseType()];
|
|
311
|
+
while (this.match(T.COMMA)) typeArgs.push(this.parseType());
|
|
312
|
+
this.expect(T.RBRACKET);
|
|
313
|
+
return { kind: "GenericTypeName", name, typeArgs };
|
|
314
|
+
}
|
|
315
|
+
return { kind: "TypeName", name };
|
|
316
|
+
},
|
|
317
|
+
|
|
201
318
|
parseInterfaceType() {
|
|
202
319
|
this.expect(T.INTERFACE);
|
|
203
320
|
this.expect(T.LBRACE);
|
package/src/resolver.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
14
14
|
import { dirname, join, resolve } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
15
16
|
|
|
16
17
|
// Walk up from startDir looking for a node_modules directory
|
|
17
18
|
function findNodeModules(startDir) {
|
|
@@ -25,6 +26,7 @@ function findNodeModules(startDir) {
|
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
import { EXCLUDED_STDLIB_PACKAGES } from "./typechecker/stdlib/excluded.js";
|
|
28
30
|
import { STDLIB_PACKAGES } from "./typechecker/types.js";
|
|
29
31
|
|
|
30
32
|
// Returns true for local relative paths (start with ./ or ../)
|
|
@@ -36,6 +38,23 @@ export function isBuiltinPackage(importPath) {
|
|
|
36
38
|
return STDLIB_PACKAGES.has(importPath);
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
// Go-source stdlib: `stdlib/<import path>/*.go`, written in GoFront Go
|
|
42
|
+
// and compiled into the caller's target. A directory for a path that is also
|
|
43
|
+
// in STDLIB_PACKAGES is an overlay: its declarations extend that namespace.
|
|
44
|
+
const STDLIB_SOURCE_ROOT = fileURLToPath(
|
|
45
|
+
new URL("../stdlib/", import.meta.url),
|
|
46
|
+
);
|
|
47
|
+
const STDLIB_PATH_RE = /^[a-z0-9]+(\/[a-z0-9]+)*$/;
|
|
48
|
+
|
|
49
|
+
// Returns the source directory for a Go-source stdlib import path, or null.
|
|
50
|
+
// `root` replaces stdlib/ (tests use a temporary tree).
|
|
51
|
+
export function resolveStdlibSource(importPath, root = null) {
|
|
52
|
+
if (!STDLIB_PATH_RE.test(importPath)) return null;
|
|
53
|
+
const dir = join(root ?? STDLIB_SOURCE_ROOT, importPath);
|
|
54
|
+
if (!existsSync(dir)) return null;
|
|
55
|
+
return readdirSync(dir).some((f) => f.endsWith(".go")) ? dir : null;
|
|
56
|
+
}
|
|
57
|
+
|
|
39
58
|
// Resolve a local relative import path to an absolute directory of .go files.
|
|
40
59
|
// Returns the absolute dir path, or null if it doesn't contain any .go files.
|
|
41
60
|
export function resolveGwDir(importPath, fromFile) {
|
|
@@ -86,14 +105,16 @@ function resolveImport(importPath, fromFile) {
|
|
|
86
105
|
|
|
87
106
|
// Given an AST's import list, resolve all imports.
|
|
88
107
|
// Returns Map<importPath, { dtsPath, importPath, types, values }>
|
|
89
|
-
export function resolveAll(imports, fromFile, parseDts) {
|
|
108
|
+
export function resolveAll(imports, fromFile, parseDts, stdlibRoot = null) {
|
|
90
109
|
const resolved = new Map();
|
|
91
110
|
|
|
92
111
|
for (const imp of imports) {
|
|
93
112
|
for (const { path } of imp.imports) {
|
|
94
113
|
if (path.startsWith("js:")) continue; // handled separately
|
|
95
114
|
if (isLocalPath(path)) continue; // local .go packages — handled by compiler
|
|
115
|
+
if (EXCLUDED_STDLIB_PACKAGES.has(path)) continue; // the checker reports it
|
|
96
116
|
if (isBuiltinPackage(path)) continue; // built-in stdlib namespaces — always available
|
|
117
|
+
if (resolveStdlibSource(path, stdlibRoot)) continue; // Go-source stdlib — handled by compiler
|
|
97
118
|
|
|
98
119
|
if (resolved.has(path)) continue;
|
|
99
120
|
|