gofront 0.0.1 → 0.0.3
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 +357 -33
- package/package.json +7 -3
- package/src/codegen/expressions.js +804 -0
- package/src/codegen/source-map.js +45 -0
- package/src/codegen/statements.js +406 -0
- package/src/codegen.js +50 -851
- package/src/compiler.js +44 -23
- package/src/dts-parser.js +0 -4
- package/src/index.js +11 -20
- package/src/lexer.js +170 -12
- package/src/parser/expressions.js +389 -0
- package/src/parser/statements.js +355 -0
- package/src/parser/types.js +140 -0
- package/src/parser.js +57 -745
- package/src/resolver.js +1 -1
- package/src/typechecker/expressions.js +359 -0
- package/src/typechecker/statements.js +250 -0
- package/src/typechecker/types.js +226 -0
- package/src/typechecker.js +390 -647
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// GoFront type system — shared type constants, predicates, and utilities
|
|
2
|
+
// used across the type-checker sub-modules.
|
|
3
|
+
//
|
|
4
|
+
// Type representation:
|
|
5
|
+
// { kind: 'basic', name: 'int'|'float64'|'string'|'bool'|'any'|'void' }
|
|
6
|
+
// { kind: 'slice', elem: Type }
|
|
7
|
+
// { kind: 'array', size: n, elem: Type }
|
|
8
|
+
// { kind: 'map', key: Type, value: Type }
|
|
9
|
+
// { kind: 'struct', name: string, fields: Map<string, Type>, methods: Map<string, FuncType> }
|
|
10
|
+
// { kind: 'interface', name: string, methods: Map<string, FuncType> }
|
|
11
|
+
// { kind: 'func', params: Type[], returns: Type[] }
|
|
12
|
+
// { kind: 'tuple', types: Type[] } ← multiple return values
|
|
13
|
+
// { kind: 'named', name: string, underlying: Type }
|
|
14
|
+
|
|
15
|
+
export class TypeCheckError extends Error {
|
|
16
|
+
constructor(msg, node, filename, sourceCode) {
|
|
17
|
+
const lineNum = node?.line || node?._line;
|
|
18
|
+
const loc = filename
|
|
19
|
+
? lineNum
|
|
20
|
+
? ` in ${filename} at line ${lineNum}`
|
|
21
|
+
: ` in ${filename}`
|
|
22
|
+
: lineNum
|
|
23
|
+
? ` at line ${lineNum}`
|
|
24
|
+
: "";
|
|
25
|
+
let lineContext = "";
|
|
26
|
+
if (lineNum && sourceCode) {
|
|
27
|
+
const lines = sourceCode.split("\n");
|
|
28
|
+
const lineStr = lines[lineNum - 1];
|
|
29
|
+
if (lineStr !== undefined) {
|
|
30
|
+
lineContext = `\n ${lineNum} | ${lineStr}`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
super(`Type error${loc}: ${msg}${lineContext}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ── Static operator sets (module-level for reuse) ────────────
|
|
38
|
+
export const CMP_OPS = new Set(["==", "!=", "<", ">", "<=", ">="]);
|
|
39
|
+
export const LOG_OPS = new Set(["&&", "||"]);
|
|
40
|
+
|
|
41
|
+
// ── Built-in types ───────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
export const INT = { kind: "basic", name: "int" };
|
|
44
|
+
export const FLOAT64 = { kind: "basic", name: "float64" };
|
|
45
|
+
export const STRING = { kind: "basic", name: "string" };
|
|
46
|
+
export const BOOL = { kind: "basic", name: "bool" };
|
|
47
|
+
export const ANY = { kind: "basic", name: "any" };
|
|
48
|
+
export const VOID = { kind: "basic", name: "void" };
|
|
49
|
+
export const NIL = { kind: "basic", name: "nil" };
|
|
50
|
+
export const ERROR = { kind: "basic", name: "error" };
|
|
51
|
+
|
|
52
|
+
// ── Untyped constant types (Go spec §Constants) ─────────────
|
|
53
|
+
// Untyped constants coerce to any compatible typed context.
|
|
54
|
+
export const UNTYPED_INT = { kind: "untyped", base: "int" };
|
|
55
|
+
export const UNTYPED_FLOAT = { kind: "untyped", base: "float64" };
|
|
56
|
+
export const UNTYPED_STRING = { kind: "untyped", base: "string" };
|
|
57
|
+
export const UNTYPED_BOOL = { kind: "untyped", base: "bool" };
|
|
58
|
+
|
|
59
|
+
export const BASIC_TYPES = {
|
|
60
|
+
int: INT,
|
|
61
|
+
float64: FLOAT64,
|
|
62
|
+
string: STRING,
|
|
63
|
+
bool: BOOL,
|
|
64
|
+
any: ANY,
|
|
65
|
+
byte: INT,
|
|
66
|
+
rune: INT,
|
|
67
|
+
error: ERROR,
|
|
68
|
+
// Sized integer / float aliases — all map to int or float64 at runtime
|
|
69
|
+
uint: INT,
|
|
70
|
+
int8: INT,
|
|
71
|
+
int16: INT,
|
|
72
|
+
int32: INT,
|
|
73
|
+
int64: INT,
|
|
74
|
+
uint8: INT,
|
|
75
|
+
uint16: INT,
|
|
76
|
+
uint32: INT,
|
|
77
|
+
uint64: INT,
|
|
78
|
+
uintptr: INT,
|
|
79
|
+
float32: FLOAT64,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export function isNumeric(t) {
|
|
83
|
+
if (t?.kind === "untyped") return t.base === "int" || t.base === "float64";
|
|
84
|
+
return t?.kind === "basic" && (t.name === "int" || t.name === "float64");
|
|
85
|
+
}
|
|
86
|
+
export function isString(t) {
|
|
87
|
+
if (t?.kind === "untyped") return t.base === "string";
|
|
88
|
+
return t?.kind === "basic" && t.name === "string";
|
|
89
|
+
}
|
|
90
|
+
export function isBool(t) {
|
|
91
|
+
if (t?.kind === "untyped") return t.base === "bool";
|
|
92
|
+
return t?.kind === "basic" && t.name === "bool";
|
|
93
|
+
}
|
|
94
|
+
export function isAny(t) {
|
|
95
|
+
return t?.kind === "basic" && t.name === "any";
|
|
96
|
+
}
|
|
97
|
+
export function isNil(t) {
|
|
98
|
+
return t?.kind === "basic" && t.name === "nil";
|
|
99
|
+
}
|
|
100
|
+
export function isVoid(t) {
|
|
101
|
+
return t?.kind === "basic" && t.name === "void";
|
|
102
|
+
}
|
|
103
|
+
export function isError(t) {
|
|
104
|
+
return t?.kind === "basic" && t.name === "error";
|
|
105
|
+
}
|
|
106
|
+
export function isUntyped(t) {
|
|
107
|
+
return t?.kind === "untyped";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Materialize an untyped type to its default concrete type. */
|
|
111
|
+
export function defaultType(t) {
|
|
112
|
+
if (t?.kind !== "untyped") return t;
|
|
113
|
+
switch (t.base) {
|
|
114
|
+
case "int":
|
|
115
|
+
return INT;
|
|
116
|
+
case "float64":
|
|
117
|
+
return FLOAT64;
|
|
118
|
+
case "string":
|
|
119
|
+
return STRING;
|
|
120
|
+
case "bool":
|
|
121
|
+
return BOOL;
|
|
122
|
+
default:
|
|
123
|
+
return ANY;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Convert a concrete basic type to its untyped equivalent (for const inference). */
|
|
128
|
+
export function toUntyped(t) {
|
|
129
|
+
if (t?.kind === "untyped") return t;
|
|
130
|
+
if (t?.kind === "basic") {
|
|
131
|
+
switch (t.name) {
|
|
132
|
+
case "int":
|
|
133
|
+
return UNTYPED_INT;
|
|
134
|
+
case "float64":
|
|
135
|
+
return UNTYPED_FLOAT;
|
|
136
|
+
case "string":
|
|
137
|
+
return UNTYPED_STRING;
|
|
138
|
+
case "bool":
|
|
139
|
+
return UNTYPED_BOOL;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return t;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function typeStr(t) {
|
|
146
|
+
if (!t) return "void";
|
|
147
|
+
if (t.kind === "basic" && t.alias) return t.alias;
|
|
148
|
+
switch (t.kind) {
|
|
149
|
+
case "basic":
|
|
150
|
+
return t.name;
|
|
151
|
+
case "untyped":
|
|
152
|
+
return `untyped ${t.base}`;
|
|
153
|
+
case "slice":
|
|
154
|
+
return `[]${typeStr(t.elem)}`;
|
|
155
|
+
case "array":
|
|
156
|
+
return `[${t.size}]${typeStr(t.elem)}`;
|
|
157
|
+
case "map":
|
|
158
|
+
return `map[${typeStr(t.key)}]${typeStr(t.value)}`;
|
|
159
|
+
case "struct":
|
|
160
|
+
return t.name || "struct{...}";
|
|
161
|
+
case "interface":
|
|
162
|
+
return t.name || "interface{...}";
|
|
163
|
+
case "namespace":
|
|
164
|
+
return t.name || "namespace{...}";
|
|
165
|
+
case "func":
|
|
166
|
+
return `func(${t.params.map(typeStr).join(", ")}) ${typeStr(t.returns[0] ?? VOID)}`;
|
|
167
|
+
case "tuple":
|
|
168
|
+
return `(${t.types.map(typeStr).join(", ")})`;
|
|
169
|
+
case "named":
|
|
170
|
+
return t.name;
|
|
171
|
+
case "pointer":
|
|
172
|
+
return `*${typeStr(t.base)}`;
|
|
173
|
+
default:
|
|
174
|
+
return "?";
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ── Scope / environment ──────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
export class Scope {
|
|
181
|
+
constructor(parent = null) {
|
|
182
|
+
this.parent = parent;
|
|
183
|
+
this.symbols = new Map();
|
|
184
|
+
this._consts = new Set(); // names declared as const in this scope
|
|
185
|
+
this._locals = new Set(); // names declared as local variables (var / :=)
|
|
186
|
+
this._used = new Set(); // names referenced in this scope
|
|
187
|
+
}
|
|
188
|
+
define(name, type) {
|
|
189
|
+
this.symbols.set(name, type);
|
|
190
|
+
}
|
|
191
|
+
defineLocal(name, type) {
|
|
192
|
+
this.symbols.set(name, type);
|
|
193
|
+
if (name !== "_") this._locals.add(name);
|
|
194
|
+
}
|
|
195
|
+
defineConst(name, type) {
|
|
196
|
+
this.symbols.set(name, type);
|
|
197
|
+
this._consts.add(name);
|
|
198
|
+
}
|
|
199
|
+
isConst(name) {
|
|
200
|
+
if (this._consts.has(name)) return true;
|
|
201
|
+
// Don't walk parent — shadowing a const with a var in a child scope is valid
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
lookup(name) {
|
|
205
|
+
if (this.symbols.has(name)) {
|
|
206
|
+
this._used.add(name);
|
|
207
|
+
return this.symbols.get(name);
|
|
208
|
+
}
|
|
209
|
+
if (this.parent) return this.parent.lookup(name);
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
// Lookup which scope owns the name (to check const flag)
|
|
213
|
+
lookupScope(name) {
|
|
214
|
+
if (this.symbols.has(name)) return this;
|
|
215
|
+
if (this.parent) return this.parent.lookupScope(name);
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
// Returns local variable names that were never referenced
|
|
219
|
+
unusedLocals() {
|
|
220
|
+
const unused = [];
|
|
221
|
+
for (const name of this._locals) {
|
|
222
|
+
if (!this._used.has(name)) unused.push(name);
|
|
223
|
+
}
|
|
224
|
+
return unused;
|
|
225
|
+
}
|
|
226
|
+
}
|