gofront 1.6.0 → 1.7.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 +35 -39
- package/package.json +37 -4
- package/src/backend/js/expressions.js +559 -144
- package/src/backend/js/index.js +174 -49
- package/src/backend/js/runtime.js +1518 -54
- package/src/backend/js/statements.js +91 -73
- package/src/backend/js/stdlib/base64.js +12 -0
- package/src/backend/js/stdlib/binary.js +102 -0
- package/src/backend/js/stdlib/bits.js +15 -1
- package/src/backend/js/stdlib/builder.js +28 -9
- package/src/backend/js/stdlib/bytes.js +75 -18
- package/src/backend/js/stdlib/errors.js +25 -1
- package/src/backend/js/stdlib/fmt.js +70 -13
- package/src/backend/js/stdlib/hex.js +17 -0
- package/src/backend/js/stdlib/html.js +1 -1
- package/src/backend/js/stdlib/index.js +28 -0
- package/src/backend/js/stdlib/io.js +105 -3
- package/src/backend/js/stdlib/json.js +972 -0
- package/src/backend/js/stdlib/math.js +13 -1
- package/src/backend/js/stdlib/path.js +18 -4
- package/src/backend/js/stdlib/rand.js +10 -5
- package/src/backend/js/stdlib/sha256.js +12 -0
- package/src/backend/js/stdlib/simd.js +24 -0
- package/src/backend/js/stdlib/slices.js +52 -26
- package/src/backend/js/stdlib/strconv.js +21 -5
- package/src/backend/js/stdlib/strings.js +148 -15
- package/src/backend/js/stdlib/sync.js +13 -0
- package/src/backend/js/stdlib/time.js +90 -19
- package/src/backend/js/stdlib/unicode.js +2 -2
- package/src/backend/wasm/boundary.js +1225 -41
- package/src/backend/wasm/const-fold.js +205 -0
- package/src/backend/wasm/emit-builtins.js +220 -76
- package/src/backend/wasm/emit-exprs.js +475 -276
- package/src/backend/wasm/emit-maps.js +144 -8
- package/src/backend/wasm/emit-stdlib.js +12 -3005
- package/src/backend/wasm/emit-stmts.js +264 -270
- package/src/backend/wasm/emit.js +124 -87
- package/src/backend/wasm/encode.js +140 -29
- package/src/backend/wasm/index.js +1001 -32
- package/src/backend/wasm/optimize.js +17 -6
- package/src/backend/wasm/stdlib/bytes.js +5039 -0
- package/src/backend/wasm/stdlib/core.js +8903 -0
- package/src/backend/wasm/stdlib/index.js +43 -0
- package/src/backend/wasm/stdlib/io.js +2161 -0
- package/src/backend/wasm/stdlib/packages.js +2400 -0
- package/src/backend/wasm/stdlib/pure.js +720 -0
- package/src/backend/wasm/stdlib/simd.js +234 -0
- package/src/backend/wasm/stdlib/time.js +1200 -0
- package/src/backend/wasm/types.js +174 -1
- package/src/backend/wasm/wat.js +37 -0
- package/src/cli-core.js +1 -1
- package/src/compiler.js +18 -4
- package/src/dts-parser.js +150 -140
- package/src/index.js +2 -6
- package/src/lexer.js +80 -29
- package/src/lower/boxing.js +1 -7
- package/src/lower/captures.js +2 -6
- package/src/lower/embedding.js +2 -1
- package/src/lower/ownership.js +30 -9
- package/src/minifier.js +20 -68
- package/src/parser/declarations.js +3 -6
- package/src/parser/expressions.js +3 -8
- package/src/parser/index.js +18 -3
- package/src/parser/statements.js +25 -38
- package/src/parser/struct-tag.js +144 -0
- package/src/parser/types.js +64 -8
- package/src/resolver.js +2 -27
- package/src/templ-lexer.js +29 -63
- package/src/templ-parser.js +22 -45
- package/src/typechecker/assignability.js +126 -13
- package/src/typechecker/expressions.js +168 -16
- package/src/typechecker/index.js +35 -8
- package/src/typechecker/resolve.js +187 -8
- package/src/typechecker/statements.js +68 -9
- package/src/typechecker/stdlib/core.js +280 -89
- package/src/typechecker/stdlib/extended.js +490 -1
- package/src/typechecker/stdlib/simd.js +73 -0
- package/src/typechecker/stdlib.js +2 -0
- package/src/typechecker/types.js +362 -9
- package/src/vendor.js +86 -11
package/src/lower/ownership.js
CHANGED
|
@@ -89,19 +89,42 @@ export function collectMutatedVars(
|
|
|
89
89
|
|
|
90
90
|
export function fnMutates(body, name, addrOnly = false, cache = null) {
|
|
91
91
|
if (!body) return true;
|
|
92
|
-
const key = `${addrOnly ? "&" : ""}${name}`;
|
|
93
92
|
if (cache) {
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
const setKey = addrOnly ? "&*" : "*";
|
|
94
|
+
let set = cache.get(setKey);
|
|
95
|
+
if (!set) {
|
|
96
|
+
set = collectMutatedVars(body, new Set(), addrOnly);
|
|
97
|
+
cache.set(setKey, set);
|
|
96
98
|
}
|
|
97
|
-
return
|
|
99
|
+
return set.has(name);
|
|
98
100
|
}
|
|
99
101
|
return nodeMutatesVar(body, name, addrOnly);
|
|
100
102
|
}
|
|
101
103
|
|
|
104
|
+
function collectClosureMutatedVars(node, mutated = new Set()) {
|
|
105
|
+
if (!node || typeof node !== "object") return mutated;
|
|
106
|
+
if (Array.isArray(node)) {
|
|
107
|
+
for (const item of node) collectClosureMutatedVars(item, mutated);
|
|
108
|
+
return mutated;
|
|
109
|
+
}
|
|
110
|
+
if (node.kind === "FuncLit") {
|
|
111
|
+
collectMutatedVars(node.body, mutated, false);
|
|
112
|
+
return mutated;
|
|
113
|
+
}
|
|
114
|
+
forEachChild(node, (child) => collectClosureMutatedVars(child, mutated));
|
|
115
|
+
return mutated;
|
|
116
|
+
}
|
|
117
|
+
|
|
102
118
|
export function closureMutates(body, name, cache = null) {
|
|
103
|
-
|
|
104
|
-
if (cache
|
|
119
|
+
if (!body) return false;
|
|
120
|
+
if (cache) {
|
|
121
|
+
let set = cache.get("λ*");
|
|
122
|
+
if (!set) {
|
|
123
|
+
set = collectClosureMutatedVars(body, new Set());
|
|
124
|
+
cache.set("λ*", set);
|
|
125
|
+
}
|
|
126
|
+
return set.has(name);
|
|
127
|
+
}
|
|
105
128
|
|
|
106
129
|
const walk = (node) => {
|
|
107
130
|
if (!node || typeof node !== "object") return false;
|
|
@@ -109,9 +132,7 @@ export function closureMutates(body, name, cache = null) {
|
|
|
109
132
|
if (node.kind === "FuncLit") return nodeMutatesVar(node.body, name);
|
|
110
133
|
return someChild(node, walk);
|
|
111
134
|
};
|
|
112
|
-
|
|
113
|
-
if (cache) cache.set(key, res);
|
|
114
|
-
return res;
|
|
135
|
+
return walk(body);
|
|
115
136
|
}
|
|
116
137
|
|
|
117
138
|
export class OwnershipContext {
|
package/src/minifier.js
CHANGED
|
@@ -121,7 +121,7 @@ const REGEX_CONTEXT_VALUES = new Set([
|
|
|
121
121
|
const DIVISION_AFTER_OPS = new Set([")", "]", "++", "--"]);
|
|
122
122
|
|
|
123
123
|
function _isRegexContext(tokens) {
|
|
124
|
-
const prev =
|
|
124
|
+
const prev = findPrevNonWs(tokens);
|
|
125
125
|
if (!prev) return true;
|
|
126
126
|
if (prev.type === "op") return !DIVISION_AFTER_OPS.has(prev.value);
|
|
127
127
|
return REGEX_CONTEXT_VALUES.has(prev.value);
|
|
@@ -323,14 +323,6 @@ function tokenize(code) {
|
|
|
323
323
|
return tokens;
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
-
function lastNonWhitespaceToken(tokens) {
|
|
327
|
-
for (let i = tokens.length - 1; i >= 0; i--) {
|
|
328
|
-
if (tokens[i].type !== "ws" && tokens[i].type !== "comment")
|
|
329
|
-
return tokens[i];
|
|
330
|
-
}
|
|
331
|
-
return null;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
326
|
// ── Stage 1 & 2: Compress ────────────────────────────────────
|
|
335
327
|
|
|
336
328
|
function isIdentLike(type) {
|
|
@@ -348,15 +340,6 @@ const SPACE_AFTER_KW_RIGHT_TYPES = new Set([
|
|
|
348
340
|
]);
|
|
349
341
|
// Operator values that do NOT need a space after a keyword
|
|
350
342
|
const NO_SPACE_AFTER_KW_OPS = new Set(["(", "[", "!", "-", "+"]);
|
|
351
|
-
// Adjacent op pairs that would accidentally merge into a different operator
|
|
352
|
-
const _INCREMENT_MERGE_PAIRS = new Set([
|
|
353
|
-
"+|+",
|
|
354
|
-
"-|-",
|
|
355
|
-
"+|++",
|
|
356
|
-
"-|--",
|
|
357
|
-
"++|+",
|
|
358
|
-
"--|−-",
|
|
359
|
-
]);
|
|
360
343
|
|
|
361
344
|
function _wouldMergeUnaryOps(left, right) {
|
|
362
345
|
return (
|
|
@@ -436,11 +419,7 @@ function compressTokens(tokens) {
|
|
|
436
419
|
return out;
|
|
437
420
|
}
|
|
438
421
|
|
|
439
|
-
function
|
|
440
|
-
return compressTokens(tokenize(code));
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
function findPrevNonWs(tokens, index) {
|
|
422
|
+
function findPrevNonWs(tokens, index = tokens.length) {
|
|
444
423
|
for (let i = index - 1; i >= 0; i--) {
|
|
445
424
|
if (tokens[i].type !== "ws" && tokens[i].type !== "comment")
|
|
446
425
|
return tokens[i];
|
|
@@ -477,7 +456,7 @@ function _matchingParen(tokens, openIdx) {
|
|
|
477
456
|
// A `(` opens a parameter list only when its `)` is followed by a body `{` or `=>`;
|
|
478
457
|
// otherwise `name(...)` is a call and its arguments may be free globals.
|
|
479
458
|
function _collectParamDecls(tokens, i, declaredLocals) {
|
|
480
|
-
const prev =
|
|
459
|
+
const prev = findPrevNonWs(tokens, i);
|
|
481
460
|
if (
|
|
482
461
|
!prev ||
|
|
483
462
|
(prev.value !== "function" && prev.type !== "ident" && prev.value !== ")")
|
|
@@ -583,22 +562,7 @@ function _filterRenameCandidates(declaredLocals) {
|
|
|
583
562
|
}
|
|
584
563
|
}
|
|
585
564
|
|
|
586
|
-
function
|
|
587
|
-
let out = "";
|
|
588
|
-
for (let i = 0; i < tokens.length; i++) {
|
|
589
|
-
const tok = tokens[i];
|
|
590
|
-
if (tok.type === "ident" && renameMap.has(tok.value)) {
|
|
591
|
-
const prev = findPrevNonWsToken(tokens, i);
|
|
592
|
-
out += prev && prev.value === "." ? tok.value : renameMap.get(tok.value);
|
|
593
|
-
} else {
|
|
594
|
-
out += tok.value;
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
return out;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
function mangle(code) {
|
|
601
|
-
const tokens = tokenize(code);
|
|
565
|
+
function mangleTokens(tokens) {
|
|
602
566
|
const { declaredLocals } = _collectLocalDecls(tokens);
|
|
603
567
|
_filterRenameCandidates(declaredLocals);
|
|
604
568
|
|
|
@@ -612,7 +576,15 @@ function mangle(code) {
|
|
|
612
576
|
const nameGen = shortNameGeneratorFn(occupied);
|
|
613
577
|
const renameMap = new Map();
|
|
614
578
|
for (const name of declaredLocals) renameMap.set(name, nameGen());
|
|
615
|
-
return
|
|
579
|
+
return tokens.map((tok, i) => {
|
|
580
|
+
if (tok.type === "ident" && renameMap.has(tok.value)) {
|
|
581
|
+
const prev = findPrevNonWs(tokens, i);
|
|
582
|
+
if (prev?.value !== ".") {
|
|
583
|
+
return { type: "ident", value: renameMap.get(tok.value) };
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return tok;
|
|
587
|
+
});
|
|
616
588
|
}
|
|
617
589
|
|
|
618
590
|
function nextNonWs(tokens, index) {
|
|
@@ -624,14 +596,6 @@ function nextNonWs(tokens, index) {
|
|
|
624
596
|
return null;
|
|
625
597
|
}
|
|
626
598
|
|
|
627
|
-
function findPrevNonWsToken(tokens, index) {
|
|
628
|
-
for (let i = index - 1; i >= 0; i--) {
|
|
629
|
-
if (tokens[i].type !== "ws" && tokens[i].type !== "comment")
|
|
630
|
-
return tokens[i];
|
|
631
|
-
}
|
|
632
|
-
return null;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
599
|
function collectDestructured(tokens, startIdx, locals) {
|
|
636
600
|
const open = tokens[startIdx].value;
|
|
637
601
|
const close = open === "[" ? "]" : "}";
|
|
@@ -647,7 +611,7 @@ function collectDestructured(tokens, startIdx, locals) {
|
|
|
647
611
|
}
|
|
648
612
|
|
|
649
613
|
function _isParamStart(tokens, i) {
|
|
650
|
-
const prev =
|
|
614
|
+
const prev = findPrevNonWs(tokens, i);
|
|
651
615
|
return (
|
|
652
616
|
!prev ||
|
|
653
617
|
prev.value === "(" ||
|
|
@@ -839,28 +803,16 @@ function foldTokens(tokens) {
|
|
|
839
803
|
return current;
|
|
840
804
|
}
|
|
841
805
|
|
|
842
|
-
function foldLiterals(code) {
|
|
843
|
-
const tokens = tokenize(code);
|
|
844
|
-
return compressTokens(foldTokens(tokens));
|
|
845
|
-
}
|
|
846
|
-
|
|
847
806
|
// ── Public API ───────────────────────────────────────────────
|
|
848
807
|
|
|
849
808
|
export function minify(code, options = {}) {
|
|
850
809
|
const { mangle: doMangle = false } = options;
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
// Stage 4: literal folding
|
|
856
|
-
result = foldLiterals(result);
|
|
857
|
-
|
|
858
|
-
// Stage 3: mangling (optional)
|
|
810
|
+
let tokens = tokenize(code).filter(
|
|
811
|
+
(t) => t.type !== "ws" && t.type !== "comment",
|
|
812
|
+
);
|
|
813
|
+
tokens = foldTokens(tokens);
|
|
859
814
|
if (doMangle) {
|
|
860
|
-
|
|
861
|
-
// Re-compress after mangling to clean up any leftover whitespace
|
|
862
|
-
result = compress(result);
|
|
815
|
+
tokens = mangleTokens(tokens);
|
|
863
816
|
}
|
|
864
|
-
|
|
865
|
-
return result;
|
|
817
|
+
return compressTokens(tokens);
|
|
866
818
|
}
|
|
@@ -261,8 +261,7 @@ export const declarationParseMethods = {
|
|
|
261
261
|
},
|
|
262
262
|
|
|
263
263
|
parseVarSpec() {
|
|
264
|
-
const names =
|
|
265
|
-
while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
|
|
264
|
+
const names = this.parseIdentList();
|
|
266
265
|
let type = null;
|
|
267
266
|
let value = null;
|
|
268
267
|
if (
|
|
@@ -284,8 +283,7 @@ export const declarationParseMethods = {
|
|
|
284
283
|
let prevRawExprs = null; // raw AST (with iota Idents) from the previous spec
|
|
285
284
|
let prevType = null;
|
|
286
285
|
while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
|
|
287
|
-
const names =
|
|
288
|
-
while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
|
|
286
|
+
const names = this.parseIdentList();
|
|
289
287
|
let type = null;
|
|
290
288
|
let rawExprs; // before iota substitution
|
|
291
289
|
if (this.check(T.ASSIGN)) {
|
|
@@ -315,8 +313,7 @@ export const declarationParseMethods = {
|
|
|
315
313
|
}
|
|
316
314
|
this.expect(T.RPAREN);
|
|
317
315
|
} else {
|
|
318
|
-
const names =
|
|
319
|
-
while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
|
|
316
|
+
const names = this.parseIdentList();
|
|
320
317
|
let type = null;
|
|
321
318
|
if (!this.check(T.ASSIGN)) type = this.parseType();
|
|
322
319
|
this.expect(T.ASSIGN);
|
|
@@ -25,6 +25,8 @@ const PREC = {
|
|
|
25
25
|
[T.AND_NOT]: 5,
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
+
const UNARY_OPS = new Set([T.NOT, T.MINUS, T.PLUS, T.CARET, T.AMP, T.STAR]);
|
|
29
|
+
|
|
28
30
|
const TYPE_KEYWORDS = new Set([
|
|
29
31
|
"int",
|
|
30
32
|
"int8",
|
|
@@ -128,14 +130,7 @@ export const expressionParserMethods = {
|
|
|
128
130
|
|
|
129
131
|
parseUnary(noCompLit = false) {
|
|
130
132
|
const t = this.peek();
|
|
131
|
-
if (
|
|
132
|
-
t.type === T.NOT ||
|
|
133
|
-
t.type === T.MINUS ||
|
|
134
|
-
t.type === T.PLUS ||
|
|
135
|
-
t.type === T.CARET ||
|
|
136
|
-
t.type === T.AMP ||
|
|
137
|
-
t.type === T.STAR
|
|
138
|
-
) {
|
|
133
|
+
if (UNARY_OPS.has(t.type)) {
|
|
139
134
|
const op = this.advance().value;
|
|
140
135
|
return { kind: "UnaryExpr", op, operand: this.parseUnary(noCompLit) };
|
|
141
136
|
}
|
package/src/parser/index.js
CHANGED
|
@@ -73,9 +73,18 @@ export class Parser {
|
|
|
73
73
|
return this.peek2().type === type;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
match(...
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
match(t1, t2, t3, ...rest) {
|
|
77
|
+
const cur = this.peek().type;
|
|
78
|
+
if (
|
|
79
|
+
cur === t1 ||
|
|
80
|
+
(t2 !== undefined && cur === t2) ||
|
|
81
|
+
(t3 !== undefined && cur === t3)
|
|
82
|
+
) {
|
|
83
|
+
this.advance();
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
for (let i = 0; i < rest.length; i++) {
|
|
87
|
+
if (cur === rest[i]) {
|
|
79
88
|
this.advance();
|
|
80
89
|
return true;
|
|
81
90
|
}
|
|
@@ -83,6 +92,12 @@ export class Parser {
|
|
|
83
92
|
return false;
|
|
84
93
|
}
|
|
85
94
|
|
|
95
|
+
parseIdentList() {
|
|
96
|
+
const names = [this.expect(T.IDENT).value];
|
|
97
|
+
while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
|
|
98
|
+
return names;
|
|
99
|
+
}
|
|
100
|
+
|
|
86
101
|
expect(type) {
|
|
87
102
|
if (!this.check(type))
|
|
88
103
|
throw new ParseError(`expected '${type}'`, this.peek());
|
package/src/parser/statements.js
CHANGED
|
@@ -15,6 +15,20 @@ const PARSE_STMT_DELEGATE = {
|
|
|
15
15
|
[T.LBRACE]: "parseBlock",
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
const ASSIGN_OPS = new Set([
|
|
19
|
+
T.ASSIGN,
|
|
20
|
+
T.PLUS_ASSIGN,
|
|
21
|
+
T.MINUS_ASSIGN,
|
|
22
|
+
T.STAR_ASSIGN,
|
|
23
|
+
T.SLASH_ASSIGN,
|
|
24
|
+
T.PERCENT_ASSIGN,
|
|
25
|
+
T.AMP_ASSIGN,
|
|
26
|
+
T.PIPE_ASSIGN,
|
|
27
|
+
T.CARET_ASSIGN,
|
|
28
|
+
T.LSHIFT_ASSIGN,
|
|
29
|
+
T.RSHIFT_ASSIGN,
|
|
30
|
+
]);
|
|
31
|
+
|
|
18
32
|
export const statementParserMethods = {
|
|
19
33
|
parseBlock() {
|
|
20
34
|
this.expect(T.LBRACE);
|
|
@@ -206,22 +220,13 @@ export const statementParserMethods = {
|
|
|
206
220
|
this.expect(T.LBRACE);
|
|
207
221
|
const cases = [];
|
|
208
222
|
while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const list = this.parseExprList();
|
|
212
|
-
this.expect(T.COLON);
|
|
213
|
-
cases.push({ kind: "CaseClause", list, stmts: this._parseCaseBody() });
|
|
214
|
-
} else if (this.check(T.DEFAULT)) {
|
|
215
|
-
this.advance();
|
|
216
|
-
this.expect(T.COLON);
|
|
217
|
-
cases.push({
|
|
218
|
-
kind: "CaseClause",
|
|
219
|
-
list: null,
|
|
220
|
-
stmts: this._parseCaseBody(),
|
|
221
|
-
});
|
|
222
|
-
} else {
|
|
223
|
+
const isCase = this.match(T.CASE);
|
|
224
|
+
if (!isCase && !this.match(T.DEFAULT)) {
|
|
223
225
|
this.err("Expected case or default");
|
|
224
226
|
}
|
|
227
|
+
const list = isCase ? this.parseExprList() : null;
|
|
228
|
+
this.expect(T.COLON);
|
|
229
|
+
cases.push({ kind: "CaseClause", list, stmts: this._parseCaseBody() });
|
|
225
230
|
}
|
|
226
231
|
this.expect(T.RBRACE);
|
|
227
232
|
return { kind: "SwitchStmt", init, tag, cases };
|
|
@@ -244,18 +249,13 @@ export const statementParserMethods = {
|
|
|
244
249
|
this.expect(T.LBRACE);
|
|
245
250
|
const cases = [];
|
|
246
251
|
while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const types = this._parseTypeSwitchTypeList();
|
|
250
|
-
this.expect(T.COLON);
|
|
251
|
-
cases.push({ types, stmts: this._parseCaseBody() });
|
|
252
|
-
} else if (this.check(T.DEFAULT)) {
|
|
253
|
-
this.advance();
|
|
254
|
-
this.expect(T.COLON);
|
|
255
|
-
cases.push({ types: null, stmts: this._parseCaseBody() });
|
|
256
|
-
} else {
|
|
252
|
+
const isCase = this.match(T.CASE);
|
|
253
|
+
if (!isCase && !this.match(T.DEFAULT)) {
|
|
257
254
|
this.err("Expected case or default");
|
|
258
255
|
}
|
|
256
|
+
const types = isCase ? this._parseTypeSwitchTypeList() : null;
|
|
257
|
+
this.expect(T.COLON);
|
|
258
|
+
cases.push({ types, stmts: this._parseCaseBody() });
|
|
259
259
|
}
|
|
260
260
|
this.expect(T.RBRACE);
|
|
261
261
|
return { kind: "TypeSwitchStmt", assign, expr, cases };
|
|
@@ -305,20 +305,7 @@ export const statementParserMethods = {
|
|
|
305
305
|
}
|
|
306
306
|
|
|
307
307
|
// x = expr or x += expr etc.
|
|
308
|
-
|
|
309
|
-
T.ASSIGN,
|
|
310
|
-
T.PLUS_ASSIGN,
|
|
311
|
-
T.MINUS_ASSIGN,
|
|
312
|
-
T.STAR_ASSIGN,
|
|
313
|
-
T.SLASH_ASSIGN,
|
|
314
|
-
T.PERCENT_ASSIGN,
|
|
315
|
-
T.AMP_ASSIGN,
|
|
316
|
-
T.PIPE_ASSIGN,
|
|
317
|
-
T.CARET_ASSIGN,
|
|
318
|
-
T.LSHIFT_ASSIGN,
|
|
319
|
-
T.RSHIFT_ASSIGN,
|
|
320
|
-
];
|
|
321
|
-
if (assignOps.includes(t.type)) {
|
|
308
|
+
if (ASSIGN_OPS.has(t.type)) {
|
|
322
309
|
const op = this.advance().value;
|
|
323
310
|
const rhs = this.parseExprList();
|
|
324
311
|
return { kind: "AssignStmt", lhs: [first], op, rhs };
|
|
@@ -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
|
+
}
|
package/src/parser/types.js
CHANGED
|
@@ -175,24 +175,80 @@ export const typeParserMethods = {
|
|
|
175
175
|
this.expect(T.LBRACE);
|
|
176
176
|
const fields = [];
|
|
177
177
|
while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
178
|
+
// Embedded pointer field: *Base or *pkg.Base
|
|
179
|
+
if (this.check(T.STAR)) {
|
|
180
|
+
this.advance();
|
|
181
|
+
let typeName = this.expect(T.IDENT).value;
|
|
182
|
+
if (this.check(T.DOT)) {
|
|
183
|
+
this.advance();
|
|
184
|
+
typeName += `.${this.expect(T.IDENT).value}`;
|
|
185
|
+
}
|
|
186
|
+
const type = {
|
|
187
|
+
kind: "PointerType",
|
|
188
|
+
base: { kind: "TypeName", name: typeName },
|
|
189
|
+
};
|
|
190
|
+
let tag = null;
|
|
191
|
+
if (this.check(T.STRING)) {
|
|
192
|
+
tag = this.advance().value;
|
|
193
|
+
}
|
|
194
|
+
fields.push({
|
|
195
|
+
names: [],
|
|
196
|
+
type,
|
|
197
|
+
embedded: true,
|
|
198
|
+
tag,
|
|
199
|
+
});
|
|
200
|
+
this.semi();
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const names = this.parseIdentList();
|
|
205
|
+
// Check if this is a qualified embedded field: pkg.Base
|
|
206
|
+
if (names.length === 1 && this.check(T.DOT)) {
|
|
207
|
+
this.advance();
|
|
208
|
+
const typeName = `${names[0]}.${this.expect(T.IDENT).value}`;
|
|
209
|
+
let tag = null;
|
|
210
|
+
if (this.check(T.STRING)) {
|
|
211
|
+
tag = this.advance().value;
|
|
212
|
+
}
|
|
184
213
|
fields.push({
|
|
185
214
|
names: [],
|
|
186
215
|
type: { kind: "TypeName", name: typeName },
|
|
187
216
|
embedded: true,
|
|
217
|
+
tag,
|
|
188
218
|
});
|
|
189
219
|
this.semi();
|
|
190
220
|
continue;
|
|
191
221
|
}
|
|
222
|
+
|
|
223
|
+
// Embedded field (just a type name, e.g. Base), optionally tagged
|
|
224
|
+
if (
|
|
225
|
+
names.length === 1 &&
|
|
226
|
+
(this.check(T.SEMICOLON) ||
|
|
227
|
+
this.check(T.RBRACE) ||
|
|
228
|
+
this.check(T.STRING))
|
|
229
|
+
) {
|
|
230
|
+
const typeName = names[0];
|
|
231
|
+
let tag = null;
|
|
232
|
+
if (this.check(T.STRING)) {
|
|
233
|
+
tag = this.advance().value;
|
|
234
|
+
}
|
|
235
|
+
fields.push({
|
|
236
|
+
names: [],
|
|
237
|
+
type: { kind: "TypeName", name: typeName },
|
|
238
|
+
embedded: true,
|
|
239
|
+
tag,
|
|
240
|
+
});
|
|
241
|
+
this.semi();
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
|
|
192
245
|
const type = this.parseType();
|
|
193
246
|
// Consume optional struct tag (backtick string), e.g. `json:"name"`
|
|
194
|
-
|
|
195
|
-
|
|
247
|
+
let tag = null;
|
|
248
|
+
if (this.check(T.STRING)) {
|
|
249
|
+
tag = this.advance().value;
|
|
250
|
+
}
|
|
251
|
+
fields.push({ names, type, tag });
|
|
196
252
|
this.semi();
|
|
197
253
|
}
|
|
198
254
|
this.expect(T.RBRACE);
|
package/src/resolver.js
CHANGED
|
@@ -25,32 +25,7 @@ function findNodeModules(startDir) {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
const BUILTIN_PACKAGES = new Set([
|
|
30
|
-
"fmt",
|
|
31
|
-
"strings",
|
|
32
|
-
"bytes",
|
|
33
|
-
"strconv",
|
|
34
|
-
"sort",
|
|
35
|
-
"math",
|
|
36
|
-
"math/bits",
|
|
37
|
-
"math/rand",
|
|
38
|
-
"errors",
|
|
39
|
-
"time",
|
|
40
|
-
"unicode",
|
|
41
|
-
"unicode/utf8",
|
|
42
|
-
"os",
|
|
43
|
-
"slices",
|
|
44
|
-
"html",
|
|
45
|
-
"io",
|
|
46
|
-
"gom",
|
|
47
|
-
"maps",
|
|
48
|
-
"regexp",
|
|
49
|
-
"path",
|
|
50
|
-
"path/filepath",
|
|
51
|
-
"testing",
|
|
52
|
-
"gofront/shared",
|
|
53
|
-
]);
|
|
28
|
+
import { STDLIB_PACKAGES } from "./typechecker/types.js";
|
|
54
29
|
|
|
55
30
|
// Returns true for local relative paths (start with ./ or ../)
|
|
56
31
|
export function isLocalPath(importPath) {
|
|
@@ -58,7 +33,7 @@ export function isLocalPath(importPath) {
|
|
|
58
33
|
}
|
|
59
34
|
|
|
60
35
|
export function isBuiltinPackage(importPath) {
|
|
61
|
-
return
|
|
36
|
+
return STDLIB_PACKAGES.has(importPath);
|
|
62
37
|
}
|
|
63
38
|
|
|
64
39
|
// Resolve a local relative import path to an absolute directory of .go files.
|