xanascript 2.0.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 +132 -0
- package/VERSION +1 -0
- package/llms.txt +322 -0
- package/package.json +33 -0
- package/src/ast.js +65 -0
- package/src/bench.js +68 -0
- package/src/bytecode/compiler.js +189 -0
- package/src/bytecode/opcodes.js +36 -0
- package/src/bytecode/vm.js +216 -0
- package/src/cli.js +535 -0
- package/src/codegen.js +101 -0
- package/src/codegen_opt.js +352 -0
- package/src/codegen_wasm.js +306 -0
- package/src/docsgen.js +262 -0
- package/src/errors.js +162 -0
- package/src/interpreter.js +471 -0
- package/src/lexer.js +195 -0
- package/src/lsp.js +304 -0
- package/src/macros.js +132 -0
- package/src/optimizer.js +175 -0
- package/src/orm.js +120 -0
- package/src/parser.js +819 -0
- package/src/pkgmgr.js +273 -0
- package/src/runtime.js +171 -0
- package/src/sourcemap.js +120 -0
- package/src/testrunner.js +118 -0
- package/src/wasm_binary.js +573 -0
- package/std/math.xs +60 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import { criarRepositorio } from "./orm.js";
|
|
2
|
+
import { XSError, typeMismatch, undefinedVar, notAFunction } from "./errors.js";
|
|
3
|
+
|
|
4
|
+
export class ReturnSignal {
|
|
5
|
+
constructor(value) {
|
|
6
|
+
this.value = value;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class BreakSignal {}
|
|
11
|
+
export class ContinueSignal {}
|
|
12
|
+
|
|
13
|
+
export class AssertionError extends Error {
|
|
14
|
+
constructor(message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "AssertionError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let TABELAS = {};
|
|
21
|
+
|
|
22
|
+
export function setTabelas(t) {
|
|
23
|
+
TABELAS = t;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function interpret(node, env) {
|
|
27
|
+
switch (node.type) {
|
|
28
|
+
case "Program": {
|
|
29
|
+
let result;
|
|
30
|
+
for (const stmt of node.body) {
|
|
31
|
+
result = await interpret(stmt, env);
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
case "Block": {
|
|
36
|
+
let result;
|
|
37
|
+
for (const stmt of node.body) {
|
|
38
|
+
result = await interpret(stmt, env);
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
case "VarDecl": {
|
|
43
|
+
const val = await interpret(node.init, env);
|
|
44
|
+
env[node.id] = val;
|
|
45
|
+
return val;
|
|
46
|
+
}
|
|
47
|
+
case "Assign": {
|
|
48
|
+
const val = await interpret(node.right, env);
|
|
49
|
+
if (node.left.type === "Member") {
|
|
50
|
+
const obj = await interpret(node.left.obj, env);
|
|
51
|
+
obj[node.left.prop] = val;
|
|
52
|
+
} else if (node.left.type === "IndexExpr") {
|
|
53
|
+
const obj = await interpret(node.left.obj, env);
|
|
54
|
+
const idx = await interpret(node.left.index, env);
|
|
55
|
+
obj[idx] = val;
|
|
56
|
+
} else {
|
|
57
|
+
env[node.left.name] = val;
|
|
58
|
+
}
|
|
59
|
+
return val;
|
|
60
|
+
}
|
|
61
|
+
case "Num":
|
|
62
|
+
return node.value;
|
|
63
|
+
case "Str":
|
|
64
|
+
return node.value;
|
|
65
|
+
case "Bool":
|
|
66
|
+
return node.value;
|
|
67
|
+
case "Nil":
|
|
68
|
+
return null;
|
|
69
|
+
case "Ident": {
|
|
70
|
+
if (!(node.name in env)) {
|
|
71
|
+
const err = undefinedVar(node.name, node.loc);
|
|
72
|
+
throw err;
|
|
73
|
+
}
|
|
74
|
+
return env[node.name];
|
|
75
|
+
}
|
|
76
|
+
case "Unary": {
|
|
77
|
+
const v = await interpret(node.arg, env);
|
|
78
|
+
switch (node.op) {
|
|
79
|
+
case "-": return -v;
|
|
80
|
+
case "!": return !v;
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case "Binary": {
|
|
85
|
+
if (node.op === "&&") {
|
|
86
|
+
const l = await interpret(node.left, env);
|
|
87
|
+
if (!l) return l;
|
|
88
|
+
return interpret(node.right, env);
|
|
89
|
+
}
|
|
90
|
+
if (node.op === "||") {
|
|
91
|
+
const l = await interpret(node.left, env);
|
|
92
|
+
if (l) return l;
|
|
93
|
+
return interpret(node.right, env);
|
|
94
|
+
}
|
|
95
|
+
if (node.op === "~=") {
|
|
96
|
+
const l = String(await interpret(node.left, env));
|
|
97
|
+
const r = String(await interpret(node.right, env));
|
|
98
|
+
return new RegExp(r).test(l);
|
|
99
|
+
}
|
|
100
|
+
const l = await interpret(node.left, env);
|
|
101
|
+
const r = await interpret(node.right, env);
|
|
102
|
+
switch (node.op) {
|
|
103
|
+
case "+": return l + r;
|
|
104
|
+
case "-": return l - r;
|
|
105
|
+
case "*": return l * r;
|
|
106
|
+
case "/": return l / r;
|
|
107
|
+
case "%": return l % r;
|
|
108
|
+
case "==": return l == r;
|
|
109
|
+
case "!=": return l != r;
|
|
110
|
+
case ">": return l > r;
|
|
111
|
+
case "<": return l < r;
|
|
112
|
+
case ">=": return l >= r;
|
|
113
|
+
case "<=": return l <= r;
|
|
114
|
+
}
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
case "IfStmt": {
|
|
118
|
+
const test = await interpret(node.test, env);
|
|
119
|
+
if (test) {
|
|
120
|
+
return interpret(node.cons, env);
|
|
121
|
+
}
|
|
122
|
+
if (node.alt) {
|
|
123
|
+
return interpret(node.alt, env);
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
case "ForStmt": {
|
|
128
|
+
if (node.init) {
|
|
129
|
+
await interpret(node.init, env);
|
|
130
|
+
}
|
|
131
|
+
while (await interpret(node.test, env)) {
|
|
132
|
+
let continued = false;
|
|
133
|
+
try {
|
|
134
|
+
await interpret(node.body, env);
|
|
135
|
+
} catch (e) {
|
|
136
|
+
if (e instanceof BreakSignal) break;
|
|
137
|
+
if (e instanceof ContinueSignal) { continued = true; }
|
|
138
|
+
else throw e;
|
|
139
|
+
}
|
|
140
|
+
if (node.update) {
|
|
141
|
+
await interpret(node.update, env);
|
|
142
|
+
}
|
|
143
|
+
if (continued) continue;
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
case "WhileStmt": {
|
|
148
|
+
while (await interpret(node.test, env)) {
|
|
149
|
+
try {
|
|
150
|
+
await interpret(node.body, env);
|
|
151
|
+
} catch (e) {
|
|
152
|
+
if (e instanceof BreakSignal) break;
|
|
153
|
+
if (e instanceof ContinueSignal) continue;
|
|
154
|
+
throw e;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
case "FunctionDecl": {
|
|
160
|
+
const fn = async (...args) => {
|
|
161
|
+
const scope = Object.create(env);
|
|
162
|
+
node.params.forEach((p, i) => { scope[p] = args[i]; });
|
|
163
|
+
try {
|
|
164
|
+
return await interpret(node.body, scope);
|
|
165
|
+
} catch (e) {
|
|
166
|
+
if (e instanceof ReturnSignal) {
|
|
167
|
+
return e.value;
|
|
168
|
+
}
|
|
169
|
+
throw e;
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
env[node.name] = fn;
|
|
173
|
+
return fn;
|
|
174
|
+
}
|
|
175
|
+
case "ReturnStmt": {
|
|
176
|
+
let val = null;
|
|
177
|
+
if (node.arg) {
|
|
178
|
+
val = await interpret(node.arg, env);
|
|
179
|
+
}
|
|
180
|
+
throw new ReturnSignal(val);
|
|
181
|
+
}
|
|
182
|
+
case "BreakStmt": {
|
|
183
|
+
throw new BreakSignal();
|
|
184
|
+
}
|
|
185
|
+
case "ContinueStmt": {
|
|
186
|
+
throw new ContinueSignal();
|
|
187
|
+
}
|
|
188
|
+
case "Call": {
|
|
189
|
+
const fn = await interpret(node.callee, env);
|
|
190
|
+
const args = [];
|
|
191
|
+
for (const a of node.args) {
|
|
192
|
+
args.push(await interpret(a, env));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (node.callee.type === "Ident") {
|
|
196
|
+
const name = node.callee.name;
|
|
197
|
+
if (name === "TAMANHO") return args[0]?.length;
|
|
198
|
+
if (name === "DIVIDE_TEXTO") return args[0]?.split(args[1]);
|
|
199
|
+
if (name === "ENCONTRA") return String(args[0])?.match(new RegExp(args[1]));
|
|
200
|
+
if (name === "DECODIFICA_URL") return decodeURIComponent(args[0]);
|
|
201
|
+
if (name === "JUNTAR") return args[0]?.join(args[1]);
|
|
202
|
+
if (name === "AGORA") return Date.now();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (typeof fn !== "function") {
|
|
206
|
+
const err = notAFunction(node.callee.name || "expressão", node.loc);
|
|
207
|
+
throw err;
|
|
208
|
+
}
|
|
209
|
+
return await fn(...args);
|
|
210
|
+
}
|
|
211
|
+
case "Member": {
|
|
212
|
+
const obj = await interpret(node.obj, env);
|
|
213
|
+
return obj[node.prop];
|
|
214
|
+
}
|
|
215
|
+
case "ImportExpr": {
|
|
216
|
+
return await env.__IMPORT__(node.path);
|
|
217
|
+
}
|
|
218
|
+
case "ImportStmt": {
|
|
219
|
+
const mod = await env.__IMPORT__(node.path);
|
|
220
|
+
if (node.alias) {
|
|
221
|
+
env[node.alias] = mod;
|
|
222
|
+
}
|
|
223
|
+
return mod;
|
|
224
|
+
}
|
|
225
|
+
case "ExportStmt":
|
|
226
|
+
return null;
|
|
227
|
+
case "ArrayExpr": {
|
|
228
|
+
const arr = [];
|
|
229
|
+
for (const item of node.items) {
|
|
230
|
+
arr.push(await interpret(item, env));
|
|
231
|
+
}
|
|
232
|
+
return arr;
|
|
233
|
+
}
|
|
234
|
+
case "ObjectExpr": {
|
|
235
|
+
const obj = {};
|
|
236
|
+
for (const p of node.props) {
|
|
237
|
+
obj[p.key] = await interpret(p.value, env);
|
|
238
|
+
}
|
|
239
|
+
return obj;
|
|
240
|
+
}
|
|
241
|
+
case "ArrowFunction": {
|
|
242
|
+
const fn = async (...args) => {
|
|
243
|
+
const scope = Object.create(env);
|
|
244
|
+
node.params.forEach((p, i) => { scope[p] = args[i]; });
|
|
245
|
+
return interpret(node.body, scope);
|
|
246
|
+
};
|
|
247
|
+
return fn;
|
|
248
|
+
}
|
|
249
|
+
case "TryCatchStmt": {
|
|
250
|
+
try {
|
|
251
|
+
return await interpret(node.tryBlock, env);
|
|
252
|
+
} catch (e) {
|
|
253
|
+
if (e instanceof ReturnSignal || e instanceof BreakSignal || e instanceof ContinueSignal) throw e;
|
|
254
|
+
const scope = Object.create(env);
|
|
255
|
+
scope[node.catchParam] = e;
|
|
256
|
+
return interpret(node.catchBlock, scope);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
case "IndexExpr": {
|
|
260
|
+
const obj = await interpret(node.obj, env);
|
|
261
|
+
const index = await interpret(node.index, env);
|
|
262
|
+
return obj[index];
|
|
263
|
+
}
|
|
264
|
+
case "Ternary": {
|
|
265
|
+
const test = await interpret(node.test, env);
|
|
266
|
+
if (test) {
|
|
267
|
+
return interpret(node.cons, env);
|
|
268
|
+
}
|
|
269
|
+
return interpret(node.alt, env);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
case "ClassDecl": {
|
|
273
|
+
const cls = function(...args) {
|
|
274
|
+
const instance = {};
|
|
275
|
+
instance.__proto__ = cls.prototype;
|
|
276
|
+
if (cls.prototype.__constructor) {
|
|
277
|
+
const scope = Object.create(env);
|
|
278
|
+
scope["ISTO"] = instance;
|
|
279
|
+
cls.prototype.params.forEach((p, i) => { scope[p] = args[i]; });
|
|
280
|
+
interpret(cls.prototype.__constructor, scope);
|
|
281
|
+
}
|
|
282
|
+
return instance;
|
|
283
|
+
};
|
|
284
|
+
cls.prototype = {};
|
|
285
|
+
|
|
286
|
+
if (node.superClass) {
|
|
287
|
+
const parent = env[node.superClass];
|
|
288
|
+
if (parent) {
|
|
289
|
+
cls.prototype.__proto__ = parent.prototype;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
for (const method of node.methods) {
|
|
294
|
+
if (method.isConstructor) {
|
|
295
|
+
cls.prototype.__constructor = method.body;
|
|
296
|
+
cls.prototype.params = method.params;
|
|
297
|
+
} else {
|
|
298
|
+
cls.prototype[method.name] = async function(...args) {
|
|
299
|
+
const scope = Object.create(env);
|
|
300
|
+
scope["ISTO"] = this;
|
|
301
|
+
method.params.forEach((p, i) => { scope[p] = args[i]; });
|
|
302
|
+
try {
|
|
303
|
+
return await interpret(method.body, scope);
|
|
304
|
+
} catch (e) {
|
|
305
|
+
if (e instanceof ReturnSignal) return e.value;
|
|
306
|
+
throw e;
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
env[node.name] = cls;
|
|
313
|
+
return cls;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
case "ThisExpr": {
|
|
317
|
+
if (!("ISTO" in env)) {
|
|
318
|
+
throw new XSError("`ISTO` usado fora de um método", {
|
|
319
|
+
loc: node.loc,
|
|
320
|
+
hint: "ISTO só funciona dentro de METODO ou CONSTRUTOR",
|
|
321
|
+
help: "Use ISTO apenas dentro de métodos de classe",
|
|
322
|
+
code: "E006",
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
return env["ISTO"];
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
case "NewExpr": {
|
|
329
|
+
const cls = await interpret(node.callee, env);
|
|
330
|
+
if (typeof cls !== "function") {
|
|
331
|
+
throw new XSError("NOVA só funciona com classes", {
|
|
332
|
+
loc: node.loc,
|
|
333
|
+
hint: "O identificador após NOVA deve ser uma CLASSE",
|
|
334
|
+
help: "Defina uma classe com CLASSE antes de usar NOVA",
|
|
335
|
+
code: "E007",
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
const args = [];
|
|
339
|
+
for (const a of node.args) {
|
|
340
|
+
args.push(await interpret(a, env));
|
|
341
|
+
}
|
|
342
|
+
return new cls(...args);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
case "SwitchStmt": {
|
|
346
|
+
const test = await interpret(node.test, env);
|
|
347
|
+
for (const c of node.cases) {
|
|
348
|
+
if (c.test === null) {
|
|
349
|
+
return interpret(c.body, env);
|
|
350
|
+
}
|
|
351
|
+
const val = await interpret(c.test, env);
|
|
352
|
+
if (test == val) {
|
|
353
|
+
return interpret(c.body, env);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
case "MatchExpr": {
|
|
360
|
+
const test = await interpret(node.test, env);
|
|
361
|
+
for (const c of node.cases) {
|
|
362
|
+
if (c.pattern === null) {
|
|
363
|
+
return interpret(c.body, env);
|
|
364
|
+
}
|
|
365
|
+
const bindings = {};
|
|
366
|
+
if (matchPattern(test, c.pattern, bindings)) {
|
|
367
|
+
const scope = Object.create(env);
|
|
368
|
+
Object.assign(scope, bindings);
|
|
369
|
+
return interpret(c.body, scope);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
case "TestStmt": {
|
|
376
|
+
try {
|
|
377
|
+
await interpret(node.body, env);
|
|
378
|
+
env.__testResults.push({ name: node.name, passed: true, error: null });
|
|
379
|
+
} catch (e) {
|
|
380
|
+
if (e instanceof AssertionError) {
|
|
381
|
+
env.__testResults.push({ name: node.name, passed: false, error: e.message });
|
|
382
|
+
} else if (e instanceof ReturnSignal || e instanceof BreakSignal || e instanceof ContinueSignal) {
|
|
383
|
+
throw e;
|
|
384
|
+
} else {
|
|
385
|
+
env.__testResults.push({ name: node.name, passed: false, error: e.message });
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
case "AssertStmt": {
|
|
392
|
+
const val = await interpret(node.test, env);
|
|
393
|
+
if (!val) {
|
|
394
|
+
throw new AssertionError(`AFIRMA falhou em ${node.loc?.line || "?"}:${node.loc?.column || "?"}`);
|
|
395
|
+
}
|
|
396
|
+
return val;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
case "TaskDecl": {
|
|
400
|
+
env.__tasks = env.__tasks || {};
|
|
401
|
+
env.__tasks[node.name] = async () => {
|
|
402
|
+
return interpret(node.body, env);
|
|
403
|
+
};
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
case "TableDecl": {
|
|
408
|
+
const repo = criarRepositorio(node.name, node.props, env.__dir || process.cwd());
|
|
409
|
+
TABELAS[node.name] = repo;
|
|
410
|
+
env[node.name] = repo;
|
|
411
|
+
|
|
412
|
+
env[`CRIA_${node.name}`] = (dados) => repo.criar(dados);
|
|
413
|
+
env[`LISTA_${node.name.toUpperCase()}`] = () => repo.listar();
|
|
414
|
+
env[`BUSCA_${node.name.toUpperCase()}`] = (id) => repo.buscar(id);
|
|
415
|
+
env[`ATUALIZA_${node.name.toUpperCase()}`] = (id, dados) => repo.atualizar(id, dados);
|
|
416
|
+
env[`DELETA_${node.name.toUpperCase()}`] = (id) => repo.deletar(id);
|
|
417
|
+
|
|
418
|
+
return repo;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
case "MacroDecl":
|
|
422
|
+
return null;
|
|
423
|
+
|
|
424
|
+
default:
|
|
425
|
+
throw new XSError(`Node não suportado: ${node.type}`, {
|
|
426
|
+
loc: node.loc,
|
|
427
|
+
code: "E999",
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function matchPattern(value, pattern, bindings) {
|
|
433
|
+
if (!pattern) return false;
|
|
434
|
+
|
|
435
|
+
switch (pattern.type) {
|
|
436
|
+
case "PatternLiteral":
|
|
437
|
+
return value === pattern.value;
|
|
438
|
+
|
|
439
|
+
case "PatternIdent":
|
|
440
|
+
if (pattern.name === "_") return true;
|
|
441
|
+
bindings[pattern.name] = value;
|
|
442
|
+
return true;
|
|
443
|
+
|
|
444
|
+
case "PatternArray": {
|
|
445
|
+
if (!Array.isArray(value)) return false;
|
|
446
|
+
let pi = 0;
|
|
447
|
+
for (const el of pattern.elements) {
|
|
448
|
+
if (el.type === "PatternRest") {
|
|
449
|
+
bindings["..."] = value.slice(pi);
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
if (pi >= value.length) return false;
|
|
453
|
+
if (!matchPattern(value[pi], el, bindings)) return false;
|
|
454
|
+
pi++;
|
|
455
|
+
}
|
|
456
|
+
return pi === value.length;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
case "PatternObject": {
|
|
460
|
+
if (typeof value !== "object" || value === null) return false;
|
|
461
|
+
for (const prop of pattern.props) {
|
|
462
|
+
if (!(prop.key in value)) return false;
|
|
463
|
+
if (!matchPattern(value[prop.key], prop.pattern, bindings)) return false;
|
|
464
|
+
}
|
|
465
|
+
return true;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
default:
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
}
|
package/src/lexer.js
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
const KEYWORDS = new Set([
|
|
2
|
+
"PARTIU", "ACABOU",
|
|
3
|
+
"CRIA",
|
|
4
|
+
"SE", "LIGA", "SO", "SENAO",
|
|
5
|
+
"REPETE", "NA", "MORAL",
|
|
6
|
+
"CHAMA", "ESSE", "CARA",
|
|
7
|
+
"VOLTA",
|
|
8
|
+
"IMPORTA",
|
|
9
|
+
"EXPORTA",
|
|
10
|
+
"SOLTA", "O", "GRITO",
|
|
11
|
+
"FALA", "BAIXO",
|
|
12
|
+
"AGORA", "VAI",
|
|
13
|
+
"ESPERA", "AI",
|
|
14
|
+
"SORTEIA",
|
|
15
|
+
"PARSEIA",
|
|
16
|
+
"OUVE", "AQUI",
|
|
17
|
+
"VERDADEIRO",
|
|
18
|
+
"FALSO",
|
|
19
|
+
"NULO",
|
|
20
|
+
"TENTA",
|
|
21
|
+
"PEGA",
|
|
22
|
+
"ERRO",
|
|
23
|
+
"ASSINCRONO",
|
|
24
|
+
"VOA",
|
|
25
|
+
"CONTINUA",
|
|
26
|
+
|
|
27
|
+
"CLASSE",
|
|
28
|
+
"HERDA",
|
|
29
|
+
"CONSTRUTOR",
|
|
30
|
+
"ISTO",
|
|
31
|
+
"NOVA",
|
|
32
|
+
"METODO",
|
|
33
|
+
|
|
34
|
+
"ESCOLHE",
|
|
35
|
+
"CASO",
|
|
36
|
+
"PADRAO",
|
|
37
|
+
|
|
38
|
+
"COMBINA",
|
|
39
|
+
|
|
40
|
+
"SERVIDOR",
|
|
41
|
+
"PARA",
|
|
42
|
+
|
|
43
|
+
"TAMANHO",
|
|
44
|
+
"DIVIDE",
|
|
45
|
+
"TEXTO",
|
|
46
|
+
"ENCONTRA",
|
|
47
|
+
"DECODIFICA",
|
|
48
|
+
"URL",
|
|
49
|
+
"JUNTAR",
|
|
50
|
+
|
|
51
|
+
"TESTE",
|
|
52
|
+
"AFIRMA",
|
|
53
|
+
"ASSUNTO",
|
|
54
|
+
|
|
55
|
+
"TAREFA",
|
|
56
|
+
|
|
57
|
+
"TABELA",
|
|
58
|
+
|
|
59
|
+
"MACRO",
|
|
60
|
+
|
|
61
|
+
"TIPO",
|
|
62
|
+
"CRUD",
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
export function lex(input, file = "input.xs") {
|
|
66
|
+
const tokens = [];
|
|
67
|
+
let i = 0;
|
|
68
|
+
let line = 1;
|
|
69
|
+
let col = 1;
|
|
70
|
+
|
|
71
|
+
function loc() {
|
|
72
|
+
return { line, column: col, file };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function push(tok) {
|
|
76
|
+
tok.loc = loc();
|
|
77
|
+
tokens.push(tok);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const isAlpha = c => /[a-zA-Z_]/.test(c);
|
|
81
|
+
const isNum = c => /[0-9]/.test(c);
|
|
82
|
+
const isAlnum = c => c && /^[a-zA-Z0-9_]$/.test(c);
|
|
83
|
+
|
|
84
|
+
while (i < input.length) {
|
|
85
|
+
const c = input[i];
|
|
86
|
+
|
|
87
|
+
if (c === "\n") { i++; line++; col = 1; continue; }
|
|
88
|
+
if (/\s/.test(c)) { i++; col++; continue; }
|
|
89
|
+
|
|
90
|
+
if (c === '"' || c === "'") {
|
|
91
|
+
const q = c;
|
|
92
|
+
i++; col++;
|
|
93
|
+
let val = "";
|
|
94
|
+
while (i < input.length && input[i] !== q) {
|
|
95
|
+
if (input[i] === "\n") { line++; col = 1; }
|
|
96
|
+
else col++;
|
|
97
|
+
val += input[i++];
|
|
98
|
+
}
|
|
99
|
+
i++; col++;
|
|
100
|
+
push({ type: "STRING", value: val });
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (c === "`") {
|
|
105
|
+
i++; col++;
|
|
106
|
+
let val = "";
|
|
107
|
+
const parts = [];
|
|
108
|
+
while (i < input.length && input[i] !== "`") {
|
|
109
|
+
if (input[i] === "$" && input[i + 1] === "{") {
|
|
110
|
+
parts.push({ type: "TEMPLATE_STR", value: val });
|
|
111
|
+
val = "";
|
|
112
|
+
i += 2; col += 2;
|
|
113
|
+
let expr = "";
|
|
114
|
+
let depth = 1;
|
|
115
|
+
while (i < input.length && depth > 0) {
|
|
116
|
+
if (input[i] === "{") depth++;
|
|
117
|
+
if (input[i] === "}") depth--;
|
|
118
|
+
if (depth > 0) {
|
|
119
|
+
if (input[i] === "\n") { line++; col = 1; }
|
|
120
|
+
else col++;
|
|
121
|
+
expr += input[i++];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
i++; col++;
|
|
125
|
+
parts.push({ type: "TEMPLATE_EXPR", value: expr });
|
|
126
|
+
} else {
|
|
127
|
+
val += input[i++]; col++;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
parts.push({ type: "TEMPLATE_STR", value: val });
|
|
131
|
+
i++; col++;
|
|
132
|
+
push({ type: "TEMPLATE", parts });
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (c === "/" && input[i + 1] === "/") {
|
|
137
|
+
|
|
138
|
+
while (i < input.length && input[i] !== "\n") i++;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (isNum(c)) {
|
|
143
|
+
let num = c;
|
|
144
|
+
i++; col++;
|
|
145
|
+
while (isNum(input[i])) {
|
|
146
|
+
num += input[i++]; col++;
|
|
147
|
+
}
|
|
148
|
+
push({ type: "NUMBER", value: Number(num) });
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (isAlpha(c)) {
|
|
153
|
+
let id = c;
|
|
154
|
+
i++; col++;
|
|
155
|
+
while (isAlnum(input[i])) {
|
|
156
|
+
id += input[i++]; col++;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (KEYWORDS.has(id)) {
|
|
160
|
+
push({ type: id, value: id });
|
|
161
|
+
} else if (id.endsWith("?")) {
|
|
162
|
+
|
|
163
|
+
push({ type: "IDENT", value: id });
|
|
164
|
+
} else {
|
|
165
|
+
push({ type: "IDENT", value: id });
|
|
166
|
+
}
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const two = input.slice(i, i + 2);
|
|
171
|
+
|
|
172
|
+
if ([
|
|
173
|
+
"=>", "&&", "||", "==", "!=", ">=", "<=",
|
|
174
|
+
"+=", "-=", "*=", "/=", "%=", "->", "~=",
|
|
175
|
+
"//", "/*",
|
|
176
|
+
].includes(two)) {
|
|
177
|
+
push({ type: two, value: two });
|
|
178
|
+
i += 2; col += 2;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if ("(){}[];,=:+.-*/<>!%?".includes(c)) {
|
|
183
|
+
push({ type: c, value: c });
|
|
184
|
+
i++; col++;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const err = new Error(`Caractere inválido: "${c}" (código: ${c.charCodeAt(0)})`);
|
|
189
|
+
err.loc = loc();
|
|
190
|
+
throw err;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
push({ type: "EOF" });
|
|
194
|
+
return tokens;
|
|
195
|
+
}
|