gofront 1.6.0 → 1.6.1
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/package.json +1 -1
- package/src/backend/js/expressions.js +238 -109
- package/src/backend/js/index.js +38 -1
- package/src/backend/js/runtime.js +953 -53
- package/src/backend/js/statements.js +55 -67
- 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 +20 -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 +16 -0
- package/src/backend/js/stdlib/io.js +105 -3
- 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/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 +1207 -41
- package/src/backend/wasm/const-fold.js +205 -0
- package/src/backend/wasm/emit-builtins.js +203 -76
- package/src/backend/wasm/emit-exprs.js +464 -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 +115 -87
- package/src/backend/wasm/encode.js +55 -29
- package/src/backend/wasm/index.js +1001 -32
- package/src/backend/wasm/optimize.js +13 -4
- 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 +40 -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/time.js +1200 -0
- package/src/backend/wasm/types.js +153 -1
- package/src/cli-core.js +1 -1
- package/src/compiler.js +18 -4
- package/src/dts-parser.js +79 -126
- package/src/index.js +2 -6
- package/src/lexer.js +80 -29
- package/src/lower/boxing.js +1 -2
- package/src/lower/captures.js +2 -6
- 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/types.js +1 -2
- 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 +81 -13
- package/src/typechecker/expressions.js +102 -2
- package/src/typechecker/resolve.js +36 -6
- package/src/typechecker/stdlib/core.js +280 -89
- package/src/typechecker/stdlib/extended.js +453 -1
- package/src/typechecker/types.js +344 -6
package/src/lexer.js
CHANGED
|
@@ -203,11 +203,25 @@ export class Lexer {
|
|
|
203
203
|
const l = this.line,
|
|
204
204
|
c = this.col;
|
|
205
205
|
this.advance(); // opening "
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
206
|
+
let start = this.pos;
|
|
207
|
+
let parts = null;
|
|
208
|
+
while (this.pos < this.src.length) {
|
|
209
|
+
const ch = this.src[this.pos];
|
|
210
|
+
if (ch === '"') {
|
|
211
|
+
if (parts === null) {
|
|
212
|
+
const str = this.src.slice(start, this.pos);
|
|
213
|
+
this.advance(); // closing "
|
|
214
|
+
return str;
|
|
215
|
+
}
|
|
216
|
+
parts.push(this.src.slice(start, this.pos));
|
|
217
|
+
this.advance(); // closing "
|
|
218
|
+
return parts.join("");
|
|
219
|
+
}
|
|
220
|
+
if (ch === "\n") this.err("Unterminated string literal");
|
|
221
|
+
if (ch === "\\") {
|
|
222
|
+
if (parts === null) parts = [];
|
|
223
|
+
if (this.pos > start) parts.push(this.src.slice(start, this.pos));
|
|
224
|
+
this.advance(); // skip \
|
|
211
225
|
const esc = this.advance();
|
|
212
226
|
switch (esc) {
|
|
213
227
|
case "n":
|
|
@@ -231,14 +245,12 @@ export class Lexer {
|
|
|
231
245
|
default:
|
|
232
246
|
parts.push(`\\${esc}`);
|
|
233
247
|
}
|
|
248
|
+
start = this.pos;
|
|
234
249
|
} else {
|
|
235
|
-
|
|
250
|
+
this.advance();
|
|
236
251
|
}
|
|
237
252
|
}
|
|
238
|
-
|
|
239
|
-
throw new LexError("Unterminated string", l, c, this.filename, this.src);
|
|
240
|
-
this.advance(); // closing "
|
|
241
|
-
return parts.join("");
|
|
253
|
+
throw new LexError("Unterminated string", l, c, this.filename, this.src);
|
|
242
254
|
}
|
|
243
255
|
|
|
244
256
|
readRawString() {
|
|
@@ -383,39 +395,78 @@ export class Lexer {
|
|
|
383
395
|
_readDecimalExponent(n) {
|
|
384
396
|
n += this.advance(); // consume e/E
|
|
385
397
|
if (this.peek() === "+" || this.peek() === "-") n += this.advance();
|
|
386
|
-
while (this.pos < this.src.length
|
|
387
|
-
const
|
|
388
|
-
if (
|
|
398
|
+
while (this.pos < this.src.length) {
|
|
399
|
+
const c = this.src.charCodeAt(this.pos);
|
|
400
|
+
if (c >= 48 && c <= 57) {
|
|
401
|
+
n += this.advance();
|
|
402
|
+
} else if (c === 95) {
|
|
403
|
+
this.advance();
|
|
404
|
+
} else {
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
389
407
|
}
|
|
390
408
|
return n;
|
|
391
409
|
}
|
|
392
410
|
|
|
393
411
|
_readDecimalNumber(n) {
|
|
394
412
|
let isFloat = false;
|
|
395
|
-
while (this.pos < this.src.length
|
|
396
|
-
const
|
|
397
|
-
if (
|
|
413
|
+
while (this.pos < this.src.length) {
|
|
414
|
+
const c = this.src.charCodeAt(this.pos);
|
|
415
|
+
if (c >= 48 && c <= 57) {
|
|
416
|
+
n += this.advance();
|
|
417
|
+
} else if (c === 95) {
|
|
418
|
+
this.advance(); // skip _
|
|
419
|
+
} else {
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
398
422
|
}
|
|
399
|
-
if (
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
423
|
+
if (
|
|
424
|
+
this.pos + 1 < this.src.length &&
|
|
425
|
+
this.src.charCodeAt(this.pos) === 46
|
|
426
|
+
) {
|
|
427
|
+
const next = this.src.charCodeAt(this.pos + 1);
|
|
428
|
+
if (next >= 48 && next <= 57) {
|
|
429
|
+
isFloat = true;
|
|
430
|
+
n += this.advance();
|
|
431
|
+
while (this.pos < this.src.length) {
|
|
432
|
+
const c = this.src.charCodeAt(this.pos);
|
|
433
|
+
if (c >= 48 && c <= 57) {
|
|
434
|
+
n += this.advance();
|
|
435
|
+
} else if (c === 95) {
|
|
436
|
+
this.advance(); // skip _
|
|
437
|
+
} else {
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
405
441
|
}
|
|
406
442
|
}
|
|
407
|
-
if (this.
|
|
408
|
-
|
|
409
|
-
|
|
443
|
+
if (this.pos < this.src.length) {
|
|
444
|
+
const c = this.src.charCodeAt(this.pos);
|
|
445
|
+
if (c === 101 || c === 69) {
|
|
446
|
+
isFloat = true;
|
|
447
|
+
n = this._readDecimalExponent(n);
|
|
448
|
+
}
|
|
410
449
|
}
|
|
411
450
|
return { n, isFloat };
|
|
412
451
|
}
|
|
413
452
|
|
|
414
453
|
readIdent() {
|
|
415
|
-
|
|
416
|
-
while (this.pos < this.src.length
|
|
417
|
-
|
|
418
|
-
|
|
454
|
+
const start = this.pos;
|
|
455
|
+
while (this.pos < this.src.length) {
|
|
456
|
+
const code = this.src.charCodeAt(this.pos);
|
|
457
|
+
if (
|
|
458
|
+
(code >= 97 && code <= 122) ||
|
|
459
|
+
(code >= 65 && code <= 90) ||
|
|
460
|
+
(code >= 48 && code <= 57) ||
|
|
461
|
+
code === 95
|
|
462
|
+
) {
|
|
463
|
+
this.pos++;
|
|
464
|
+
} else {
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
this.col += this.pos - start;
|
|
469
|
+
return this.src.slice(start, this.pos);
|
|
419
470
|
}
|
|
420
471
|
|
|
421
472
|
// ── Semicolon insertion ──────────────────────────────────────
|
package/src/lower/boxing.js
CHANGED
package/src/lower/captures.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Identifies free variables captured by closures and determines which ones
|
|
3
3
|
// are mutated and thus require heap-allocated environment cells for WASM closures.
|
|
4
4
|
|
|
5
|
-
import { collectMutatedVars
|
|
5
|
+
import { collectMutatedVars } from "./ownership.js";
|
|
6
6
|
import { forEachChild } from "./walk.js";
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -151,11 +151,7 @@ export function analyzeCaptures(fnDecl) {
|
|
|
151
151
|
result.capturesByClosure.set(node, captured);
|
|
152
152
|
|
|
153
153
|
for (const varName of captured) {
|
|
154
|
-
if (
|
|
155
|
-
mutatedVars.has(varName) ||
|
|
156
|
-
nodeMutatesVar(fnDecl.body, varName) ||
|
|
157
|
-
nodeMutatesVar(node.body, varName)
|
|
158
|
-
) {
|
|
154
|
+
if (mutatedVars.has(varName)) {
|
|
159
155
|
result.mutatedCaptures.add(varName);
|
|
160
156
|
}
|
|
161
157
|
}
|
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 };
|
package/src/parser/types.js
CHANGED
|
@@ -175,8 +175,7 @@ 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
|
-
const names =
|
|
179
|
-
while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
|
|
178
|
+
const names = this.parseIdentList();
|
|
180
179
|
// Check if this is an embedded field (just a type name)
|
|
181
180
|
// e.g., type T struct { Base }
|
|
182
181
|
if (this.check(T.SEMICOLON) || this.check(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.
|