gofront 0.0.7 → 0.0.9
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 +104 -18
- package/package.json +9 -4
- package/src/codegen/expressions.js +351 -6
- package/src/codegen/source-map.js +8 -5
- package/src/codegen.js +329 -4
- package/src/compiler.js +39 -4
- package/src/dev-server.js +1 -0
- package/src/index.js +10 -6
- package/src/lexer.js +14 -14
- package/src/parser/expressions.js +3 -2
- package/src/parser/statements.js +5 -1
- package/src/parser.js +8 -1
- package/src/resolver.js +29 -0
- package/src/templ-lexer.js +737 -0
- package/src/templ-parser.js +235 -0
- package/src/typechecker/stdlib.js +305 -2
- package/src/typechecker/types.js +6 -18
- package/src/typechecker.js +30 -2
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// Templ parser: extends the Go Parser to handle TemplDecl nodes.
|
|
2
|
+
//
|
|
3
|
+
// A .templ file is tokenised by TemplLexer into a mixed stream of standard Go
|
|
4
|
+
// tokens (T.*) and HTML/templ tokens (TT.*). This parser handles TT.TEMPL_KW
|
|
5
|
+
// in parseTopDecl; everything else is delegated to the base Parser.
|
|
6
|
+
//
|
|
7
|
+
// Produced AST additions:
|
|
8
|
+
// TemplDecl { kind, name, params, body: TemplNode[] }
|
|
9
|
+
// TemplElement { kind, tag, attrs, children }
|
|
10
|
+
// TemplText { kind, value }
|
|
11
|
+
// TemplExpr { kind, tokens } ← raw Go token array
|
|
12
|
+
// TemplComponent { kind, tokens } ← raw Go token array for call
|
|
13
|
+
// TemplChildren { kind }
|
|
14
|
+
// TemplIf { kind, condTokens, then: TemplNode[], else_: TemplNode[] | null }
|
|
15
|
+
// TemplFor { kind, stmtTokens, body: TemplNode[] }
|
|
16
|
+
// TemplSwitch { kind, exprTokens, cases: TemplCase[] }
|
|
17
|
+
// TemplCase { kind, caseTokens: tokens | null (null = default), body: TemplNode[] }
|
|
18
|
+
|
|
19
|
+
import { T } from "./lexer.js";
|
|
20
|
+
import { Parser } from "./parser.js";
|
|
21
|
+
import { TT } from "./templ-lexer.js";
|
|
22
|
+
|
|
23
|
+
const VOID_ELEMENTS = new Set([
|
|
24
|
+
"area",
|
|
25
|
+
"base",
|
|
26
|
+
"br",
|
|
27
|
+
"col",
|
|
28
|
+
"embed",
|
|
29
|
+
"hr",
|
|
30
|
+
"img",
|
|
31
|
+
"input",
|
|
32
|
+
"link",
|
|
33
|
+
"meta",
|
|
34
|
+
"param",
|
|
35
|
+
"source",
|
|
36
|
+
"track",
|
|
37
|
+
"wbr",
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
export class TemplParser extends Parser {
|
|
41
|
+
// ── Top-level declaration override ───────────────────────────
|
|
42
|
+
|
|
43
|
+
parseTopDecl() {
|
|
44
|
+
if (this.check(TT.TEMPL_KW)) return this.parseTemplDecl();
|
|
45
|
+
return super.parseTopDecl();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ── TemplDecl ─────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
parseTemplDecl() {
|
|
51
|
+
const _line = this.peek().line;
|
|
52
|
+
this.advance(); // consume TEMPL_KW
|
|
53
|
+
|
|
54
|
+
const name = this.expect(T.IDENT).value;
|
|
55
|
+
|
|
56
|
+
// Parse parameter list (standard Go params: LPAREN...RPAREN)
|
|
57
|
+
const params = this.parseParamList();
|
|
58
|
+
|
|
59
|
+
// Optional semicolon between ) and {
|
|
60
|
+
this.semi();
|
|
61
|
+
|
|
62
|
+
// Consume the opening { of the HTML body
|
|
63
|
+
this.expect(T.LBRACE);
|
|
64
|
+
|
|
65
|
+
// Parse the HTML body; parseHtmlChildren consumes the closing RBRACE
|
|
66
|
+
const body = this.parseHtmlChildren();
|
|
67
|
+
|
|
68
|
+
this.semi();
|
|
69
|
+
|
|
70
|
+
return { kind: "TemplDecl", name, params, body, _line };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── HTML node parsing ─────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
// Parse zero or more HTML nodes until RBRACE (end of body) or EOF.
|
|
76
|
+
// Consumes the RBRACE.
|
|
77
|
+
parseHtmlChildren() {
|
|
78
|
+
const nodes = [];
|
|
79
|
+
while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
|
|
80
|
+
if (this.check(TT.TEMPL_ELSE)) break; // handled by caller (parseTemplIf)
|
|
81
|
+
const node = this.parseHtmlNode();
|
|
82
|
+
if (node) nodes.push(node);
|
|
83
|
+
}
|
|
84
|
+
if (this.check(T.RBRACE)) this.advance(); // consume }
|
|
85
|
+
return nodes;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Parse a single HTML node from the current token.
|
|
89
|
+
parseHtmlNode() {
|
|
90
|
+
const t = this.peek();
|
|
91
|
+
switch (t.type) {
|
|
92
|
+
case TT.HTML_OPEN:
|
|
93
|
+
return this.parseHtmlElement();
|
|
94
|
+
case TT.HTML_SELF: {
|
|
95
|
+
this.advance();
|
|
96
|
+
return {
|
|
97
|
+
kind: "TemplElement",
|
|
98
|
+
tag: t.value.tag,
|
|
99
|
+
attrs: t.value.attrs,
|
|
100
|
+
children: [],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
case TT.HTML_TEXT:
|
|
104
|
+
this.advance();
|
|
105
|
+
return { kind: "TemplText", value: t.value };
|
|
106
|
+
case TT.TEMPL_EXPR:
|
|
107
|
+
this.advance();
|
|
108
|
+
return { kind: "TemplExpr", tokens: t.value };
|
|
109
|
+
case TT.TEMPL_COMP:
|
|
110
|
+
this.advance();
|
|
111
|
+
return { kind: "TemplComponent", tokens: t.value };
|
|
112
|
+
case TT.TEMPL_CHILDREN:
|
|
113
|
+
this.advance();
|
|
114
|
+
return { kind: "TemplChildren" };
|
|
115
|
+
case TT.TEMPL_IF: {
|
|
116
|
+
this.advance();
|
|
117
|
+
return this.parseTemplIf(t);
|
|
118
|
+
}
|
|
119
|
+
case TT.TEMPL_FOR: {
|
|
120
|
+
this.advance();
|
|
121
|
+
return this.parseTemplFor(t);
|
|
122
|
+
}
|
|
123
|
+
case TT.TEMPL_SWITCH: {
|
|
124
|
+
this.advance();
|
|
125
|
+
return this.parseTemplSwitch(t);
|
|
126
|
+
}
|
|
127
|
+
default:
|
|
128
|
+
// Unknown token inside HTML body — skip it to avoid infinite loops
|
|
129
|
+
this.advance();
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Parse children of an element until the matching close tag.
|
|
135
|
+
parseHtmlElement() {
|
|
136
|
+
const openTok = this.advance(); // HTML_OPEN
|
|
137
|
+
const { tag, attrs } = openTok.value;
|
|
138
|
+
|
|
139
|
+
// Void elements have no children and no close tag even without />
|
|
140
|
+
if (VOID_ELEMENTS.has(tag)) {
|
|
141
|
+
return { kind: "TemplElement", tag, attrs, children: [] };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const children = [];
|
|
145
|
+
while (!this.check(T.EOF) && !this.check(T.RBRACE)) {
|
|
146
|
+
if (this.check(TT.HTML_CLOSE)) break;
|
|
147
|
+
// Also break on a close tag for a different ancestor (shouldn't happen in
|
|
148
|
+
// well-formed HTML, but prevents infinite loops on malformed input)
|
|
149
|
+
if (this.check(TT.TEMPL_ELSE)) break;
|
|
150
|
+
const node = this.parseHtmlNode();
|
|
151
|
+
if (node) children.push(node);
|
|
152
|
+
}
|
|
153
|
+
if (this.check(TT.HTML_CLOSE)) {
|
|
154
|
+
const closeTag = this.peek().value;
|
|
155
|
+
if (closeTag !== tag) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
`${this.filename ?? "<templ>"}:${this.peek().line}: unclosed tag <${tag}>, got </${closeTag}>`,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
this.advance(); // consume HTML_CLOSE
|
|
161
|
+
} else if (!VOID_ELEMENTS.has(tag)) {
|
|
162
|
+
// Non-void element with no close tag — parse error
|
|
163
|
+
throw new Error(
|
|
164
|
+
`${this.filename ?? "<templ>"}:${openTok.line}: unclosed tag <${tag}>`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
return { kind: "TemplElement", tag, attrs, children };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Parse a TemplIf node. `tok` is the TEMPL_IF token (already advanced).
|
|
171
|
+
parseTemplIf(tok) {
|
|
172
|
+
const condTokens = tok.value; // array of Go tokens
|
|
173
|
+
const then = this.parseHtmlChildren(); // reads body until RBRACE
|
|
174
|
+
let else_ = null;
|
|
175
|
+
if (this.check(TT.TEMPL_ELSE)) {
|
|
176
|
+
this.advance(); // consume TEMPL_ELSE
|
|
177
|
+
if (this.check(TT.TEMPL_IF)) {
|
|
178
|
+
const t2 = this.advance();
|
|
179
|
+
else_ = [this.parseTemplIf(t2)]; // else if → wrap in array
|
|
180
|
+
} else {
|
|
181
|
+
else_ = this.parseHtmlChildren(); // else body until RBRACE
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return { kind: "TemplIf", condTokens, then, else_ };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Parse a TemplFor node. `tok` is the TEMPL_FOR token (already advanced).
|
|
188
|
+
parseTemplFor(tok) {
|
|
189
|
+
const stmtTokens = tok.value;
|
|
190
|
+
const body = this.parseHtmlChildren();
|
|
191
|
+
return { kind: "TemplFor", stmtTokens, body };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Parse a TemplSwitch node. `tok` is the TEMPL_SWITCH token (already advanced).
|
|
195
|
+
parseTemplSwitch(tok) {
|
|
196
|
+
const exprTokens = tok.value;
|
|
197
|
+
const cases = [];
|
|
198
|
+
while (
|
|
199
|
+
!this.check(TT.TEMPL_END) &&
|
|
200
|
+
!this.check(T.RBRACE) &&
|
|
201
|
+
!this.check(T.EOF)
|
|
202
|
+
) {
|
|
203
|
+
if (this.check(TT.TEMPL_CASE)) {
|
|
204
|
+
const caseTok = this.advance();
|
|
205
|
+
const body = this.parseTemplCaseBody();
|
|
206
|
+
cases.push({ kind: "TemplCase", caseTokens: caseTok.value, body });
|
|
207
|
+
} else if (this.check(TT.TEMPL_DEFAULT)) {
|
|
208
|
+
this.advance();
|
|
209
|
+
const body = this.parseTemplCaseBody();
|
|
210
|
+
cases.push({ kind: "TemplCase", caseTokens: null, body });
|
|
211
|
+
} else {
|
|
212
|
+
this.advance(); // skip unexpected
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (this.check(TT.TEMPL_END)) this.advance(); // consume closing }
|
|
216
|
+
return { kind: "TemplSwitch", exprTokens, cases };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Parse HTML nodes for a switch case body — stops at CASE/DEFAULT/END/RBRACE.
|
|
220
|
+
parseTemplCaseBody() {
|
|
221
|
+
const nodes = [];
|
|
222
|
+
while (
|
|
223
|
+
!this.check(TT.TEMPL_CASE) &&
|
|
224
|
+
!this.check(TT.TEMPL_DEFAULT) &&
|
|
225
|
+
!this.check(TT.TEMPL_END) &&
|
|
226
|
+
!this.check(T.RBRACE) &&
|
|
227
|
+
!this.check(T.EOF)
|
|
228
|
+
) {
|
|
229
|
+
if (this.check(TT.TEMPL_ELSE)) break;
|
|
230
|
+
const node = this.parseHtmlNode();
|
|
231
|
+
if (node) nodes.push(node);
|
|
232
|
+
}
|
|
233
|
+
return nodes;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
import { ANY, BOOL, ERROR, FLOAT64, INT, STRING, VOID } from "./types.js";
|
|
10
10
|
|
|
11
|
+
// time.Time named type — used by the time package
|
|
12
|
+
const TIME_T = { kind: "named", name: "time.Time", underlying: ANY };
|
|
13
|
+
|
|
11
14
|
export function setupGlobals(globals, types) {
|
|
12
15
|
// Browser globals — typed as 'any' so any access/call is permitted
|
|
13
16
|
const browserGlobals = [
|
|
@@ -113,6 +116,24 @@ export function setupGlobals(globals, types) {
|
|
|
113
116
|
returns: [INT, ERROR],
|
|
114
117
|
variadic: true,
|
|
115
118
|
},
|
|
119
|
+
Sscan: {
|
|
120
|
+
kind: "func",
|
|
121
|
+
params: [STRING, ANY],
|
|
122
|
+
returns: [INT, ERROR],
|
|
123
|
+
variadic: true,
|
|
124
|
+
},
|
|
125
|
+
Sscanln: {
|
|
126
|
+
kind: "func",
|
|
127
|
+
params: [STRING, ANY],
|
|
128
|
+
returns: [INT, ERROR],
|
|
129
|
+
variadic: true,
|
|
130
|
+
},
|
|
131
|
+
Sscanf: {
|
|
132
|
+
kind: "func",
|
|
133
|
+
params: [STRING, STRING, ANY],
|
|
134
|
+
returns: [INT, ERROR],
|
|
135
|
+
variadic: true,
|
|
136
|
+
},
|
|
116
137
|
},
|
|
117
138
|
});
|
|
118
139
|
|
|
@@ -164,6 +185,71 @@ export function setupGlobals(globals, types) {
|
|
|
164
185
|
returns: [STRING],
|
|
165
186
|
},
|
|
166
187
|
EqualFold: strFn2(STRING, BOOL),
|
|
188
|
+
Fields: strFn1({ kind: "slice", elem: STRING }),
|
|
189
|
+
Cut: {
|
|
190
|
+
kind: "func",
|
|
191
|
+
params: [STRING, STRING],
|
|
192
|
+
returns: [STRING, STRING, BOOL],
|
|
193
|
+
},
|
|
194
|
+
CutPrefix: {
|
|
195
|
+
kind: "func",
|
|
196
|
+
params: [STRING, STRING],
|
|
197
|
+
returns: [STRING, BOOL],
|
|
198
|
+
},
|
|
199
|
+
CutSuffix: {
|
|
200
|
+
kind: "func",
|
|
201
|
+
params: [STRING, STRING],
|
|
202
|
+
returns: [STRING, BOOL],
|
|
203
|
+
},
|
|
204
|
+
SplitN: strFn3(STRING, INT, { kind: "slice", elem: STRING }),
|
|
205
|
+
SplitAfter: strFn2(STRING, { kind: "slice", elem: STRING }),
|
|
206
|
+
SplitAfterN: strFn3(STRING, INT, { kind: "slice", elem: STRING }),
|
|
207
|
+
IndexAny: strFn2(STRING, INT),
|
|
208
|
+
LastIndexAny: strFn2(STRING, INT),
|
|
209
|
+
ContainsAny: strFn2(STRING, BOOL),
|
|
210
|
+
ContainsRune: strFn2(INT, BOOL),
|
|
211
|
+
IndexRune: strFn2(INT, INT),
|
|
212
|
+
IndexByte: strFn2(INT, INT),
|
|
213
|
+
LastIndexByte: strFn2(INT, INT),
|
|
214
|
+
Map: {
|
|
215
|
+
kind: "func",
|
|
216
|
+
params: [ANY, STRING],
|
|
217
|
+
returns: [STRING],
|
|
218
|
+
},
|
|
219
|
+
Title: strFn1(STRING),
|
|
220
|
+
ToTitle: strFn1(STRING),
|
|
221
|
+
TrimFunc: {
|
|
222
|
+
kind: "func",
|
|
223
|
+
params: [STRING, ANY],
|
|
224
|
+
returns: [STRING],
|
|
225
|
+
},
|
|
226
|
+
TrimLeftFunc: {
|
|
227
|
+
kind: "func",
|
|
228
|
+
params: [STRING, ANY],
|
|
229
|
+
returns: [STRING],
|
|
230
|
+
},
|
|
231
|
+
TrimRightFunc: {
|
|
232
|
+
kind: "func",
|
|
233
|
+
params: [STRING, ANY],
|
|
234
|
+
returns: [STRING],
|
|
235
|
+
},
|
|
236
|
+
IndexFunc: {
|
|
237
|
+
kind: "func",
|
|
238
|
+
params: [STRING, ANY],
|
|
239
|
+
returns: [INT],
|
|
240
|
+
},
|
|
241
|
+
LastIndexFunc: {
|
|
242
|
+
kind: "func",
|
|
243
|
+
params: [STRING, ANY],
|
|
244
|
+
returns: [INT],
|
|
245
|
+
},
|
|
246
|
+
NewReplacer: {
|
|
247
|
+
kind: "func",
|
|
248
|
+
params: [STRING],
|
|
249
|
+
returns: [ANY],
|
|
250
|
+
variadic: true,
|
|
251
|
+
},
|
|
252
|
+
NewReader: { kind: "func", params: [STRING], returns: [ANY] },
|
|
167
253
|
},
|
|
168
254
|
});
|
|
169
255
|
|
|
@@ -205,6 +291,46 @@ export function setupGlobals(globals, types) {
|
|
|
205
291
|
params: [{ kind: "slice", elem: BYTE_SLICE }, BYTE_SLICE],
|
|
206
292
|
returns: [BYTE_SLICE],
|
|
207
293
|
},
|
|
294
|
+
ReplaceAll: {
|
|
295
|
+
kind: "func",
|
|
296
|
+
params: [BYTE_SLICE, BYTE_SLICE, BYTE_SLICE],
|
|
297
|
+
returns: [BYTE_SLICE],
|
|
298
|
+
},
|
|
299
|
+
TrimPrefix: byFn2(BYTE_SLICE, BYTE_SLICE),
|
|
300
|
+
TrimSuffix: byFn2(BYTE_SLICE, BYTE_SLICE),
|
|
301
|
+
TrimLeft: byFn2(STRING, BYTE_SLICE),
|
|
302
|
+
TrimRight: byFn2(STRING, BYTE_SLICE),
|
|
303
|
+
TrimFunc: {
|
|
304
|
+
kind: "func",
|
|
305
|
+
params: [BYTE_SLICE, ANY],
|
|
306
|
+
returns: [BYTE_SLICE],
|
|
307
|
+
},
|
|
308
|
+
IndexByte: byFn2(INT, INT),
|
|
309
|
+
LastIndex: byFn2(BYTE_SLICE, INT),
|
|
310
|
+
LastIndexByte: byFn2(INT, INT),
|
|
311
|
+
Fields: byFn1({ kind: "slice", elem: BYTE_SLICE }),
|
|
312
|
+
Cut: {
|
|
313
|
+
kind: "func",
|
|
314
|
+
params: [BYTE_SLICE, BYTE_SLICE],
|
|
315
|
+
returns: [BYTE_SLICE, BYTE_SLICE, BOOL],
|
|
316
|
+
},
|
|
317
|
+
ContainsAny: byFn2(STRING, BOOL),
|
|
318
|
+
ContainsRune: byFn2(INT, BOOL),
|
|
319
|
+
Map: {
|
|
320
|
+
kind: "func",
|
|
321
|
+
params: [ANY, BYTE_SLICE],
|
|
322
|
+
returns: [BYTE_SLICE],
|
|
323
|
+
},
|
|
324
|
+
SplitN: {
|
|
325
|
+
kind: "func",
|
|
326
|
+
params: [BYTE_SLICE, BYTE_SLICE, INT],
|
|
327
|
+
returns: [{ kind: "slice", elem: BYTE_SLICE }],
|
|
328
|
+
},
|
|
329
|
+
NewReader: {
|
|
330
|
+
kind: "func",
|
|
331
|
+
params: [BYTE_SLICE],
|
|
332
|
+
returns: [ANY],
|
|
333
|
+
},
|
|
208
334
|
},
|
|
209
335
|
});
|
|
210
336
|
|
|
@@ -241,6 +367,22 @@ export function setupGlobals(globals, types) {
|
|
|
241
367
|
params: [STRING],
|
|
242
368
|
returns: [BOOL, ERROR],
|
|
243
369
|
},
|
|
370
|
+
Quote: { kind: "func", params: [STRING], returns: [STRING] },
|
|
371
|
+
Unquote: {
|
|
372
|
+
kind: "func",
|
|
373
|
+
params: [STRING],
|
|
374
|
+
returns: [STRING, ERROR],
|
|
375
|
+
},
|
|
376
|
+
AppendInt: {
|
|
377
|
+
kind: "func",
|
|
378
|
+
params: [{ kind: "slice", elem: INT }, INT, INT],
|
|
379
|
+
returns: [{ kind: "slice", elem: INT }],
|
|
380
|
+
},
|
|
381
|
+
AppendFloat: {
|
|
382
|
+
kind: "func",
|
|
383
|
+
params: [{ kind: "slice", elem: INT }, FLOAT64, INT, INT, INT],
|
|
384
|
+
returns: [{ kind: "slice", elem: INT }],
|
|
385
|
+
},
|
|
244
386
|
},
|
|
245
387
|
});
|
|
246
388
|
|
|
@@ -279,6 +421,26 @@ export function setupGlobals(globals, types) {
|
|
|
279
421
|
params: [ANY, ANY],
|
|
280
422
|
returns: [BOOL],
|
|
281
423
|
},
|
|
424
|
+
Search: {
|
|
425
|
+
kind: "func",
|
|
426
|
+
params: [INT, ANY],
|
|
427
|
+
returns: [INT],
|
|
428
|
+
},
|
|
429
|
+
IntsAreSorted: {
|
|
430
|
+
kind: "func",
|
|
431
|
+
params: [{ kind: "slice", elem: INT }],
|
|
432
|
+
returns: [BOOL],
|
|
433
|
+
},
|
|
434
|
+
Float64sAreSorted: {
|
|
435
|
+
kind: "func",
|
|
436
|
+
params: [{ kind: "slice", elem: FLOAT64 }],
|
|
437
|
+
returns: [BOOL],
|
|
438
|
+
},
|
|
439
|
+
StringsAreSorted: {
|
|
440
|
+
kind: "func",
|
|
441
|
+
params: [{ kind: "slice", elem: STRING }],
|
|
442
|
+
returns: [BOOL],
|
|
443
|
+
},
|
|
282
444
|
},
|
|
283
445
|
});
|
|
284
446
|
|
|
@@ -327,6 +489,26 @@ export function setupGlobals(globals, types) {
|
|
|
327
489
|
returns: [BOOL],
|
|
328
490
|
},
|
|
329
491
|
NaN: { kind: "func", params: [], returns: [FLOAT64] },
|
|
492
|
+
Atan: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
493
|
+
Atan2: { kind: "func", params: [FLOAT64, FLOAT64], returns: [FLOAT64] },
|
|
494
|
+
Asin: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
495
|
+
Acos: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
496
|
+
Exp: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
497
|
+
Exp2: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
498
|
+
Trunc: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
499
|
+
Hypot: { kind: "func", params: [FLOAT64, FLOAT64], returns: [FLOAT64] },
|
|
500
|
+
Signbit: { kind: "func", params: [FLOAT64], returns: [BOOL] },
|
|
501
|
+
Copysign: {
|
|
502
|
+
kind: "func",
|
|
503
|
+
params: [FLOAT64, FLOAT64],
|
|
504
|
+
returns: [FLOAT64],
|
|
505
|
+
},
|
|
506
|
+
Dim: { kind: "func", params: [FLOAT64, FLOAT64], returns: [FLOAT64] },
|
|
507
|
+
Remainder: {
|
|
508
|
+
kind: "func",
|
|
509
|
+
params: [FLOAT64, FLOAT64],
|
|
510
|
+
returns: [FLOAT64],
|
|
511
|
+
},
|
|
330
512
|
// Constants
|
|
331
513
|
Pi: FLOAT64,
|
|
332
514
|
E: FLOAT64,
|
|
@@ -353,15 +535,57 @@ export function setupGlobals(globals, types) {
|
|
|
353
535
|
kind: "namespace",
|
|
354
536
|
name: "time",
|
|
355
537
|
members: {
|
|
356
|
-
Now: { kind: "func", params: [], returns: [
|
|
357
|
-
Since: { kind: "func", params: [
|
|
538
|
+
Now: { kind: "func", params: [], returns: [TIME_T] },
|
|
539
|
+
Since: { kind: "func", params: [TIME_T], returns: [INT] },
|
|
358
540
|
Sleep: { kind: "func", params: [ANY], returns: [VOID], async: true },
|
|
541
|
+
Parse: {
|
|
542
|
+
kind: "func",
|
|
543
|
+
params: [STRING, STRING],
|
|
544
|
+
returns: [TIME_T, ERROR],
|
|
545
|
+
},
|
|
546
|
+
Unix: { kind: "func", params: [INT, INT], returns: [TIME_T] },
|
|
547
|
+
Date: {
|
|
548
|
+
kind: "func",
|
|
549
|
+
params: [INT, INT, INT, INT, INT, INT, INT, ANY],
|
|
550
|
+
returns: [TIME_T],
|
|
551
|
+
},
|
|
359
552
|
Millisecond: INT,
|
|
360
553
|
Second: INT,
|
|
361
554
|
Minute: INT,
|
|
362
555
|
Hour: INT,
|
|
556
|
+
// Layout constants
|
|
557
|
+
RFC3339: STRING,
|
|
558
|
+
RFC3339Nano: STRING,
|
|
559
|
+
DateOnly: STRING,
|
|
560
|
+
TimeOnly: STRING,
|
|
561
|
+
DateTime: STRING,
|
|
562
|
+
// Location constants
|
|
563
|
+
UTC: ANY,
|
|
564
|
+
Local: ANY,
|
|
565
|
+
// Month constants
|
|
566
|
+
January: INT,
|
|
567
|
+
February: INT,
|
|
568
|
+
March: INT,
|
|
569
|
+
April: INT,
|
|
570
|
+
May: INT,
|
|
571
|
+
June: INT,
|
|
572
|
+
July: INT,
|
|
573
|
+
August: INT,
|
|
574
|
+
September: INT,
|
|
575
|
+
October: INT,
|
|
576
|
+
November: INT,
|
|
577
|
+
December: INT,
|
|
578
|
+
// Weekday constants
|
|
579
|
+
Sunday: INT,
|
|
580
|
+
Monday: INT,
|
|
581
|
+
Tuesday: INT,
|
|
582
|
+
Wednesday: INT,
|
|
583
|
+
Thursday: INT,
|
|
584
|
+
Friday: INT,
|
|
585
|
+
Saturday: INT,
|
|
363
586
|
},
|
|
364
587
|
});
|
|
588
|
+
types.set("time.Time", TIME_T);
|
|
365
589
|
|
|
366
590
|
// unicode package
|
|
367
591
|
const runeToStr = { kind: "func", params: [INT], returns: [BOOL] };
|
|
@@ -455,22 +679,30 @@ export function setupGlobals(globals, types) {
|
|
|
455
679
|
// io package — Writer interface + WriteString
|
|
456
680
|
// io.Writer is typed as ANY so GoFront's built-in writer types (strings.Builder,
|
|
457
681
|
// bytes.Buffer) are accepted without explicit interface-satisfaction registration.
|
|
682
|
+
const BYTE_SLICE_IO = { kind: "slice", elem: INT };
|
|
458
683
|
globals.define("io", {
|
|
459
684
|
kind: "namespace",
|
|
460
685
|
name: "io",
|
|
461
686
|
members: {
|
|
462
687
|
Writer: ANY,
|
|
688
|
+
Reader: ANY,
|
|
463
689
|
EOF: ERROR,
|
|
464
690
|
WriteString: {
|
|
465
691
|
kind: "func",
|
|
466
692
|
params: [ANY, STRING],
|
|
467
693
|
returns: [INT, ERROR],
|
|
468
694
|
},
|
|
695
|
+
ReadAll: {
|
|
696
|
+
kind: "func",
|
|
697
|
+
params: [ANY],
|
|
698
|
+
returns: [BYTE_SLICE_IO, ERROR],
|
|
699
|
+
},
|
|
469
700
|
Discard: ANY,
|
|
470
701
|
},
|
|
471
702
|
});
|
|
472
703
|
// Register io.Writer as a type alias so it resolves in parameter/field annotations.
|
|
473
704
|
types.set("io.Writer", ANY);
|
|
705
|
+
types.set("io.Reader", ANY);
|
|
474
706
|
|
|
475
707
|
// gom package — browser-native component built-in
|
|
476
708
|
const GOM_NODE_T = {
|
|
@@ -742,6 +974,77 @@ export function setupGlobals(globals, types) {
|
|
|
742
974
|
},
|
|
743
975
|
});
|
|
744
976
|
|
|
977
|
+
// math/rand package — import "math/rand" → local name "rand"
|
|
978
|
+
globals.define("rand", {
|
|
979
|
+
kind: "namespace",
|
|
980
|
+
name: "rand",
|
|
981
|
+
members: {
|
|
982
|
+
Intn: { kind: "func", params: [INT], returns: [INT] },
|
|
983
|
+
Float64: { kind: "func", params: [], returns: [FLOAT64] },
|
|
984
|
+
Float32: { kind: "func", params: [], returns: [FLOAT64] },
|
|
985
|
+
Int: { kind: "func", params: [], returns: [INT] },
|
|
986
|
+
Int63: { kind: "func", params: [], returns: [INT] },
|
|
987
|
+
Int63n: { kind: "func", params: [INT], returns: [INT] },
|
|
988
|
+
Int31: { kind: "func", params: [], returns: [INT] },
|
|
989
|
+
Int31n: { kind: "func", params: [INT], returns: [INT] },
|
|
990
|
+
Seed: { kind: "func", params: [INT], returns: [VOID] },
|
|
991
|
+
Shuffle: { kind: "func", params: [INT, ANY], returns: [VOID] },
|
|
992
|
+
Perm: {
|
|
993
|
+
kind: "func",
|
|
994
|
+
params: [INT],
|
|
995
|
+
returns: [{ kind: "slice", elem: INT }],
|
|
996
|
+
},
|
|
997
|
+
},
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
// unicode/utf8 package — import "unicode/utf8" → local name "utf8"
|
|
1001
|
+
globals.define("utf8", {
|
|
1002
|
+
kind: "namespace",
|
|
1003
|
+
name: "utf8",
|
|
1004
|
+
members: {
|
|
1005
|
+
RuneCountInString: { kind: "func", params: [STRING], returns: [INT] },
|
|
1006
|
+
RuneLen: { kind: "func", params: [INT], returns: [INT] },
|
|
1007
|
+
ValidString: { kind: "func", params: [STRING], returns: [BOOL] },
|
|
1008
|
+
ValidRune: { kind: "func", params: [INT], returns: [BOOL] },
|
|
1009
|
+
DecodeRuneInString: {
|
|
1010
|
+
kind: "func",
|
|
1011
|
+
params: [STRING],
|
|
1012
|
+
returns: [INT, INT],
|
|
1013
|
+
},
|
|
1014
|
+
DecodeLastRuneInString: {
|
|
1015
|
+
kind: "func",
|
|
1016
|
+
params: [STRING],
|
|
1017
|
+
returns: [INT, INT],
|
|
1018
|
+
},
|
|
1019
|
+
FullRuneInString: { kind: "func", params: [STRING], returns: [BOOL] },
|
|
1020
|
+
// Constants
|
|
1021
|
+
RuneError: INT,
|
|
1022
|
+
MaxRune: INT,
|
|
1023
|
+
UTFMax: INT,
|
|
1024
|
+
},
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
// path package — import "path" or "path/filepath" → local name "path"
|
|
1028
|
+
globals.define("path", {
|
|
1029
|
+
kind: "namespace",
|
|
1030
|
+
name: "path",
|
|
1031
|
+
members: {
|
|
1032
|
+
Base: { kind: "func", params: [STRING], returns: [STRING] },
|
|
1033
|
+
Dir: { kind: "func", params: [STRING], returns: [STRING] },
|
|
1034
|
+
Ext: { kind: "func", params: [STRING], returns: [STRING] },
|
|
1035
|
+
Join: {
|
|
1036
|
+
kind: "func",
|
|
1037
|
+
params: [STRING],
|
|
1038
|
+
returns: [STRING],
|
|
1039
|
+
variadic: true,
|
|
1040
|
+
},
|
|
1041
|
+
Clean: { kind: "func", params: [STRING], returns: [STRING] },
|
|
1042
|
+
IsAbs: { kind: "func", params: [STRING], returns: [BOOL] },
|
|
1043
|
+
Split: { kind: "func", params: [STRING], returns: [STRING, STRING] },
|
|
1044
|
+
Match: { kind: "func", params: [STRING, STRING], returns: [BOOL, ERROR] },
|
|
1045
|
+
},
|
|
1046
|
+
});
|
|
1047
|
+
|
|
745
1048
|
// Built-in functions
|
|
746
1049
|
globals.define("append", { kind: "builtin", name: "append" });
|
|
747
1050
|
globals.define("len", { kind: "builtin", name: "len" });
|
package/src/typechecker/types.js
CHANGED
|
@@ -15,19 +15,22 @@
|
|
|
15
15
|
export class TypeCheckError extends Error {
|
|
16
16
|
constructor(msg, node, filename, sourceCode) {
|
|
17
17
|
const lineNum = node?.line || node?._line;
|
|
18
|
+
const colNum = node?.col || node?._col;
|
|
18
19
|
const loc = filename
|
|
19
20
|
? lineNum
|
|
20
|
-
? ` in ${filename} at line ${lineNum}`
|
|
21
|
+
? ` in ${filename} at line ${lineNum}:${colNum ?? 1}`
|
|
21
22
|
: ` in ${filename}`
|
|
22
23
|
: lineNum
|
|
23
|
-
? ` at line ${lineNum}`
|
|
24
|
+
? ` at line ${lineNum}:${colNum ?? 1}`
|
|
24
25
|
: "";
|
|
25
26
|
let lineContext = "";
|
|
26
27
|
if (lineNum && sourceCode) {
|
|
27
28
|
const lines = sourceCode.split("\n");
|
|
28
29
|
const lineStr = lines[lineNum - 1];
|
|
29
30
|
if (lineStr !== undefined) {
|
|
30
|
-
|
|
31
|
+
const prefix = ` ${lineNum} | `;
|
|
32
|
+
const caretPad = " ".repeat((colNum ?? 1) - 1);
|
|
33
|
+
lineContext = `\n${prefix}${lineStr}\n${" ".repeat(prefix.length)}${caretPad}^`;
|
|
31
34
|
}
|
|
32
35
|
}
|
|
33
36
|
super(`Type error${loc}: ${msg}${lineContext}`);
|
|
@@ -93,14 +96,6 @@ export const BASIC_TYPES = {
|
|
|
93
96
|
|
|
94
97
|
export const COMPARABLE = { kind: "basic", name: "comparable" };
|
|
95
98
|
|
|
96
|
-
export function isTypeParam(t) {
|
|
97
|
-
return t?.kind === "typeParam";
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function isGeneric(t) {
|
|
101
|
-
return t?.kind === "generic";
|
|
102
|
-
}
|
|
103
|
-
|
|
104
99
|
function makeBasicPredicate(...names) {
|
|
105
100
|
const set = new Set(names);
|
|
106
101
|
const pred = (t) => {
|
|
@@ -137,13 +132,6 @@ export function isNil(t) {
|
|
|
137
132
|
export function isVoid(t) {
|
|
138
133
|
return t?.kind === "basic" && t.name === "void";
|
|
139
134
|
}
|
|
140
|
-
export function isError(t) {
|
|
141
|
-
if (!t) return false;
|
|
142
|
-
if (t === ERROR) return true;
|
|
143
|
-
if (t.kind === "interface" && t.name === "error") return true;
|
|
144
|
-
if (t.kind === "named" && isError(t.underlying)) return true;
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
135
|
export function isPointer(t) {
|
|
148
136
|
if (!t) return false;
|
|
149
137
|
if (t.kind === "pointer") return true;
|