lt-script 1.0.1 → 1.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 +256 -15
- package/dist/cli/ltc.js +44 -34
- package/dist/cli/utils.d.ts +40 -0
- package/dist/cli/utils.js +40 -0
- package/dist/compiler/codegen/LuaEmitter.js +19 -3
- package/dist/compiler/lexer/Lexer.js +7 -2
- package/dist/compiler/lexer/Token.d.ts +2 -1
- package/dist/compiler/lexer/Token.js +6 -2
- package/dist/compiler/parser/AST.d.ts +12 -0
- package/dist/compiler/parser/AST.js +2 -0
- package/dist/compiler/parser/Parser.d.ts +4 -0
- package/dist/compiler/parser/Parser.js +141 -26
- package/dist/compiler/semantics/SemanticAnalyzer.d.ts +23 -0
- package/dist/compiler/semantics/SemanticAnalyzer.js +411 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +9 -1
- package/package.json +12 -3
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import * as AST from '../parser/AST.js';
|
|
2
|
+
class Scope {
|
|
3
|
+
symbols = new Map();
|
|
4
|
+
parent;
|
|
5
|
+
constructor(parent) {
|
|
6
|
+
this.parent = parent;
|
|
7
|
+
}
|
|
8
|
+
define(name, kind, type, line) {
|
|
9
|
+
// In strict mode we might check for redeclaration here
|
|
10
|
+
this.symbols.set(name, { name, kind, type, definedAtLine: line });
|
|
11
|
+
}
|
|
12
|
+
lookup(name) {
|
|
13
|
+
let current = this;
|
|
14
|
+
while (current) {
|
|
15
|
+
if (current.symbols.has(name)) {
|
|
16
|
+
return current.symbols.get(name);
|
|
17
|
+
}
|
|
18
|
+
current = current.parent;
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export class SemanticAnalyzer {
|
|
24
|
+
globalScope = new Scope();
|
|
25
|
+
currentScope = this.globalScope;
|
|
26
|
+
typeRegistry = new Map();
|
|
27
|
+
memberTypes = new Map(); // Tracks Table.Field -> type mapping
|
|
28
|
+
analyze(program) {
|
|
29
|
+
// Reset scope for fresh analysis (though typically instance is fresh)
|
|
30
|
+
this.currentScope = this.globalScope;
|
|
31
|
+
// Visit all statements
|
|
32
|
+
for (const stmt of program.body) {
|
|
33
|
+
this.visitStatement(stmt);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
enterScope() {
|
|
37
|
+
this.currentScope = new Scope(this.currentScope);
|
|
38
|
+
}
|
|
39
|
+
exitScope() {
|
|
40
|
+
if (this.currentScope.parent) {
|
|
41
|
+
this.currentScope = this.currentScope.parent;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
visitStatement(stmt) {
|
|
45
|
+
switch (stmt.kind) {
|
|
46
|
+
case AST.NodeType.VariableDecl:
|
|
47
|
+
this.visitVariableDecl(stmt);
|
|
48
|
+
break;
|
|
49
|
+
case AST.NodeType.TypeDecl:
|
|
50
|
+
this.visitTypeDecl(stmt);
|
|
51
|
+
break;
|
|
52
|
+
case AST.NodeType.AssignmentStmt:
|
|
53
|
+
this.visitAssignmentStmt(stmt);
|
|
54
|
+
break;
|
|
55
|
+
case AST.NodeType.CompoundAssignment:
|
|
56
|
+
this.visitCompoundAssignment(stmt);
|
|
57
|
+
break;
|
|
58
|
+
case AST.NodeType.FunctionDecl:
|
|
59
|
+
this.visitFunctionDecl(stmt);
|
|
60
|
+
break;
|
|
61
|
+
case AST.NodeType.Block:
|
|
62
|
+
this.visitBlock(stmt);
|
|
63
|
+
break;
|
|
64
|
+
case AST.NodeType.IfStmt:
|
|
65
|
+
{
|
|
66
|
+
const s = stmt;
|
|
67
|
+
this.visitExpression(s.condition);
|
|
68
|
+
this.visitBlock(s.thenBody);
|
|
69
|
+
s.elseIfClauses?.forEach(c => {
|
|
70
|
+
this.visitExpression(c.condition);
|
|
71
|
+
this.visitBlock(c.body);
|
|
72
|
+
});
|
|
73
|
+
if (s.elseBody)
|
|
74
|
+
this.visitBlock(s.elseBody);
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
case AST.NodeType.WhileStmt:
|
|
78
|
+
{
|
|
79
|
+
const s = stmt;
|
|
80
|
+
this.visitExpression(s.condition);
|
|
81
|
+
this.visitBlock(s.body);
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
case AST.NodeType.ForStmt:
|
|
85
|
+
{
|
|
86
|
+
const s = stmt;
|
|
87
|
+
this.enterScope(); // For loop creates scope for iterators
|
|
88
|
+
s.iterators.forEach(i => this.currentScope.define(i.name, 'let', 'any', s.line || 0)); // Iterators can be anything in generic for
|
|
89
|
+
this.visitExpression(s.iterable);
|
|
90
|
+
// We can treat the body as part of this scope or nested.
|
|
91
|
+
// AST.Block usually suggests its own scope, but let's reuse this one or just visit statements.
|
|
92
|
+
// Since AST.Block logic below creates a NEW scope, we have: Scope(Iterators) -> Scope(Body). This is correct.
|
|
93
|
+
this.visitBlock(s.body);
|
|
94
|
+
this.exitScope();
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
case AST.NodeType.RangeForStmt:
|
|
98
|
+
{
|
|
99
|
+
const s = stmt;
|
|
100
|
+
this.enterScope();
|
|
101
|
+
this.currentScope.define(s.counter.name, 'let', 'number', s.line || 0); // Range for is always number
|
|
102
|
+
this.visitExpression(s.start);
|
|
103
|
+
this.visitExpression(s.end);
|
|
104
|
+
if (s.step)
|
|
105
|
+
this.visitExpression(s.step);
|
|
106
|
+
this.visitBlock(s.body);
|
|
107
|
+
this.exitScope();
|
|
108
|
+
}
|
|
109
|
+
break;
|
|
110
|
+
// ... Handle other control flows that have blocks ...
|
|
111
|
+
case AST.NodeType.CommandStmt:
|
|
112
|
+
{
|
|
113
|
+
const s = stmt;
|
|
114
|
+
this.enterScope();
|
|
115
|
+
s.params.forEach(p => this.currentScope.define(p.name.name, 'param', 'string', s.line || 0)); // Command params are strings (usually)
|
|
116
|
+
this.visitBlock(s.body);
|
|
117
|
+
this.exitScope();
|
|
118
|
+
}
|
|
119
|
+
break;
|
|
120
|
+
// Basic fallback recursive visiting
|
|
121
|
+
default:
|
|
122
|
+
// Identify other nodes that contain blocks/expressions to visit
|
|
123
|
+
this.visitChildren(stmt);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
visitChildren(node) {
|
|
127
|
+
if (!node || typeof node !== 'object')
|
|
128
|
+
return;
|
|
129
|
+
for (const key in node) {
|
|
130
|
+
if (key === 'kind')
|
|
131
|
+
continue;
|
|
132
|
+
const val = node[key];
|
|
133
|
+
if (Array.isArray(val)) {
|
|
134
|
+
val.forEach(v => {
|
|
135
|
+
if (v && typeof v === 'object' && 'kind' in v) {
|
|
136
|
+
if (v.kind.endsWith('Stmt') || v.kind === 'VariableDecl' || v.kind === 'Block') {
|
|
137
|
+
this.visitStatement(v);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
this.visitExpression(v);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
else if (val && typeof val === 'object' && 'kind' in val) {
|
|
146
|
+
if (val.kind.endsWith('Stmt') || val.kind === 'VariableDecl' || val.kind === 'Block') {
|
|
147
|
+
this.visitStatement(val);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
this.visitExpression(val);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
visitVariableDecl(decl) {
|
|
156
|
+
// Visit values first
|
|
157
|
+
if (decl.values) {
|
|
158
|
+
decl.values.forEach(v => this.visitExpression(v));
|
|
159
|
+
}
|
|
160
|
+
const kind = decl.scope === 'const' ? 'const' : 'let';
|
|
161
|
+
decl.names.forEach((n, index) => {
|
|
162
|
+
let type = 'any';
|
|
163
|
+
let customTypeName;
|
|
164
|
+
// 1. Try to get type from annotation
|
|
165
|
+
if (decl.typeAnnotations && decl.typeAnnotations[index]) {
|
|
166
|
+
const annotation = decl.typeAnnotations[index];
|
|
167
|
+
customTypeName = this.typeRegistry.has(annotation) ? annotation : undefined;
|
|
168
|
+
type = this.stringToType(annotation);
|
|
169
|
+
}
|
|
170
|
+
// 2. Try to infer from value if available
|
|
171
|
+
if (decl.values && decl.values[index]) {
|
|
172
|
+
const value = decl.values[index];
|
|
173
|
+
const inferred = this.inferType(value);
|
|
174
|
+
// If we have a custom type annotation and the value is a table, validate it
|
|
175
|
+
if (customTypeName && value.kind === AST.NodeType.TableLiteral) {
|
|
176
|
+
this.validateTableAgainstType(value, customTypeName, decl.line || 0);
|
|
177
|
+
}
|
|
178
|
+
// If we have an annotation, check compatibility
|
|
179
|
+
if (type !== 'any' && inferred !== 'any' && type !== inferred) {
|
|
180
|
+
throw new Error(`Type mismatch at line ${decl.line}: Expected '${type}', but got '${inferred}'`);
|
|
181
|
+
}
|
|
182
|
+
// If no annotation, keep type as 'any' (default)
|
|
183
|
+
// if (type === 'any' && (!decl.typeAnnotations || !decl.typeAnnotations[index])) {
|
|
184
|
+
// type = inferred;
|
|
185
|
+
// }
|
|
186
|
+
}
|
|
187
|
+
if (n.kind === AST.NodeType.Identifier) {
|
|
188
|
+
this.currentScope.define(n.name, kind, type, decl.line || 0);
|
|
189
|
+
}
|
|
190
|
+
else if (n.kind === AST.NodeType.ObjectDestructure) {
|
|
191
|
+
n.properties.forEach(p => {
|
|
192
|
+
this.currentScope.define(p.name, kind, 'any', decl.line || 0);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
else if (n.kind === AST.NodeType.ArrayDestructure) {
|
|
196
|
+
n.elements.forEach(el => {
|
|
197
|
+
this.currentScope.define(el.name, kind, 'any', decl.line || 0);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
visitAssignmentStmt(stmt) {
|
|
203
|
+
// Check RHS
|
|
204
|
+
stmt.values.forEach(v => this.visitExpression(v));
|
|
205
|
+
// Check LHS
|
|
206
|
+
stmt.targets.forEach((t, index) => {
|
|
207
|
+
// If it's a simple identifier, check const and TYPE
|
|
208
|
+
if (t.kind === AST.NodeType.Identifier) {
|
|
209
|
+
const name = t.name;
|
|
210
|
+
const sym = this.currentScope.lookup(name);
|
|
211
|
+
if (sym) {
|
|
212
|
+
if (sym.kind === 'const') {
|
|
213
|
+
throw new Error(`Assignment to constant variable '${name}' at line ${stmt.line}`);
|
|
214
|
+
}
|
|
215
|
+
// Type Checking
|
|
216
|
+
if (stmt.values[index]) {
|
|
217
|
+
const valueType = this.inferType(stmt.values[index]);
|
|
218
|
+
if (sym.type !== 'any' && valueType !== 'any' && sym.type !== valueType) {
|
|
219
|
+
throw new Error(`Type mismatch at line ${stmt.line}: Variable '${name}' is type '${sym.type}', but assigned '${valueType}'`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Handle member expression type checking (Config.Field: type = value)
|
|
225
|
+
if (t.kind === AST.NodeType.MemberExpr) {
|
|
226
|
+
const memberKey = this.getMemberExprKey(t);
|
|
227
|
+
// If this assignment has a type annotation, register it
|
|
228
|
+
if (stmt.typeAnnotation && index === 0) {
|
|
229
|
+
const type = this.stringToType(stmt.typeAnnotation);
|
|
230
|
+
this.memberTypes.set(memberKey, type);
|
|
231
|
+
// Also check that the value matches the declared type
|
|
232
|
+
if (stmt.values[index]) {
|
|
233
|
+
const valueType = this.inferType(stmt.values[index]);
|
|
234
|
+
if (type !== 'any' && valueType !== 'any' && type !== valueType) {
|
|
235
|
+
throw new Error(`Type mismatch at line ${stmt.line}: '${memberKey}' is declared as '${stmt.typeAnnotation}', but assigned '${valueType}'`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
// Check if this member has a registered type
|
|
241
|
+
const registeredType = this.memberTypes.get(memberKey);
|
|
242
|
+
if (registeredType && stmt.values[index]) {
|
|
243
|
+
const valueType = this.inferType(stmt.values[index]);
|
|
244
|
+
if (registeredType !== 'any' && valueType !== 'any' && registeredType !== valueType) {
|
|
245
|
+
throw new Error(`Type mismatch at line ${stmt.line}: '${memberKey}' is type '${registeredType}', but assigned '${valueType}'`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
this.visitExpression(t);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
// Helper to get a string key for a member expression (e.g., "Config.Locale")
|
|
254
|
+
getMemberExprKey(expr) {
|
|
255
|
+
let parts = [];
|
|
256
|
+
let current = expr;
|
|
257
|
+
while (current.kind === AST.NodeType.MemberExpr) {
|
|
258
|
+
const memberExpr = current;
|
|
259
|
+
if (memberExpr.property.kind === AST.NodeType.Identifier) {
|
|
260
|
+
parts.unshift(memberExpr.property.name);
|
|
261
|
+
}
|
|
262
|
+
current = memberExpr.object;
|
|
263
|
+
}
|
|
264
|
+
if (current.kind === AST.NodeType.Identifier) {
|
|
265
|
+
parts.unshift(current.name);
|
|
266
|
+
}
|
|
267
|
+
return parts.join('.');
|
|
268
|
+
}
|
|
269
|
+
visitCompoundAssignment(stmt) {
|
|
270
|
+
this.visitExpression(stmt.value);
|
|
271
|
+
if (stmt.target.kind === AST.NodeType.Identifier) {
|
|
272
|
+
const name = stmt.target.name;
|
|
273
|
+
const sym = this.currentScope.lookup(name);
|
|
274
|
+
if (sym && sym.kind === 'const') {
|
|
275
|
+
throw new Error(`Assignment to constant variable '${name}' at line ${stmt.line}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
this.visitExpression(stmt.target);
|
|
279
|
+
}
|
|
280
|
+
visitFunctionDecl(decl) {
|
|
281
|
+
// Name is defined in CURRENT scope
|
|
282
|
+
if (decl.name && decl.name.kind === AST.NodeType.Identifier) {
|
|
283
|
+
this.currentScope.define(decl.name.name, 'const', 'function', decl.line || 0);
|
|
284
|
+
}
|
|
285
|
+
this.enterScope();
|
|
286
|
+
decl.params.forEach(p => {
|
|
287
|
+
// Parse param type annotation
|
|
288
|
+
let type = 'any';
|
|
289
|
+
if (p.typeAnnotation) {
|
|
290
|
+
type = this.stringToType(p.typeAnnotation);
|
|
291
|
+
}
|
|
292
|
+
this.currentScope.define(p.name.name, 'param', type, decl.line || 0);
|
|
293
|
+
if (p.defaultValue) {
|
|
294
|
+
const defType = this.inferType(p.defaultValue);
|
|
295
|
+
if (type !== 'any' && defType !== 'any' && type !== defType) {
|
|
296
|
+
throw new Error(`Type mismatch in parameter default value at line ${decl.line}`);
|
|
297
|
+
}
|
|
298
|
+
this.visitExpression(p.defaultValue);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
this.visitBlock(decl.body);
|
|
302
|
+
this.exitScope();
|
|
303
|
+
}
|
|
304
|
+
visitBlock(block) {
|
|
305
|
+
this.enterScope();
|
|
306
|
+
block.statements.forEach(s => this.visitStatement(s));
|
|
307
|
+
this.exitScope();
|
|
308
|
+
}
|
|
309
|
+
visitExpression(expr) {
|
|
310
|
+
// Mostly just traversing to find nested scopes in functions/arrows
|
|
311
|
+
if (!expr)
|
|
312
|
+
return;
|
|
313
|
+
if (expr.kind === AST.NodeType.ArrowFunc) {
|
|
314
|
+
const arrow = expr;
|
|
315
|
+
this.enterScope();
|
|
316
|
+
arrow.params.forEach(p => {
|
|
317
|
+
let type = 'any';
|
|
318
|
+
if (p.typeAnnotation) {
|
|
319
|
+
type = this.stringToType(p.typeAnnotation);
|
|
320
|
+
}
|
|
321
|
+
this.currentScope.define(p.name.name, 'param', type, expr.line || 0);
|
|
322
|
+
if (p.defaultValue)
|
|
323
|
+
this.visitExpression(p.defaultValue);
|
|
324
|
+
});
|
|
325
|
+
if ('kind' in arrow.body && arrow.body.kind === AST.NodeType.Block) {
|
|
326
|
+
this.visitBlock(arrow.body);
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
this.visitExpression(arrow.body);
|
|
330
|
+
}
|
|
331
|
+
this.exitScope();
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (expr.kind === AST.NodeType.FunctionDecl) {
|
|
335
|
+
this.visitFunctionDecl(expr);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
// Recurse
|
|
339
|
+
this.visitChildren(expr);
|
|
340
|
+
}
|
|
341
|
+
inferType(expr) {
|
|
342
|
+
if (!expr)
|
|
343
|
+
return 'any';
|
|
344
|
+
switch (expr.kind) {
|
|
345
|
+
case AST.NodeType.NumberLiteral: return 'number';
|
|
346
|
+
case AST.NodeType.StringLiteral: return 'string';
|
|
347
|
+
case AST.NodeType.InterpolatedString: return 'string';
|
|
348
|
+
case AST.NodeType.BooleanLiteral: return 'boolean';
|
|
349
|
+
case AST.NodeType.NilLiteral: return 'nil';
|
|
350
|
+
case AST.NodeType.VectorLiteral: return 'vector';
|
|
351
|
+
case AST.NodeType.TableLiteral: return 'table';
|
|
352
|
+
case AST.NodeType.FunctionDecl: return 'function';
|
|
353
|
+
case AST.NodeType.ArrowFunc: return 'function';
|
|
354
|
+
case AST.NodeType.Identifier:
|
|
355
|
+
const sym = this.currentScope.lookup(expr.name);
|
|
356
|
+
return sym ? sym.type : 'any';
|
|
357
|
+
// TODO: Improve binary expr inference (e.g. number + number = number)
|
|
358
|
+
case AST.NodeType.BinaryExpr: return 'any';
|
|
359
|
+
default:
|
|
360
|
+
return 'any';
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
stringToType(str) {
|
|
364
|
+
switch (str) {
|
|
365
|
+
case 'string': return 'string';
|
|
366
|
+
case 'number': return 'number';
|
|
367
|
+
case 'boolean': return 'boolean';
|
|
368
|
+
case 'vector': return 'vector';
|
|
369
|
+
case 'table': return 'table';
|
|
370
|
+
case 'function': return 'function';
|
|
371
|
+
case 'any': return 'any';
|
|
372
|
+
default:
|
|
373
|
+
// Check if it's a custom type
|
|
374
|
+
if (this.typeRegistry.has(str)) {
|
|
375
|
+
return 'table'; // Custom types are tables internally
|
|
376
|
+
}
|
|
377
|
+
return 'any';
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
visitTypeDecl(decl) {
|
|
381
|
+
const typeName = decl.name.name;
|
|
382
|
+
// Register the type
|
|
383
|
+
this.typeRegistry.set(typeName, decl.fields);
|
|
384
|
+
}
|
|
385
|
+
validateTableAgainstType(table, typeName, line) {
|
|
386
|
+
const fields = this.typeRegistry.get(typeName);
|
|
387
|
+
if (!fields)
|
|
388
|
+
return; // Unknown type, skip validation
|
|
389
|
+
const providedFields = new Map();
|
|
390
|
+
for (const field of table.fields) {
|
|
391
|
+
if (field.key && field.key.kind === AST.NodeType.Identifier) {
|
|
392
|
+
providedFields.set(field.key.name, field.value);
|
|
393
|
+
}
|
|
394
|
+
else if (field.key && field.key.kind === AST.NodeType.StringLiteral) {
|
|
395
|
+
providedFields.set(field.key.value, field.value);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
// Check each expected field
|
|
399
|
+
for (const expected of fields) {
|
|
400
|
+
const provided = providedFields.get(expected.name);
|
|
401
|
+
if (!provided) {
|
|
402
|
+
throw new Error(`Type '${typeName}' requires field '${expected.name}' at line ${line}`);
|
|
403
|
+
}
|
|
404
|
+
const providedType = this.inferType(provided);
|
|
405
|
+
const expectedType = this.stringToType(expected.type);
|
|
406
|
+
if (expectedType !== 'any' && providedType !== 'any' && expectedType !== providedType) {
|
|
407
|
+
throw new Error(`Type mismatch: field '${expected.name}' should be '${expected.type}', got '${providedType}' at line ${line}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Lexer } from './compiler/lexer/Lexer.js';
|
|
2
2
|
import { Parser } from './compiler/parser/Parser.js';
|
|
3
3
|
import { LuaEmitter } from './compiler/codegen/LuaEmitter.js';
|
|
4
|
+
import { SemanticAnalyzer } from './compiler/semantics/SemanticAnalyzer.js';
|
|
4
5
|
export declare function compile(source: string): string;
|
|
5
|
-
export { Lexer, Parser, LuaEmitter };
|
|
6
|
+
export { Lexer, Parser, LuaEmitter, SemanticAnalyzer };
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import { Lexer } from './compiler/lexer/Lexer.js';
|
|
2
2
|
import { Parser } from './compiler/parser/Parser.js';
|
|
3
3
|
import { LuaEmitter } from './compiler/codegen/LuaEmitter.js';
|
|
4
|
+
import { SemanticAnalyzer } from './compiler/semantics/SemanticAnalyzer.js';
|
|
4
5
|
export function compile(source) {
|
|
5
6
|
const lexer = new Lexer(source);
|
|
6
7
|
const tokens = lexer.tokenize();
|
|
8
|
+
// DEBUG: Check first token to see if it has line info
|
|
9
|
+
// if (tokens.length > 0) {
|
|
10
|
+
// console.log('[DEBUG] First Token:', JSON.stringify(tokens[0]));
|
|
11
|
+
// console.log('[DEBUG] Token count:', tokens.length);
|
|
12
|
+
// }
|
|
7
13
|
const parser = new Parser(tokens);
|
|
8
14
|
const ast = parser.parse();
|
|
15
|
+
const analyzer = new SemanticAnalyzer();
|
|
16
|
+
analyzer.analyze(ast);
|
|
9
17
|
const emitter = new LuaEmitter();
|
|
10
18
|
return emitter.emit(ast);
|
|
11
19
|
}
|
|
12
|
-
export { Lexer, Parser, LuaEmitter };
|
|
20
|
+
export { Lexer, Parser, LuaEmitter, SemanticAnalyzer };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lt-script",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "LT Language Compiler for FiveM",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -18,16 +18,25 @@
|
|
|
18
18
|
"laot",
|
|
19
19
|
"lt"
|
|
20
20
|
],
|
|
21
|
-
"author": "
|
|
21
|
+
"author": "laot",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/laot7490/lt-script.git"
|
|
25
|
+
},
|
|
22
26
|
"license": "MIT",
|
|
23
27
|
"scripts": {
|
|
24
28
|
"build": "tsc",
|
|
25
29
|
"dev": "tsc --watch",
|
|
26
30
|
"test": "node dist/tests/run.js",
|
|
27
|
-
"
|
|
31
|
+
"build:lt": "npm run build",
|
|
32
|
+
"build:vscode": "cd vscode-extension && npx vsce package",
|
|
33
|
+
"watch:playground": "tsx src/cli/ltc.ts watch playground",
|
|
34
|
+
"watch:testing": "tsx src/cli/ltc.ts watch .testing",
|
|
35
|
+
"github:push": "node tools/push.js"
|
|
28
36
|
},
|
|
29
37
|
"devDependencies": {
|
|
30
38
|
"@types/node": "^25.0.10",
|
|
39
|
+
"tsx": "^4.21.0",
|
|
31
40
|
"typescript": "^5.3.0"
|
|
32
41
|
}
|
|
33
42
|
}
|