gofront 0.0.8 → 1.0.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 +88 -12
- package/package.json +8 -4
- package/src/codegen/expressions.js +520 -1717
- package/src/{codegen.js → codegen/index.js} +155 -184
- package/src/codegen/runtime.js +151 -0
- package/src/codegen/statements.js +305 -296
- package/src/codegen/stdlib/builder.js +36 -0
- package/src/codegen/stdlib/bytes.js +62 -0
- package/src/codegen/stdlib/errors.js +17 -0
- package/src/codegen/stdlib/fmt.js +101 -0
- package/src/codegen/stdlib/gom.js +152 -0
- package/src/codegen/stdlib/html.js +17 -0
- package/src/codegen/stdlib/index.js +95 -0
- package/src/codegen/stdlib/io.js +33 -0
- package/src/codegen/stdlib/maps.js +42 -0
- package/src/codegen/stdlib/math.js +53 -0
- package/src/codegen/stdlib/os.js +10 -0
- package/src/codegen/stdlib/path.js +31 -0
- package/src/codegen/stdlib/rand.js +24 -0
- package/src/codegen/stdlib/regexp.js +51 -0
- package/src/codegen/stdlib/slices.js +48 -0
- package/src/codegen/stdlib/sort.js +28 -0
- package/src/codegen/stdlib/strconv.js +30 -0
- package/src/codegen/stdlib/strings.js +83 -0
- package/src/codegen/stdlib/time.js +54 -0
- package/src/codegen/stdlib/unicode.js +22 -0
- package/src/codegen/stdlib/utf8.js +23 -0
- package/src/codegen/templ.js +281 -0
- package/src/compiler.js +102 -62
- package/src/dts-parser.js +355 -371
- package/src/index.js +22 -54
- package/src/lexer.js +200 -286
- package/src/minifier.js +305 -318
- package/src/parser/expressions.js +54 -36
- package/src/{parser.js → parser/index.js} +12 -5
- package/src/parser/statements.js +29 -35
- package/src/parser/types.js +31 -29
- package/src/templ-lexer.js +737 -0
- package/src/templ-parser.js +235 -0
- package/src/tokens.js +109 -0
- package/src/typechecker/assignability.js +258 -0
- package/src/typechecker/expressions.js +559 -549
- package/src/typechecker/index.js +555 -0
- package/src/typechecker/resolve.js +421 -0
- package/src/typechecker/statements.js +267 -280
- package/src/typechecker/stdlib/core.js +592 -0
- package/src/typechecker/stdlib/extended.js +332 -0
- package/src/typechecker/stdlib.js +9 -1060
- package/src/typechecker/termination.js +83 -0
- package/src/typechecker/types.js +6 -3
- package/src/typechecker.js +0 -1271
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// CodeGen for Go `math/rand` package.
|
|
2
|
+
|
|
3
|
+
const RAND_DISPATCH = {
|
|
4
|
+
Intn: (a) => `Math.floor(Math.random() * ${a[0]})`,
|
|
5
|
+
Int63n: (a) => `Math.floor(Math.random() * ${a[0]})`,
|
|
6
|
+
Int31n: (a) => `Math.floor(Math.random() * ${a[0]})`,
|
|
7
|
+
Float64: () => "Math.random()",
|
|
8
|
+
Float32: () => "Math.random()",
|
|
9
|
+
Int: () => "Math.floor(Math.random() * 2147483647)",
|
|
10
|
+
Int31: () => "Math.floor(Math.random() * 2147483647)",
|
|
11
|
+
Int63: () => "Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)",
|
|
12
|
+
Seed: () => "(void 0)",
|
|
13
|
+
Shuffle: (a) =>
|
|
14
|
+
`((n, f) => { for (let i = n - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); f(i, j); } })(${a[0]}, ${a[1]})`,
|
|
15
|
+
Perm: (a) =>
|
|
16
|
+
`((n) => { const a = Array.from({length: n}, (_, i) => i); for (let i = n - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; })(${a[0]})`,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const randMethods = {
|
|
20
|
+
_genRand(fn, a) {
|
|
21
|
+
const gen = RAND_DISPATCH[fn];
|
|
22
|
+
return gen ? gen(a()) : undefined;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// CodeGen for Go `regexp` package and `*Regexp` methods.
|
|
2
|
+
|
|
3
|
+
const REGEXP_METHOD = {
|
|
4
|
+
MatchString: (re, a) => `${re}.test(${a[0]})`,
|
|
5
|
+
FindString: (re, a) => `(${re}.exec(${a[0]})?.[0] ?? "")`,
|
|
6
|
+
FindStringIndex: (re, a) =>
|
|
7
|
+
`((m => m ? [m.index, m.index + m[0].length] : null)(${re}.exec(${a[0]})))`,
|
|
8
|
+
FindAllString: (re, a) =>
|
|
9
|
+
`[...${a[0]}.matchAll(new RegExp(${re}.source, ${re}.flags.includes("g") ? ${re}.flags : ${re}.flags + "g"))].slice(0, ${a[1]} < 0 ? undefined : ${a[1]}).map(m => m[0])`,
|
|
10
|
+
FindStringSubmatch: (re, a) => `[...(${re}.exec(${a[0]}) ?? [])]`,
|
|
11
|
+
FindAllStringSubmatch: (re, a) =>
|
|
12
|
+
`[...${a[0]}.matchAll(new RegExp(${re}.source, ${re}.flags.includes("g") ? ${re}.flags : ${re}.flags + "g"))].slice(0, ${a[1]} < 0 ? undefined : ${a[1]}).map(m => [...m])`,
|
|
13
|
+
ReplaceAllString: (re, a) =>
|
|
14
|
+
`${a[0]}.replaceAll(new RegExp(${re}.source, ${re}.flags.includes("g") ? ${re}.flags : ${re}.flags + "g"), ${a[1]})`,
|
|
15
|
+
ReplaceAllLiteralString: (re, a) =>
|
|
16
|
+
`${a[0]}.replaceAll(new RegExp(${re}.source, ${re}.flags.includes("g") ? ${re}.flags : ${re}.flags + "g"), ${a[1]}.replace(/\\$/g, '$$$$'))`,
|
|
17
|
+
Split: (re, a) =>
|
|
18
|
+
`${a[0]}.split(${re}).slice(0, ${a[1]} < 0 ? undefined : ${a[1]})`,
|
|
19
|
+
String: (re) => `${re}.source`,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const regexpMethods = {
|
|
23
|
+
_genRegexp(fn, a) {
|
|
24
|
+
// Converts a Go regexp pattern string (which may contain inline flags like (?i))
|
|
25
|
+
// into a JS RegExp, extracting the flags into the second constructor argument.
|
|
26
|
+
const makeRegExp = (pat) =>
|
|
27
|
+
`((p) => { const m = /^\\(\\?([gimsuy]+)\\)/.exec(p); return m ? new RegExp(p.slice(m[0].length), m[1]) : new RegExp(p); })(${pat})`;
|
|
28
|
+
switch (fn) {
|
|
29
|
+
case "MustCompile":
|
|
30
|
+
return makeRegExp(a()[0]);
|
|
31
|
+
case "Compile":
|
|
32
|
+
return `[${makeRegExp(a()[0])}, null]`;
|
|
33
|
+
case "MatchString": {
|
|
34
|
+
const [pat, s] = a();
|
|
35
|
+
return `[new RegExp(${pat}).test(${s}), null]`;
|
|
36
|
+
}
|
|
37
|
+
case "QuoteMeta":
|
|
38
|
+
return `${a()[0]}.replace(/[.*+?^\${}()|[\\]\\\\]/g, '\\\\$&')`;
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
_genRegexpMethodCall(method, expr) {
|
|
44
|
+
const recv = expr.func.expr;
|
|
45
|
+
// *Regexp is a plain JS RegExp — no .value boxing needed
|
|
46
|
+
const re = this.genExpr(recv);
|
|
47
|
+
const args = expr.args.map((a) => this.genExpr(a));
|
|
48
|
+
const gen = REGEXP_METHOD[method];
|
|
49
|
+
return gen ? gen(re, args) : undefined;
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// CodeGen for Go `slices` package.
|
|
2
|
+
|
|
3
|
+
const SLICES_DISPATCH = {
|
|
4
|
+
Contains: (a) => `${a[0]}.includes(${a[1]})`,
|
|
5
|
+
Index: (a) => `${a[0]}.indexOf(${a[1]})`,
|
|
6
|
+
Compare: (a) =>
|
|
7
|
+
`((a, b) => { for (let i = 0; i < a.length && i < b.length; i++) { if (a[i] < b[i]) return -1; if (a[i] > b[i]) return 1; } return a.length - b.length; })(${a[0]}, ${a[1]})`,
|
|
8
|
+
Sort: (a) => `${a[0]}.sort((a, b) => a < b ? -1 : a > b ? 1 : 0)`,
|
|
9
|
+
SortFunc: (a) => `${a[0]}.sort(${a[1]})`,
|
|
10
|
+
SortStableFunc: (a) => `${a[0]}.sort(${a[1]})`,
|
|
11
|
+
IsSorted: (a) =>
|
|
12
|
+
`((s) => { for (let i = 1; i < s.length; i++) { if (s[i] < s[i-1]) return false; } return true; })(${a[0]})`,
|
|
13
|
+
IsSortedFunc: (a) =>
|
|
14
|
+
`((s, f) => { for (let i = 1; i < s.length; i++) { if (f(s[i], s[i-1]) < 0) return false; } return true; })(${a[0]}, ${a[1]})`,
|
|
15
|
+
Reverse: (a) => `${a[0]}.reverse()`,
|
|
16
|
+
Max: (a) => `Math.max(...${a[0]})`,
|
|
17
|
+
Min: (a) => `Math.min(...${a[0]})`,
|
|
18
|
+
MaxFunc: (a) =>
|
|
19
|
+
`((s, f) => s.reduce((m, x) => f(x, m) > 0 ? x : m))(${a[0]}, ${a[1]})`,
|
|
20
|
+
MinFunc: (a) =>
|
|
21
|
+
`((s, f) => s.reduce((m, x) => f(x, m) < 0 ? x : m))(${a[0]}, ${a[1]})`,
|
|
22
|
+
Clone: (a) => `${a[0]}.slice()`,
|
|
23
|
+
Compact: (a) =>
|
|
24
|
+
`((s) => s.filter((v, i) => i === 0 || v !== s[i-1]))(${a[0]})`,
|
|
25
|
+
CompactFunc: (a) =>
|
|
26
|
+
`((s, f) => s.filter((v, i) => i === 0 || !f(v, s[i-1])))(${a[0]}, ${a[1]})`,
|
|
27
|
+
Concat: (a) => `[].concat(${a.join(", ")})`,
|
|
28
|
+
Delete: (a) => `[...${a[0]}.slice(0, ${a[1]}), ...${a[0]}.slice(${a[2]})]`,
|
|
29
|
+
DeleteFunc: (a) => `${a[0]}.filter((v) => !${a[1]}(v))`,
|
|
30
|
+
Insert: (a) =>
|
|
31
|
+
`[...${a[0]}.slice(0, ${a[1]}), ${a.slice(2).join(", ")}, ...${a[0]}.slice(${a[1]})]`,
|
|
32
|
+
Replace: (a) =>
|
|
33
|
+
`[...${a[0]}.slice(0, ${a[1]}), ${a.slice(3).join(", ")}, ...${a[0]}.slice(${a[2]})]`,
|
|
34
|
+
Grow: (a) => `${a[0]}.slice()`,
|
|
35
|
+
Clip: (a) => `${a[0]}.slice()`,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const slicesMethods = {
|
|
39
|
+
_genSlices(fn, a) {
|
|
40
|
+
if (fn === "Equal") {
|
|
41
|
+
this._usesEqual = true;
|
|
42
|
+
const [a1, b1] = a();
|
|
43
|
+
return `__equal(${a1}, ${b1})`;
|
|
44
|
+
}
|
|
45
|
+
const gen = SLICES_DISPATCH[fn];
|
|
46
|
+
return gen ? gen(a()) : undefined;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// CodeGen for Go `sort` package.
|
|
2
|
+
|
|
3
|
+
const SORT_DISPATCH = {
|
|
4
|
+
Ints: (a) => `${a[0]}.sort((a, b) => a - b)`,
|
|
5
|
+
Float64s: (a) => `${a[0]}.sort((a, b) => a - b)`,
|
|
6
|
+
Strings: (a) => `${a[0]}.sort()`,
|
|
7
|
+
Slice: (a) =>
|
|
8
|
+
`${a[0]}.sort((a, b) => ${a[1]}(a, b) ? -1 : ${a[1]}(b, a) ? 1 : 0)`,
|
|
9
|
+
SliceStable: (a) =>
|
|
10
|
+
`${a[0]}.sort((a, b) => ${a[1]}(a, b) ? -1 : ${a[1]}(b, a) ? 1 : 0)`,
|
|
11
|
+
SliceIsSorted: (a) =>
|
|
12
|
+
`${a[0]}.every((v, i, a) => i === 0 || ${a[1]}(a[i - 1], v))`,
|
|
13
|
+
Search: (a) =>
|
|
14
|
+
`((n, f) => { let lo = 0, hi = n; while (lo < hi) { const mid = (lo + hi) >>> 1; if (f(mid)) hi = mid; else lo = mid + 1; } return lo; })(${a[0]}, ${a[1]})`,
|
|
15
|
+
IntsAreSorted: (a) =>
|
|
16
|
+
`(${a[0]}).every((v, i, a) => i === 0 || a[i - 1] <= v)`,
|
|
17
|
+
Float64sAreSorted: (a) =>
|
|
18
|
+
`(${a[0]}).every((v, i, a) => i === 0 || a[i - 1] <= v)`,
|
|
19
|
+
StringsAreSorted: (a) =>
|
|
20
|
+
`(${a[0]}).every((v, i, a) => i === 0 || a[i - 1] <= v)`,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const sortMethods = {
|
|
24
|
+
_genSort(fn, a) {
|
|
25
|
+
const gen = SORT_DISPATCH[fn];
|
|
26
|
+
return gen ? gen(a()) : undefined;
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// CodeGen for Go `strconv` package.
|
|
2
|
+
|
|
3
|
+
const STRCONV_DISPATCH = {
|
|
4
|
+
Itoa: (a) => `String(${a[0]})`,
|
|
5
|
+
Atoi: (a) =>
|
|
6
|
+
`(Number.isNaN(Number(${a[0]})) ? [0, "invalid syntax"] : [Number(${a[0]}) | 0, null])`,
|
|
7
|
+
FormatBool: (a) => `String(${a[0]})`,
|
|
8
|
+
FormatInt: (a) => `(${a[0]}).toString(${a[1]})`,
|
|
9
|
+
FormatFloat: (a) => `String(${a[0]})`,
|
|
10
|
+
ParseFloat: (a) =>
|
|
11
|
+
`(Number.isNaN(Number(${a[0]})) ? [0, "invalid syntax"] : [Number(${a[0]}), null])`,
|
|
12
|
+
ParseInt: (a) =>
|
|
13
|
+
`(Number.isNaN(parseInt(${a[0]}, ${a[1]} || 10)) ? [0, "invalid syntax"] : [parseInt(${a[0]}, ${a[1]} || 10), null])`,
|
|
14
|
+
ParseBool: (a) =>
|
|
15
|
+
`(${a[0]} === "true" || ${a[0]} === "1" ? [true, null] : ${a[0]} === "false" || ${a[0]} === "0" ? [false, null] : [false, "invalid syntax"])`,
|
|
16
|
+
Quote: (a) => `JSON.stringify(${a[0]})`,
|
|
17
|
+
Unquote: (a) =>
|
|
18
|
+
`((s) => { try { const v = JSON.parse(s); return [v, null]; } catch(e) { return ["", "invalid syntax"]; } })(${a[0]})`,
|
|
19
|
+
AppendInt: (a) =>
|
|
20
|
+
`[...(${a[0]}), ...new TextEncoder().encode((${a[1]}).toString(${a[2]}))]`,
|
|
21
|
+
AppendFloat: (a) =>
|
|
22
|
+
`[...(${a[0]}), ...new TextEncoder().encode(${a[1]}.toFixed(${a[3]} < 0 ? 6 : ${a[3]}))]`,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const strconvMethods = {
|
|
26
|
+
_genStrconv(fn, a) {
|
|
27
|
+
const gen = STRCONV_DISPATCH[fn];
|
|
28
|
+
return gen ? gen(a()) : undefined;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// CodeGen for Go `strings` package.
|
|
2
|
+
|
|
3
|
+
const STRINGS_M1 = {
|
|
4
|
+
ToUpper: "toUpperCase",
|
|
5
|
+
ToLower: "toLowerCase",
|
|
6
|
+
TrimSpace: "trim",
|
|
7
|
+
ToTitle: "toUpperCase",
|
|
8
|
+
};
|
|
9
|
+
const STRINGS_M2 = {
|
|
10
|
+
Contains: "includes",
|
|
11
|
+
HasPrefix: "startsWith",
|
|
12
|
+
HasSuffix: "endsWith",
|
|
13
|
+
Index: "indexOf",
|
|
14
|
+
LastIndex: "lastIndexOf",
|
|
15
|
+
Repeat: "repeat",
|
|
16
|
+
Split: "split",
|
|
17
|
+
Join: "join",
|
|
18
|
+
};
|
|
19
|
+
const STRINGS_DISPATCH = {
|
|
20
|
+
Count: ([s, s2]) =>
|
|
21
|
+
`((s, sep) => sep === "" ? s.length + 1 : s.split(sep).length - 1)(${s}, ${s2})`,
|
|
22
|
+
Replace: ([s, s2, s3]) => `${s}.replace(${s2}, ${s3})`,
|
|
23
|
+
ReplaceAll: ([s, s2, s3]) => `${s}.replaceAll(${s2}, ${s3})`,
|
|
24
|
+
Trim: ([s, s2]) =>
|
|
25
|
+
`${s}.replace(new RegExp(\`^[\${${s2}}]+|[\${${s2}}]+$\`, "g"), "")`,
|
|
26
|
+
TrimPrefix: ([s, s2]) =>
|
|
27
|
+
`((s, pre) => s.startsWith(pre) ? s.slice(pre.length) : s)(${s}, ${s2})`,
|
|
28
|
+
TrimSuffix: ([s, s2]) =>
|
|
29
|
+
`((s, suf) => !suf.length || !s.endsWith(suf) ? s : s.slice(0, -suf.length))(${s}, ${s2})`,
|
|
30
|
+
TrimLeft: ([s, s2]) => `${s}.replace(new RegExp(\`^[\${${s2}}]+\`), "")`,
|
|
31
|
+
TrimRight: ([s, s2]) => `${s}.replace(new RegExp(\`[\${${s2}}]+$\`), "")`,
|
|
32
|
+
EqualFold: ([s, s2]) => `${s}.toLowerCase() === ${s2}.toLowerCase()`,
|
|
33
|
+
Fields: ([s]) => `(${s}).trim() === '' ? [] : (${s}).trim().split(/\\s+/)`,
|
|
34
|
+
Cut: ([s, s2]) =>
|
|
35
|
+
`((s, sep) => { const i = s.indexOf(sep); return i < 0 ? [s, "", false] : [s.slice(0, i), s.slice(i + sep.length), true]; })(${s}, ${s2})`,
|
|
36
|
+
CutPrefix: ([s, s2]) =>
|
|
37
|
+
`(${s}).startsWith(${s2}) ? [(${s}).slice((${s2}).length), true] : [${s}, false]`,
|
|
38
|
+
CutSuffix: ([s, s2]) =>
|
|
39
|
+
`(${s}).endsWith(${s2}) ? [(${s}).slice(0, -(${s2}).length), true] : [${s}, false]`,
|
|
40
|
+
SplitN: ([s, s2, s3]) =>
|
|
41
|
+
`((s, sep, n) => { if (n === 0) return []; if (n < 0) return s.split(sep); const r = []; let cur = s; for (let i = 1; i < n && cur.length; i++) { const j = cur.indexOf(sep); if (j < 0) break; r.push(cur.slice(0, j)); cur = cur.slice(j + sep.length); } r.push(cur); return r; })(${s}, ${s2}, ${s3})`,
|
|
42
|
+
SplitAfter: ([s, s2]) =>
|
|
43
|
+
`((s, sep) => { if (sep === "") return [...s]; const r = []; let cur = s; while (cur.length) { const j = cur.indexOf(sep); if (j < 0) { r.push(cur); break; } r.push(cur.slice(0, j + sep.length)); cur = cur.slice(j + sep.length); } return r; })(${s}, ${s2})`,
|
|
44
|
+
SplitAfterN: ([s, s2, s3]) =>
|
|
45
|
+
`((s, sep, n) => { if (n === 0) return []; if (n < 0 || sep === "") return ((s, sep) => { if (sep === "") return [...s]; const r = []; let cur = s; while (cur.length) { const j = cur.indexOf(sep); if (j < 0) { r.push(cur); break; } r.push(cur.slice(0, j + sep.length)); cur = cur.slice(j + sep.length); } return r; })(s, sep); const r = []; let cur = s; for (let i = 1; i < n && cur.length; i++) { const j = cur.indexOf(sep); if (j < 0) break; r.push(cur.slice(0, j + sep.length)); cur = cur.slice(j + sep.length); } r.push(cur); return r; })(${s}, ${s2}, ${s3})`,
|
|
46
|
+
IndexAny: ([s, s2]) =>
|
|
47
|
+
`((s, chars) => { let m = -1; for (const c of chars) { const i = s.indexOf(c); if (i >= 0 && (m < 0 || i < m)) m = i; } return m; })(${s}, ${s2})`,
|
|
48
|
+
LastIndexAny: ([s, s2]) =>
|
|
49
|
+
`((s, chars) => { let m = -1; for (const c of chars) { const i = s.lastIndexOf(c); if (i > m) m = i; } return m; })(${s}, ${s2})`,
|
|
50
|
+
ContainsAny: ([s, s2]) => `[...(${s2})].some(c => (${s}).includes(c))`,
|
|
51
|
+
ContainsRune: ([s, s2]) => `(${s}).includes(String.fromCodePoint(${s2}))`,
|
|
52
|
+
IndexRune: ([s, s2]) => `(${s}).indexOf(String.fromCodePoint(${s2}))`,
|
|
53
|
+
IndexByte: ([s, s2]) => `(${s}).indexOf(String.fromCharCode(${s2}))`,
|
|
54
|
+
LastIndexByte: ([s, s2]) => `(${s}).lastIndexOf(String.fromCharCode(${s2}))`,
|
|
55
|
+
Map: ([s, s2]) =>
|
|
56
|
+
`[...(${s2})].map(c => String.fromCodePoint((${s})(c.codePointAt(0)))).join("")`,
|
|
57
|
+
Title: ([s]) => `(${s}).replace(/\\b\\w/g, c => c.toUpperCase())`,
|
|
58
|
+
TrimFunc: ([s, s2]) =>
|
|
59
|
+
`((s, f) => { let l = 0, r = s.length; while (l < r && f(s.codePointAt(l))) l++; while (r > l && f(s.codePointAt(r - 1))) r--; return s.slice(l, r); })(${s}, ${s2})`,
|
|
60
|
+
TrimLeftFunc: ([s, s2]) =>
|
|
61
|
+
`((s, f) => { let l = 0; while (l < s.length && f(s.codePointAt(l))) l++; return s.slice(l); })(${s}, ${s2})`,
|
|
62
|
+
TrimRightFunc: ([s, s2]) =>
|
|
63
|
+
`((s, f) => { let r = s.length; while (r > 0 && f(s.codePointAt(r - 1))) r--; return s.slice(0, r); })(${s}, ${s2})`,
|
|
64
|
+
IndexFunc: ([s, s2]) =>
|
|
65
|
+
`((s, f) => { for (let i = 0; i < s.length; i++) { const cp = s.codePointAt(i); if (f(cp)) return i; if (cp > 0xFFFF) i++; } return -1; })(${s}, ${s2})`,
|
|
66
|
+
LastIndexFunc: ([s, s2]) =>
|
|
67
|
+
`((s, f) => { for (let i = s.length - 1; i >= 0; i--) { const cp = s.codePointAt(i); if (f(cp)) return i; } return -1; })(${s}, ${s2})`,
|
|
68
|
+
NewReader: ([s]) =>
|
|
69
|
+
`{_src: ${s}, _pos: 0, Read(p) { const n = Math.min(p.length, this._src.length - this._pos); for (let i = 0; i < n; i++) p[i] = this._src.charCodeAt(this._pos + i); this._pos += n; return [n, n === 0 ? "EOF" : null]; }, Len() { return this._src.length - this._pos; }, Reset(s) { this._src = s; this._pos = 0; }}`,
|
|
70
|
+
NewReplacer: (args) =>
|
|
71
|
+
`((...pairs) => { const p = []; for (let i = 0; i < pairs.length; i += 2) p.push([pairs[i], pairs[i+1]]); return { _p: p, Replace(s) { let r = s; for (const [o, n] of this._p) r = r.split(o).join(n); return r; } }; })(${args.join(", ")})`,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const stringsMethods = {
|
|
75
|
+
_genStrings(fn, a) {
|
|
76
|
+
const args = a();
|
|
77
|
+
const [s, s2] = args;
|
|
78
|
+
if (STRINGS_M1[fn]) return `${s}.${STRINGS_M1[fn]}()`;
|
|
79
|
+
if (STRINGS_M2[fn]) return `${s}.${STRINGS_M2[fn]}(${s2})`;
|
|
80
|
+
const gen = STRINGS_DISPATCH[fn];
|
|
81
|
+
return gen ? gen(args) : undefined;
|
|
82
|
+
},
|
|
83
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// CodeGen for Go `time` package — `time.Now`, `time.Since`, etc., plus `*Time` methods.
|
|
2
|
+
|
|
3
|
+
const TIME_METHOD_DISPATCH = {
|
|
4
|
+
Year: (r) => `${r}._d.getFullYear()`,
|
|
5
|
+
Month: (r) => `${r}._d.getMonth() + 1`,
|
|
6
|
+
Day: (r) => `${r}._d.getDate()`,
|
|
7
|
+
Hour: (r) => `${r}._d.getHours()`,
|
|
8
|
+
Minute: (r) => `${r}._d.getMinutes()`,
|
|
9
|
+
Second: (r) => `${r}._d.getSeconds()`,
|
|
10
|
+
Weekday: (r) => `${r}._d.getDay()`,
|
|
11
|
+
Unix: (r) => `Math.floor(${r}._d.getTime() / 1000)`,
|
|
12
|
+
UnixMilli: (r) => `${r}._d.getTime()`,
|
|
13
|
+
Add: (r, a) => `{_d: new Date(${r}._d.getTime() + (${a[0]}))}`,
|
|
14
|
+
Sub: (r, a) => `${r}._d.getTime() - (${a[0]})._d.getTime()`,
|
|
15
|
+
Before: (r, a) => `${r}._d < (${a[0]})._d`,
|
|
16
|
+
After: (r, a) => `${r}._d > (${a[0]})._d`,
|
|
17
|
+
Equal: (r, a) => `${r}._d.getTime() === (${a[0]})._d.getTime()`,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const TIME_DISPATCH = {
|
|
21
|
+
Now: () => "{_d: new Date()}",
|
|
22
|
+
Since: (a) => `(Date.now() - (${a[0]})._d.getTime())`,
|
|
23
|
+
Sleep: (a) => `await new Promise(r => setTimeout(r, ${a[0]} / 1000000))`,
|
|
24
|
+
Unix: (a) => `{_d: new Date((${a[0]}) * 1000)}`,
|
|
25
|
+
Date: (a) =>
|
|
26
|
+
`{_d: new Date(${a[0]}, ${a[1]} - 1, ${a[2]}, ${a[3]}, ${a[4]}, ${a[5]})}`,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const timeMethods = {
|
|
30
|
+
_genTimeMethodCall(method, expr) {
|
|
31
|
+
const recv = expr.func.expr;
|
|
32
|
+
const recvJs = this.genExpr(recv);
|
|
33
|
+
const args = expr.args.map((a) => this.genExpr(a));
|
|
34
|
+
if (method === "Format") {
|
|
35
|
+
this._usesTimeFmt = true;
|
|
36
|
+
return `__timeFmt(${recvJs}._d, ${args[0]})`;
|
|
37
|
+
}
|
|
38
|
+
if (method === "String") {
|
|
39
|
+
this._usesTimeFmt = true;
|
|
40
|
+
return `__timeFmt(${recvJs}._d, "2006-01-02T15:04:05Z07:00")`;
|
|
41
|
+
}
|
|
42
|
+
const gen = TIME_METHOD_DISPATCH[method];
|
|
43
|
+
return gen ? gen(recvJs, args) : undefined;
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
_genTime(fn, a) {
|
|
47
|
+
if (fn === "Parse") {
|
|
48
|
+
this._usesTimeParse = true;
|
|
49
|
+
return `__timeParse(${a()[0]}, ${a()[1]})`;
|
|
50
|
+
}
|
|
51
|
+
const gen = TIME_DISPATCH[fn];
|
|
52
|
+
return gen ? gen(a()) : undefined;
|
|
53
|
+
},
|
|
54
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// CodeGen for Go `unicode` package.
|
|
2
|
+
|
|
3
|
+
export const unicodeMethods = {
|
|
4
|
+
_genUnicode(fn, a) {
|
|
5
|
+
const [arg] = a();
|
|
6
|
+
const cp = `String.fromCodePoint(${arg})`;
|
|
7
|
+
const UNICODE_TEST = {
|
|
8
|
+
IsLetter: `/\\p{L}/u.test(${cp})`,
|
|
9
|
+
IsDigit: `/\\p{Nd}/u.test(${cp})`,
|
|
10
|
+
IsSpace: `/\\s/.test(${cp})`,
|
|
11
|
+
IsUpper: `((__c) => __c === __c.toUpperCase() && /\\p{L}/u.test(__c))(${cp})`,
|
|
12
|
+
IsLower: `((__c) => __c === __c.toLowerCase() && /\\p{L}/u.test(__c))(${cp})`,
|
|
13
|
+
IsPunct: `/\\p{P}/u.test(${cp})`,
|
|
14
|
+
IsControl: `/\\p{Cc}/u.test(${cp})`,
|
|
15
|
+
IsPrint: `!/\\p{Cc}/u.test(${cp})`,
|
|
16
|
+
IsGraphic: `!/\\p{Cc}/u.test(${cp})`,
|
|
17
|
+
ToUpper: `${cp}.toUpperCase().codePointAt(0)`,
|
|
18
|
+
ToLower: `${cp}.toLowerCase().codePointAt(0)`,
|
|
19
|
+
};
|
|
20
|
+
return UNICODE_TEST[fn] ?? undefined;
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// CodeGen for Go `unicode/utf8` package.
|
|
2
|
+
|
|
3
|
+
const UTF8_DISPATCH = {
|
|
4
|
+
RuneCountInString: (a) => `[...(${a[0]})].length`,
|
|
5
|
+
RuneLen: (a) =>
|
|
6
|
+
`((r) => r < 0 ? -1 : r <= 0x7F ? 1 : r <= 0x7FF ? 2 : r <= 0xFFFF ? (r >= 0xD800 && r <= 0xDFFF ? -1 : 3) : r <= 0x10FFFF ? 4 : -1)(${a[0]})`,
|
|
7
|
+
ValidString: (a) =>
|
|
8
|
+
`/[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]/.test(${a[0]}) === false`,
|
|
9
|
+
ValidRune: (a) =>
|
|
10
|
+
`(${a[0]}) >= 0 && (${a[0]}) <= 0x10FFFF && !((${a[0]}) >= 0xD800 && (${a[0]}) <= 0xDFFF)`,
|
|
11
|
+
DecodeRuneInString: (a) =>
|
|
12
|
+
`((s) => { if (!s) return [0xFFFD, 0]; const cp = s.codePointAt(0); return [cp, cp > 0xFFFF ? 2 : 1]; })(${a[0]})`,
|
|
13
|
+
DecodeLastRuneInString: (a) =>
|
|
14
|
+
`((s) => { if (!s) return [0xFFFD, 0]; const i = s.length > 1 && s.charCodeAt(s.length - 1) >= 0xDC00 && s.charCodeAt(s.length - 1) <= 0xDFFF ? s.length - 2 : s.length - 1; const cp = s.codePointAt(i); return [cp, cp > 0xFFFF ? 2 : 1]; })(${a[0]})`,
|
|
15
|
+
FullRuneInString: (a) => `(${a[0]}).length > 0`,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const utf8Methods = {
|
|
19
|
+
_genUtf8(fn, a) {
|
|
20
|
+
const gen = UTF8_DISPATCH[fn];
|
|
21
|
+
return gen ? gen(a()) : undefined;
|
|
22
|
+
},
|
|
23
|
+
};
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
// Templ (.templ file) code-generation mixin for CodeGen.
|
|
2
|
+
// Mounts an HTML/expression tree onto a parent DOM node by emitting plain
|
|
3
|
+
// document.createElement / appendChild / setAttribute calls.
|
|
4
|
+
|
|
5
|
+
import { Parser } from "../parser/index.js";
|
|
6
|
+
import { T, Token } from "../tokens.js";
|
|
7
|
+
|
|
8
|
+
export const templGenMethods = {
|
|
9
|
+
genTemplDecl(decl) {
|
|
10
|
+
const params = decl.params
|
|
11
|
+
.map((p, i) =>
|
|
12
|
+
p.variadic && i === decl.params.length - 1 ? `...${p.name}` : p.name,
|
|
13
|
+
)
|
|
14
|
+
.join(", ");
|
|
15
|
+
this.line(`function ${decl.name}(${params}) {`);
|
|
16
|
+
this.indented(() => {
|
|
17
|
+
this.line(`return {Mount(___p) {`);
|
|
18
|
+
this.indented(() => {
|
|
19
|
+
this._genTemplNodes(decl.body, "___p");
|
|
20
|
+
});
|
|
21
|
+
this.line(`}};`);
|
|
22
|
+
});
|
|
23
|
+
this.line("}");
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
// Emit mount statements for an array of TemplNodes into parent variable `p`.
|
|
27
|
+
_genTemplNodes(nodes, p) {
|
|
28
|
+
if (!nodes || nodes.length === 0) return;
|
|
29
|
+
if (nodes.length === 1) {
|
|
30
|
+
this._genTemplNodeMount(nodes[0], p);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// Wrap multiple root nodes in a fragment div? No — emit each directly.
|
|
34
|
+
for (const node of nodes) {
|
|
35
|
+
this._genTemplNodeMount(node, p);
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
// Emit JS to mount a single TemplNode onto parent `p`.
|
|
40
|
+
_genTemplNodeMount(node, p) {
|
|
41
|
+
switch (node.kind) {
|
|
42
|
+
case "TemplElement":
|
|
43
|
+
return this._genTemplElement(node, p);
|
|
44
|
+
case "TemplText":
|
|
45
|
+
return this.line(
|
|
46
|
+
`${p}.appendChild(document.createTextNode(${JSON.stringify(node.value)}));`,
|
|
47
|
+
);
|
|
48
|
+
case "TemplExpr":
|
|
49
|
+
return this.line(
|
|
50
|
+
`${p}.appendChild(document.createTextNode(String(${this._genTemplTokenExpr(node.tokens)})));`,
|
|
51
|
+
);
|
|
52
|
+
case "TemplComponent":
|
|
53
|
+
return this._genTemplComponentMount(node, p);
|
|
54
|
+
case "TemplChildren":
|
|
55
|
+
// children is a variadic gom.Node param — mount each child
|
|
56
|
+
return this.line(
|
|
57
|
+
`if(typeof children!=="undefined")(Array.isArray(children)?children:[...children]).forEach(___c=>___c?.Mount?.(${p}));`,
|
|
58
|
+
);
|
|
59
|
+
case "TemplIf":
|
|
60
|
+
return this._genTemplIf(node, p);
|
|
61
|
+
case "TemplFor":
|
|
62
|
+
return this._genTemplFor(node, p);
|
|
63
|
+
case "TemplSwitch":
|
|
64
|
+
return this._genTemplSwitch(node, p);
|
|
65
|
+
default:
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
_genTemplComponentMount(node, p) {
|
|
71
|
+
// Special case: @templ.Raw(expr) → insertAdjacentHTML
|
|
72
|
+
const toks = node.tokens;
|
|
73
|
+
if (
|
|
74
|
+
toks.length >= 4 &&
|
|
75
|
+
toks[0]?.value === "templ" &&
|
|
76
|
+
toks[1]?.type === T.DOT &&
|
|
77
|
+
toks[2]?.value === "Raw" &&
|
|
78
|
+
toks[3]?.type === T.LPAREN
|
|
79
|
+
) {
|
|
80
|
+
const argTokens = toks.slice(4, toks.length - 1);
|
|
81
|
+
const argJs = this._genTemplTokenExpr(argTokens);
|
|
82
|
+
return this.line(`${p}.insertAdjacentHTML("beforeend", ${argJs});`);
|
|
83
|
+
}
|
|
84
|
+
return this.line(
|
|
85
|
+
`(${this._genTemplTokenCallExpr(node.tokens)}).Mount(${p});`,
|
|
86
|
+
);
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
_genTemplElement(node, p) {
|
|
90
|
+
const { tag, attrs, children } = node;
|
|
91
|
+
const elVar = this._freshVar("e");
|
|
92
|
+
this.line(
|
|
93
|
+
`const ${elVar} = document.createElement(${JSON.stringify(tag)});`,
|
|
94
|
+
);
|
|
95
|
+
for (const attr of attrs) {
|
|
96
|
+
this._genTemplAttr(attr, elVar);
|
|
97
|
+
}
|
|
98
|
+
this._genTemplNodes(children, elVar);
|
|
99
|
+
this.line(`${p}.appendChild(${elVar});`);
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
_genTemplAttr(attr, el) {
|
|
103
|
+
const { name } = attr;
|
|
104
|
+
if (attr.kind === "static") {
|
|
105
|
+
if (name === "class") {
|
|
106
|
+
this.line(`${el}.className = ${JSON.stringify(attr.value)};`);
|
|
107
|
+
} else {
|
|
108
|
+
this.line(
|
|
109
|
+
`${el}.setAttribute(${JSON.stringify(name)}, ${JSON.stringify(attr.value)});`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
} else if (attr.kind === "expr") {
|
|
113
|
+
const exprJs = this._genTemplTokenExpr(attr.tokens);
|
|
114
|
+
if (name === "class") {
|
|
115
|
+
this.line(`${el}.className = ${exprJs};`);
|
|
116
|
+
} else {
|
|
117
|
+
this.line(
|
|
118
|
+
`${el}.setAttribute(${JSON.stringify(name)}, String(${exprJs}));`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
} else if (attr.kind === "bool") {
|
|
122
|
+
this.line(`${el}.setAttribute(${JSON.stringify(name)}, "");`);
|
|
123
|
+
} else if (attr.kind === "cond-bool") {
|
|
124
|
+
const exprJs = this._genTemplTokenExpr(attr.tokens);
|
|
125
|
+
this.line(
|
|
126
|
+
`if(${exprJs})${el}.setAttribute(${JSON.stringify(name)}, "");`,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
_genTemplIf(node, p) {
|
|
132
|
+
const condJs = this._genTemplCondTokens(node.condTokens);
|
|
133
|
+
this.line(`if (${condJs}) {`);
|
|
134
|
+
this.indented(() => this._genTemplNodes(node.then, p));
|
|
135
|
+
this._genTemplIfElse(node.else_, p);
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
_genTemplIfElse(else_, p) {
|
|
139
|
+
if (!else_ || else_.length === 0) {
|
|
140
|
+
this.line(`}`);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (else_.length === 1 && else_[0].kind === "TemplIf") {
|
|
144
|
+
const elseIf = else_[0];
|
|
145
|
+
this.line(`} else if (${this._genTemplCondTokens(elseIf.condTokens)}) {`);
|
|
146
|
+
this.indented(() => this._genTemplNodes(elseIf.then, p));
|
|
147
|
+
this._genTemplIfElse(elseIf.else_, p);
|
|
148
|
+
} else {
|
|
149
|
+
this.line(`} else {`);
|
|
150
|
+
this.indented(() => this._genTemplNodes(else_, p));
|
|
151
|
+
this.line(`}`);
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
_genTemplSwitch(node, p) {
|
|
156
|
+
const { exprTokens, cases } = node;
|
|
157
|
+
if (cases.length === 0) return;
|
|
158
|
+
const exprJs = this._genTemplTokenExpr(exprTokens);
|
|
159
|
+
this.line(`switch (${exprJs}) {`);
|
|
160
|
+
this.indented(() => {
|
|
161
|
+
for (const c of cases) {
|
|
162
|
+
const label =
|
|
163
|
+
c.caseTokens === null
|
|
164
|
+
? "default"
|
|
165
|
+
: `case ${this._genTemplTokenExpr(c.caseTokens)}`;
|
|
166
|
+
this.line(`${label}: {`);
|
|
167
|
+
this.indented(() => this._genTemplNodes(c.body, p));
|
|
168
|
+
this.line(`break; }`);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
this.line(`}`);
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
_genTemplFor(node, p) {
|
|
175
|
+
// stmtTokens include the `for` keyword plus the range clause (no body).
|
|
176
|
+
// Strip any trailing semicolons (inserted by the Go lexer on newlines),
|
|
177
|
+
// then add a fake empty body `{ }` so the parser can complete the ForStmt.
|
|
178
|
+
let tokens = node.stmtTokens;
|
|
179
|
+
while (tokens.length > 0 && tokens[tokens.length - 1].type === T.SEMICOLON)
|
|
180
|
+
tokens = tokens.slice(0, -1);
|
|
181
|
+
const eofTok = new Token(T.EOF, "", 1, 1);
|
|
182
|
+
const lbrace = new Token(T.LBRACE, "{", 1, 1);
|
|
183
|
+
const rbrace = new Token(T.RBRACE, "}", 1, 1);
|
|
184
|
+
const semi = new Token(T.SEMICOLON, ";", 1, 1);
|
|
185
|
+
const withFor =
|
|
186
|
+
tokens[0]?.type === T.FOR
|
|
187
|
+
? [...tokens, lbrace, rbrace, semi, eofTok]
|
|
188
|
+
: [
|
|
189
|
+
new Token(T.FOR, "for", 1, 1),
|
|
190
|
+
...tokens,
|
|
191
|
+
lbrace,
|
|
192
|
+
rbrace,
|
|
193
|
+
semi,
|
|
194
|
+
eofTok,
|
|
195
|
+
];
|
|
196
|
+
try {
|
|
197
|
+
const p2 = new Parser(withFor);
|
|
198
|
+
const forStmt = p2.parseFor();
|
|
199
|
+
this._genTemplRangeFor(forStmt, node.body, p);
|
|
200
|
+
} catch {
|
|
201
|
+
// Fallback for non-range loops: emit a comment + skip
|
|
202
|
+
this.line(`/* templ for: ${this._tokensToSource(tokens)} */`);
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
_genTemplRangeFor(forStmt, templBody, mountParent) {
|
|
207
|
+
const init = forStmt.init;
|
|
208
|
+
if (!init?.rhs?.[0] || init.rhs[0].kind !== "RangeExpr") {
|
|
209
|
+
// Plain for loop (cond-only or three-clause)
|
|
210
|
+
const condJs = forStmt.cond ? this.genExpr(forStmt.cond) : "true";
|
|
211
|
+
this.line(`while (${condJs}) {`);
|
|
212
|
+
this.indented(() => this._genTemplNodes(templBody, mountParent));
|
|
213
|
+
this.line("}");
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
// Range for: for key, val := range iter
|
|
217
|
+
const range = init.rhs[0];
|
|
218
|
+
const iterJs = this.genExpr(range.expr);
|
|
219
|
+
const lhs = init.lhs.map((e) => e.name ?? "_");
|
|
220
|
+
const keyName = lhs[0] ?? "_";
|
|
221
|
+
const valName = lhs[1] ?? null;
|
|
222
|
+
|
|
223
|
+
const loopHeader = this._genTemplRangeHeader(keyName, valName, iterJs);
|
|
224
|
+
this.line(loopHeader);
|
|
225
|
+
this.indented(() => this._genTemplNodes(templBody, mountParent));
|
|
226
|
+
this.line("}");
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
_genTemplRangeHeader(keyName, valName, iterJs) {
|
|
230
|
+
if (valName === null || valName === undefined) {
|
|
231
|
+
return `for (const ${keyName === "_" ? "_$" : keyName} of ${iterJs}) {`;
|
|
232
|
+
}
|
|
233
|
+
if (keyName === "_" && valName === "_")
|
|
234
|
+
return `for (const _$ of ${iterJs}) {`;
|
|
235
|
+
if (keyName === "_") return `for (const ${valName} of ${iterJs}) {`;
|
|
236
|
+
if (valName === "_")
|
|
237
|
+
return `for (const [${keyName}] of Object.entries(${iterJs})) {`;
|
|
238
|
+
// Default to .entries() for arrays, Object.entries for objects
|
|
239
|
+
this._usesSliceGuard = true;
|
|
240
|
+
return `for (const [${keyName}, ${valName}] of __s(${iterJs}).entries()) {`;
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
// Parse a token array as a Go expression and emit JS.
|
|
244
|
+
_genTemplTokenExpr(tokens) {
|
|
245
|
+
if (!tokens || tokens.length === 0) return '""';
|
|
246
|
+
const eofTok = new Token(T.EOF, "", 1, 1);
|
|
247
|
+
const p = new Parser([...tokens, eofTok]);
|
|
248
|
+
const ast = p.parseExpr();
|
|
249
|
+
return this.genExpr(ast);
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
// Parse a token array as a Go call expression and emit JS.
|
|
253
|
+
_genTemplTokenCallExpr(tokens) {
|
|
254
|
+
if (!tokens || tokens.length === 0) return "null";
|
|
255
|
+
const eofTok = new Token(T.EOF, "", 1, 1);
|
|
256
|
+
const p = new Parser([...tokens, eofTok]);
|
|
257
|
+
const ast = p.parseExpr();
|
|
258
|
+
return this.genExpr(ast);
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
// Extract condition JS from condTokens (which may start with `if`).
|
|
262
|
+
_genTemplCondTokens(tokens) {
|
|
263
|
+
if (!tokens || tokens.length === 0) return "false";
|
|
264
|
+
let toks = tokens;
|
|
265
|
+
if (toks[0]?.type === T.IF) toks = toks.slice(1); // skip `if` keyword
|
|
266
|
+
const eofTok = new Token(T.EOF, "", 1, 1);
|
|
267
|
+
const p = new Parser([...toks, eofTok]);
|
|
268
|
+
const ast = p.parseExpr();
|
|
269
|
+
return this.genExpr(ast);
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
_tokensToSource(tokens) {
|
|
273
|
+
return tokens.map((t) => t.value).join(" ");
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
// Fresh variable name counter
|
|
277
|
+
_freshVar(prefix = "v") {
|
|
278
|
+
this._templVarCount = (this._templVarCount ?? 0) + 1;
|
|
279
|
+
return `___${prefix}${this._templVarCount}`;
|
|
280
|
+
},
|
|
281
|
+
};
|