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
package/src/cli.js
ADDED
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import readline from "readline";
|
|
6
|
+
import { runXS } from "./runtime.js";
|
|
7
|
+
import { lex } from "./lexer.js";
|
|
8
|
+
import { parse } from "./parser.js";
|
|
9
|
+
import { optimize } from "./optimizer.js";
|
|
10
|
+
import { generate } from "./codegen.js";
|
|
11
|
+
import { setSource, XSError, formatError } from "./errors.js";
|
|
12
|
+
import { interpret, AssertionError } from "./interpreter.js";
|
|
13
|
+
import { createEnv } from "./runtime.js";
|
|
14
|
+
|
|
15
|
+
const [, , cmd, ...rest] = process.argv;
|
|
16
|
+
|
|
17
|
+
const HELP = `
|
|
18
|
+
XanaScript CLI — v2.0
|
|
19
|
+
xs run <file> Executa .xs (AST Interpreter)
|
|
20
|
+
xs vm <file> Executa .xs (Bytecode VM)
|
|
21
|
+
xs fmt <file> Formata .xs
|
|
22
|
+
xs build <file> Gera JavaScript do .xs
|
|
23
|
+
xs build --opt <file> Gera JS ultra-otimizado
|
|
24
|
+
xs build --wasm <file> Gera WebAssembly (.wat + .wasm)
|
|
25
|
+
xs build --standalone <file> Gera .js único com runtime
|
|
26
|
+
xs check <file> Verifica sintaxe
|
|
27
|
+
xs dev [file] Watcher com hot reload
|
|
28
|
+
xs lsp Language Server Protocol (stdin/stdout)
|
|
29
|
+
xs repl Modo interativo
|
|
30
|
+
xs bench Roda benchmark
|
|
31
|
+
|
|
32
|
+
GERENCIADOR DE PACOTES:
|
|
33
|
+
xs init [dir] Cria novo projeto XanaScript
|
|
34
|
+
xs install [pacote] Instala dependências
|
|
35
|
+
xs publish Publica pacote no registro
|
|
36
|
+
xs search <termo> Busca pacotes
|
|
37
|
+
|
|
38
|
+
TESTES:
|
|
39
|
+
xs test [dir] Roda todos os testes (*test*.xs)
|
|
40
|
+
|
|
41
|
+
DOCUMENTAÇÃO:
|
|
42
|
+
xs docs [src] [out] Gera documentação HTML
|
|
43
|
+
|
|
44
|
+
TAREFAS:
|
|
45
|
+
xs <tarefa> Executa tarefa de tarefas.xs
|
|
46
|
+
(auto-detectado)
|
|
47
|
+
|
|
48
|
+
xs help Mostra ajuda
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
if (!cmd || cmd === "help") {
|
|
52
|
+
console.log(HELP);
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
(async () => {
|
|
57
|
+
try {
|
|
58
|
+
if (cmd === "init") {
|
|
59
|
+
const { initProject } = await import("./pkgmgr.js");
|
|
60
|
+
await initProject(rest[0]);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (cmd === "install") {
|
|
65
|
+
const { installPackages } = await import("./pkgmgr.js");
|
|
66
|
+
await installPackages(rest);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (cmd === "publish") {
|
|
71
|
+
const { publishPackage } = await import("./pkgmgr.js");
|
|
72
|
+
await publishPackage();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (cmd === "search") {
|
|
77
|
+
const { searchPackages } = await import("./pkgmgr.js");
|
|
78
|
+
await searchPackages(rest.join(" "));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (cmd === "test") {
|
|
83
|
+
const { runTests } = await import("./testrunner.js");
|
|
84
|
+
await runTests(rest[0] || ".");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (cmd === "docs") {
|
|
89
|
+
const { generateDocs } = await import("./docsgen.js");
|
|
90
|
+
await generateDocs(rest[0] || ".", rest[1] || "docs");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (cmd === "lsp") {
|
|
95
|
+
const { startLSP } = await import("./lsp.js");
|
|
96
|
+
startLSP();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (cmd === "repl") {
|
|
101
|
+
await startREPL();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (cmd === "dev") {
|
|
106
|
+
const watchFile = rest[0] || "index.xs";
|
|
107
|
+
if (!fs.existsSync(watchFile)) {
|
|
108
|
+
console.log(` Arquivo não encontrado: ${watchFile}`);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
console.log(` Assistindo: ${watchFile}`);
|
|
112
|
+
const chokidar = await import("chokidar").catch(() => null);
|
|
113
|
+
if (!chokidar) {
|
|
114
|
+
console.log(" Use polling fallback...");
|
|
115
|
+
await simpleWatch(watchFile);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
chokidar.watch(watchFile, { persistent: true }).on("change", async () => {
|
|
119
|
+
console.log(`\n↻ ${new Date().toLocaleTimeString()}`);
|
|
120
|
+
try {
|
|
121
|
+
const c = fs.readFileSync(watchFile, "utf-8");
|
|
122
|
+
setSource(c, watchFile);
|
|
123
|
+
await runXS(c, process.cwd(), watchFile);
|
|
124
|
+
console.log(" OK");
|
|
125
|
+
} catch (e) {
|
|
126
|
+
if (e instanceof XSError) {
|
|
127
|
+
console.error(e.toString());
|
|
128
|
+
} else {
|
|
129
|
+
console.error("", e.message);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
await new Promise(() => {});
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (cmd === "bench") {
|
|
138
|
+
const { runBench } = await import("./bench.js");
|
|
139
|
+
await runBench();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const taskFiles = ["tarefas.xs", "TASKS.xs", "tasks.xs"];
|
|
144
|
+
const taskFile = taskFiles.find(f => fs.existsSync(f));
|
|
145
|
+
let taskRan = false;
|
|
146
|
+
if (taskFile && cmd !== "test" && cmd !== "docs") {
|
|
147
|
+
const taskCode = fs.readFileSync(taskFile, "utf-8");
|
|
148
|
+
setSource(taskCode, taskFile);
|
|
149
|
+
const taskTokens = lex(taskCode);
|
|
150
|
+
const taskAst = parse(taskTokens);
|
|
151
|
+
const taskOpt = optimize(taskAst);
|
|
152
|
+
const taskEnv = createEnv(process.cwd());
|
|
153
|
+
|
|
154
|
+
await interpret(taskOpt, taskEnv);
|
|
155
|
+
|
|
156
|
+
if (taskEnv.__tasks && taskEnv.__tasks[cmd]) {
|
|
157
|
+
await taskEnv.__tasks[cmd]();
|
|
158
|
+
taskRan = true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (taskRan) return;
|
|
162
|
+
|
|
163
|
+
const file = rest[0];
|
|
164
|
+
if (!file) {
|
|
165
|
+
console.log("Especifique um arquivo .xs");
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const code = fs.readFileSync(file, "utf-8");
|
|
170
|
+
setSource(code, file);
|
|
171
|
+
|
|
172
|
+
if (cmd === "run" || cmd === ".") {
|
|
173
|
+
await runXS(code, path.dirname(path.resolve(file)), file);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (cmd === "vm") {
|
|
178
|
+
const { compile } = await import("./bytecode/compiler.js");
|
|
179
|
+
const { run } = await import("./bytecode/vm.js");
|
|
180
|
+
|
|
181
|
+
const tokens = lex(code);
|
|
182
|
+
let ast = parse(tokens);
|
|
183
|
+
ast = optimize(ast);
|
|
184
|
+
const bytecode = compile(ast);
|
|
185
|
+
|
|
186
|
+
console.log("BYTECODE:");
|
|
187
|
+
console.log(bytecode);
|
|
188
|
+
console.log("\nRESULT:");
|
|
189
|
+
console.log(run(bytecode));
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (cmd === "fmt") {
|
|
194
|
+
const tokens = lex(code);
|
|
195
|
+
const ast = parse(tokens);
|
|
196
|
+
console.log(formatAST(ast));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (cmd === "build") {
|
|
201
|
+
const tokens = lex(code);
|
|
202
|
+
let ast = parse(tokens);
|
|
203
|
+
ast = optimize(ast);
|
|
204
|
+
|
|
205
|
+
if (rest.includes("--wasm") || rest.includes("-w")) {
|
|
206
|
+
const { compileWasm, generateWasm, getWasmRuntime } = await import("./wasm_binary.js");
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
const wasmBytes = compileWasm(ast);
|
|
210
|
+
const wasmFile = file.replace(/\.xs$/, ".wasm");
|
|
211
|
+
fs.writeFileSync(wasmFile, Buffer.from(wasmBytes));
|
|
212
|
+
console.log(` Gerado .wasm direto: ${wasmFile} (${wasmBytes.length} bytes)`);
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const wasmMod = await WebAssembly.instantiate(wasmBytes, { env: getWasmRuntime() });
|
|
216
|
+
const result = wasmMod.instance.exports.main?.() ?? 0;
|
|
217
|
+
console.log(` Teste: main() = ${result}`);
|
|
218
|
+
} catch (e) {
|
|
219
|
+
console.log(` Execução: ${e.message}`);
|
|
220
|
+
}
|
|
221
|
+
} catch (e) {
|
|
222
|
+
console.log(` Binary fallback: ${e.message}`);
|
|
223
|
+
console.log(` Gerando WAT como alternativa...`);
|
|
224
|
+
const wat = generateWasm(ast);
|
|
225
|
+
const outFile = file.replace(/\.xs$/, ".wat");
|
|
226
|
+
fs.writeFileSync(outFile, wat, "utf-8");
|
|
227
|
+
console.log(` Gerado WAT: ${outFile}`);
|
|
228
|
+
console.log(` Para compilar: npm install -g wabt && wat2wasm ${outFile} -o ${wasmFile}`);
|
|
229
|
+
}
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (rest.includes("--standalone") || rest.includes("-s")) {
|
|
234
|
+
const { generateOpt, inferTypes } = await import("./codegen_opt.js");
|
|
235
|
+
const types = inferTypes(ast);
|
|
236
|
+
const jsCode = generateOpt(ast, types);
|
|
237
|
+
const standalone = buildStandalone(jsCode, file);
|
|
238
|
+
const outFile = file.replace(/\.xs$/, ".js");
|
|
239
|
+
fs.writeFileSync(outFile, standalone, "utf-8");
|
|
240
|
+
console.log(` Gerado: ${outFile}`);
|
|
241
|
+
console.log(` Rode com: node ${outFile}`);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (rest.includes("--opt") || rest.includes("-o")) {
|
|
246
|
+
const { generateOpt, inferTypes } = await import("./codegen_opt.js");
|
|
247
|
+
const types = inferTypes(ast);
|
|
248
|
+
console.log(generateOpt(ast, types));
|
|
249
|
+
} else {
|
|
250
|
+
console.log(generate(ast));
|
|
251
|
+
}
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (cmd === "check") {
|
|
256
|
+
const tokens = lex(code);
|
|
257
|
+
parse(tokens);
|
|
258
|
+
console.log(" Sintaxe OK");
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
console.log(`Comando desconhecido: ${cmd}`);
|
|
263
|
+
console.log(HELP);
|
|
264
|
+
} catch (e) {
|
|
265
|
+
if (e instanceof XSError) {
|
|
266
|
+
console.error(e.toString());
|
|
267
|
+
} else {
|
|
268
|
+
console.error(`\x1b[1;31m╔═══ XanaScript ERROR \x1b[0m`);
|
|
269
|
+
console.error(`\x1b[1;31m║\x1b[0m ${e.message}`);
|
|
270
|
+
if (e.loc) {
|
|
271
|
+
console.error(`\x1b[1;31m║\x1b[0m \x1b[2m --> ${e.loc.file}:${e.loc.line}:${e.loc.column}\x1b[0m`);
|
|
272
|
+
}
|
|
273
|
+
console.error(`\x1b[1;31m╚══════════════════════════════════\x1b[0m`);
|
|
274
|
+
}
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
})();
|
|
278
|
+
|
|
279
|
+
async function startREPL() {
|
|
280
|
+
const rl = readline.createInterface({
|
|
281
|
+
input: process.stdin,
|
|
282
|
+
output: process.stdout,
|
|
283
|
+
prompt: "xs> "
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
let env = createEnv(process.cwd());
|
|
287
|
+
|
|
288
|
+
console.log("XanaScript REPL — digite .help para comandos");
|
|
289
|
+
rl.prompt();
|
|
290
|
+
|
|
291
|
+
rl.on("line", async line => {
|
|
292
|
+
line = line.trim();
|
|
293
|
+
if (!line) { rl.prompt(); return; }
|
|
294
|
+
|
|
295
|
+
if (line === ".exit") { rl.close(); return; }
|
|
296
|
+
if (line === ".help") {
|
|
297
|
+
console.log("Comandos: .exit, .help, .reset");
|
|
298
|
+
rl.prompt();
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (line === ".reset") {
|
|
302
|
+
env = createEnv(process.cwd());
|
|
303
|
+
console.log("Ambiente resetado");
|
|
304
|
+
rl.prompt();
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
setSource(line, "<repl>");
|
|
310
|
+
const toks = lex(line);
|
|
311
|
+
const ast = parse(toks);
|
|
312
|
+
if (ast.body.length === 1 && ast.body[0].type === "Program") {
|
|
313
|
+
const result = await interpret(ast, env);
|
|
314
|
+
if (result !== undefined) console.log(result);
|
|
315
|
+
} else {
|
|
316
|
+
for (const stmt of ast.body) {
|
|
317
|
+
const result = await interpret(stmt, env);
|
|
318
|
+
if (result !== undefined) console.log(result);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
} catch (e) {
|
|
322
|
+
if (e instanceof XSError) {
|
|
323
|
+
console.error(e.toString());
|
|
324
|
+
} else {
|
|
325
|
+
console.error("erro:", e.message);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
rl.prompt();
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
rl.on("close", () => {
|
|
333
|
+
console.log("Até mais!");
|
|
334
|
+
process.exit(0);
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function simpleWatch(file) {
|
|
339
|
+
let last = fs.statSync(file).mtimeMs;
|
|
340
|
+
console.log(` Polling a cada 500ms`);
|
|
341
|
+
return new Promise(async (resolve) => {
|
|
342
|
+
while (true) {
|
|
343
|
+
await new Promise(r => setTimeout(r, 500));
|
|
344
|
+
try {
|
|
345
|
+
const mtime = fs.statSync(file).mtimeMs;
|
|
346
|
+
if (mtime > last) {
|
|
347
|
+
last = mtime;
|
|
348
|
+
console.log(`\n↻ ${new Date().toLocaleTimeString()}`);
|
|
349
|
+
try {
|
|
350
|
+
const c = fs.readFileSync(file, "utf-8");
|
|
351
|
+
setSource(c, file);
|
|
352
|
+
const { runXS } = await import("./runtime.js");
|
|
353
|
+
await runXS(c, process.cwd(), file);
|
|
354
|
+
console.log(" OK");
|
|
355
|
+
} catch (e) {
|
|
356
|
+
if (e instanceof XSError) {
|
|
357
|
+
console.error(e.toString());
|
|
358
|
+
} else {
|
|
359
|
+
console.error("", e.message);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
} catch {}
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function formatAST(node, indent = 0) {
|
|
369
|
+
const sp = " ".repeat(indent);
|
|
370
|
+
switch (node.type) {
|
|
371
|
+
case "Program":
|
|
372
|
+
return node.body.map(n => formatAST(n, 0)).join("\n");
|
|
373
|
+
case "Block":
|
|
374
|
+
return `{\n${node.body.map(n => formatAST(n, indent + 1)).join("\n")}\n${sp}}`;
|
|
375
|
+
case "VarDecl":
|
|
376
|
+
return `${sp}CRIA ${node.id} = ${formatAST(node.init, indent)}`;
|
|
377
|
+
case "Assign":
|
|
378
|
+
return `${sp}${formatAST(node.left, indent)} = ${formatAST(node.right, indent)}`;
|
|
379
|
+
case "IfStmt": {
|
|
380
|
+
let s = `${sp}SE LIGA SO (${formatAST(node.test, indent)}) ${formatAST(node.cons, indent)}`;
|
|
381
|
+
if (node.alt) s += ` SENAO ${formatAST(node.alt, indent)}`;
|
|
382
|
+
return s;
|
|
383
|
+
}
|
|
384
|
+
case "ForStmt": {
|
|
385
|
+
const init = node.init ? formatAST(node.init, indent) : "";
|
|
386
|
+
const test = formatAST(node.test, indent);
|
|
387
|
+
const update = node.update ? formatAST(node.update, indent) : "";
|
|
388
|
+
return `${sp}REPETE NA MORAL (${init}; ${test}; ${update}) ${formatAST(node.body, indent)}`;
|
|
389
|
+
}
|
|
390
|
+
case "WhileStmt":
|
|
391
|
+
return `${sp}REPETE AI (${formatAST(node.test, indent)}) ${formatAST(node.body, indent)}`;
|
|
392
|
+
case "FunctionDecl": {
|
|
393
|
+
const params = node.params.join(", ");
|
|
394
|
+
return `${sp}CHAMA ESSE CARA ${node.name}(${params}) ${formatAST(node.body, indent)}`;
|
|
395
|
+
}
|
|
396
|
+
case "ReturnStmt":
|
|
397
|
+
return `${sp}VOLTA${node.arg ? " " + formatAST(node.arg, indent) : ""}`;
|
|
398
|
+
case "Call": {
|
|
399
|
+
const args = node.args.map(a => formatAST(a, indent)).join(", ");
|
|
400
|
+
if (node.callee.type === "Ident") {
|
|
401
|
+
const name = node.callee.name;
|
|
402
|
+
if (name === "SOLTA_O_GRITO") return `${sp}SOLTA O GRITO(${args})`;
|
|
403
|
+
if (name === "FALA_BAIXO") return `${sp}FALA BAIXO(${args})`;
|
|
404
|
+
if (name === "AGORA_VAI") return `${sp}AGORA VAI(${args})`;
|
|
405
|
+
if (name === "ESPERA_AI") return `${sp}ESPERA AI(${args})`;
|
|
406
|
+
if (name === "SORTEIA") return `${sp}SORTEIA(${args})`;
|
|
407
|
+
if (name === "PARSEIA") return `${sp}PARSEIA(${args})`;
|
|
408
|
+
if (name === "OUVE_AQUI") return `${sp}OUVE AQUI(${args})`;
|
|
409
|
+
return `${sp}${node.callee.name}(${args})`;
|
|
410
|
+
}
|
|
411
|
+
return `${sp}${formatAST(node.callee, indent)}(${args})`;
|
|
412
|
+
}
|
|
413
|
+
case "Member":
|
|
414
|
+
return `${formatAST(node.obj, indent)}.${node.prop}`;
|
|
415
|
+
case "IndexExpr":
|
|
416
|
+
return `${formatAST(node.obj, indent)}[${formatAST(node.index, indent)}]`;
|
|
417
|
+
case "Binary":
|
|
418
|
+
return `(${formatAST(node.left, indent)} ${node.op} ${formatAST(node.right, indent)})`;
|
|
419
|
+
case "Unary":
|
|
420
|
+
return `${node.op}${formatAST(node.arg, indent)}`;
|
|
421
|
+
case "Ident":
|
|
422
|
+
return node.name;
|
|
423
|
+
case "Num":
|
|
424
|
+
return String(node.value);
|
|
425
|
+
case "Str":
|
|
426
|
+
return JSON.stringify(node.value);
|
|
427
|
+
case "Bool":
|
|
428
|
+
return node.value ? "VERDADEIRO" : "FALSO";
|
|
429
|
+
case "Nil":
|
|
430
|
+
return "NULO";
|
|
431
|
+
case "ArrayExpr":
|
|
432
|
+
return `[${node.items.map(i => formatAST(i, indent)).join(", ")}]`;
|
|
433
|
+
case "ObjectExpr":
|
|
434
|
+
return `{${node.props.map(p => `${p.key}: ${formatAST(p.value, indent)}`).join(", ")}}`;
|
|
435
|
+
case "TryCatchStmt":
|
|
436
|
+
return `${sp}TENTA ${formatAST(node.tryBlock, indent)} PEGA(${node.catchParam}) ${formatAST(node.catchBlock, indent)}`;
|
|
437
|
+
case "ImportExpr":
|
|
438
|
+
return `IMPORTA ${JSON.stringify(node.path)}`;
|
|
439
|
+
case "ImportStmt":
|
|
440
|
+
return `${sp}IMPORTA ${JSON.stringify(node.path)}`;
|
|
441
|
+
case "ExportStmt":
|
|
442
|
+
return `${sp}EXPORTA ${node.name}`;
|
|
443
|
+
case "BreakStmt":
|
|
444
|
+
return `${sp}VOA()`;
|
|
445
|
+
case "ContinueStmt":
|
|
446
|
+
return `${sp}CONTINUA()`;
|
|
447
|
+
case "Ternary":
|
|
448
|
+
return `(${formatAST(node.test, indent)} ? ${formatAST(node.cons, indent)} : ${formatAST(node.alt, indent)})`;
|
|
449
|
+
default:
|
|
450
|
+
return `${sp}`;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const STDLIB_RUNTIME = `
|
|
455
|
+
|
|
456
|
+
const __xs_cache = new Map();
|
|
457
|
+
const __xs_loading = new Set();
|
|
458
|
+
|
|
459
|
+
function __xs_require(mod) {
|
|
460
|
+
if (mod.startsWith(".") || mod.startsWith("/")) {
|
|
461
|
+
const { resolve } = require("path");
|
|
462
|
+
const { readFileSync } = require("fs");
|
|
463
|
+
const full = resolve(__dirname, mod);
|
|
464
|
+
if (__xs_cache.has(full)) return __xs_cache.get(full);
|
|
465
|
+
if (__xs_loading.has(full)) throw new Error("Import cíclico: " + mod);
|
|
466
|
+
__xs_loading.add(full);
|
|
467
|
+
const code = readFileSync(full, "utf-8");
|
|
468
|
+
const exports = {};
|
|
469
|
+
if (full.endsWith(".js")) {
|
|
470
|
+
const m = require(full);
|
|
471
|
+
__xs_cache.set(full, m);
|
|
472
|
+
__xs_loading.delete(full);
|
|
473
|
+
return m;
|
|
474
|
+
}
|
|
475
|
+
const fn = new Function("require", "__exports", "__dirname", code);
|
|
476
|
+
fn(require, exports, require("path").dirname(full));
|
|
477
|
+
__xs_cache.set(full, exports);
|
|
478
|
+
__xs_loading.delete(full);
|
|
479
|
+
return exports;
|
|
480
|
+
}
|
|
481
|
+
return require(mod);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function __randInt(a, b) {
|
|
485
|
+
return Math.floor(Math.random() * (b - a + 1)) + a;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function __env(k) {
|
|
489
|
+
return process.env[k] ?? null;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function __http(url) {
|
|
493
|
+
try {
|
|
494
|
+
const mod = require(url.startsWith("https") ? "https" : "http");
|
|
495
|
+
return new Promise((resolve, reject) => {
|
|
496
|
+
const req = mod.get(url, res => {
|
|
497
|
+
let data = "";
|
|
498
|
+
res.on("data", c => data += c);
|
|
499
|
+
res.on("end", () => {
|
|
500
|
+
try { resolve(JSON.parse(data)); }
|
|
501
|
+
catch { resolve(data); }
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
req.on("error", reject);
|
|
505
|
+
req.setTimeout(3000, () => { req.destroy(); reject(new Error("timeout")); });
|
|
506
|
+
});
|
|
507
|
+
} catch {
|
|
508
|
+
throw new Error("AGORA_VAI não disponível neste ambiente");
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function __sleep(ms) {
|
|
513
|
+
return new Promise(r => setTimeout(r, ms));
|
|
514
|
+
}
|
|
515
|
+
`;
|
|
516
|
+
|
|
517
|
+
function buildStandalone(jsCode, entryFile) {
|
|
518
|
+
const dir = path.dirname(path.resolve(entryFile));
|
|
519
|
+
|
|
520
|
+
return `#!/usr/bin/env node
|
|
521
|
+
|
|
522
|
+
${STDLIB_RUNTIME}
|
|
523
|
+
const __dirname = ${JSON.stringify(dir)};
|
|
524
|
+
|
|
525
|
+
(async () => {
|
|
526
|
+
try {
|
|
527
|
+
${jsCode.split("\n").map(l => " " + l).join("\n")}
|
|
528
|
+
} catch (e) {
|
|
529
|
+
console.error("\\n XanaScript runtime error:");
|
|
530
|
+
console.error(e.stack || e.message);
|
|
531
|
+
process.exit(1);
|
|
532
|
+
}
|
|
533
|
+
})();
|
|
534
|
+
`;
|
|
535
|
+
}
|
package/src/codegen.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
export function generate(node) {
|
|
2
|
+
switch (node.type) {
|
|
3
|
+
case "FunctionDecl":
|
|
4
|
+
return `function ${node.name}(${node.params.join(",")}) ${generate(node.body)}`;
|
|
5
|
+
case "ReturnStmt":
|
|
6
|
+
return node.arg ? `return ${generate(node.arg)};` : `return;`;
|
|
7
|
+
case "Program":
|
|
8
|
+
return node.body.map(generate).join("\n");
|
|
9
|
+
case "Block":
|
|
10
|
+
return `{\n${node.body.map(generate).join("\n")}\n}`;
|
|
11
|
+
case "VarDecl":
|
|
12
|
+
return `let ${node.id} = ${generate(node.init)};`;
|
|
13
|
+
case "ExportStmt":
|
|
14
|
+
return `__exports["${node.name}"] = ${node.name};`;
|
|
15
|
+
case "IfStmt":
|
|
16
|
+
return `if (${generate(node.test)}) ${generate(node.cons)}`
|
|
17
|
+
+ (node.alt ? ` else ${generate(node.alt)}` : "");
|
|
18
|
+
case "ForStmt":
|
|
19
|
+
return `for (${genForInit(node.init)}; ${generate(node.test)}; ${generate(node.update)}) ${generate(node.body)}`;
|
|
20
|
+
case "Assign":
|
|
21
|
+
return `${generate(node.left)} = ${generate(node.right)}`;
|
|
22
|
+
case "Binary":
|
|
23
|
+
return `(${generate(node.left)} ${node.op} ${generate(node.right)})`;
|
|
24
|
+
case "Unary":
|
|
25
|
+
return `(${node.op}${generate(node.arg)})`;
|
|
26
|
+
case "Call": {
|
|
27
|
+
const callee = generate(node.callee);
|
|
28
|
+
const args = node.args.map(generate).join(",");
|
|
29
|
+
if (callee === "SOLTA_O_GRITO") {
|
|
30
|
+
return `console.log(${args});`;
|
|
31
|
+
}
|
|
32
|
+
if (callee === "FALA_BAIXO") {
|
|
33
|
+
return `console.warn(${args});`;
|
|
34
|
+
}
|
|
35
|
+
if (callee === "AGORA_VAI") {
|
|
36
|
+
return `await __http(${args})`;
|
|
37
|
+
}
|
|
38
|
+
if (callee === "ESPERA_AI") {
|
|
39
|
+
return `await __sleep(${args})`;
|
|
40
|
+
}
|
|
41
|
+
if (callee === "SORTEIA") {
|
|
42
|
+
return `__randInt(${args})`;
|
|
43
|
+
}
|
|
44
|
+
if (callee === "PARSEIA") {
|
|
45
|
+
return `JSON.parse(${args})`;
|
|
46
|
+
}
|
|
47
|
+
if (callee === "OUVE_AQUI") {
|
|
48
|
+
return `__env(${args})`;
|
|
49
|
+
}
|
|
50
|
+
if (callee === "__IMPORT__") {
|
|
51
|
+
return `await __require(${args})`;
|
|
52
|
+
}
|
|
53
|
+
return `${callee}(${args})`;
|
|
54
|
+
}
|
|
55
|
+
case "Member":
|
|
56
|
+
return `${generate(node.obj)}.${node.prop}`;
|
|
57
|
+
case "Ident":
|
|
58
|
+
if (node.name === "SOLTA" || node.name === "FALA") { }
|
|
59
|
+
return node.name;
|
|
60
|
+
case "Num":
|
|
61
|
+
return String(node.value);
|
|
62
|
+
case "Str":
|
|
63
|
+
return JSON.stringify(node.value);
|
|
64
|
+
case "Bool":
|
|
65
|
+
return node.value ? "true" : "false";
|
|
66
|
+
case "Nil":
|
|
67
|
+
return "null";
|
|
68
|
+
case "ArrayExpr":
|
|
69
|
+
return `[${node.items.map(generate).join(",")}]`;
|
|
70
|
+
case "ObjectExpr":
|
|
71
|
+
return `{${node.props.map(p => `${p.key}:${generate(p.value)}`).join(",")}}`;
|
|
72
|
+
case "Member":
|
|
73
|
+
return `${generate(node.obj)}.${node.prop}`;
|
|
74
|
+
case "IndexExpr":
|
|
75
|
+
return `${generate(node.obj)}[${generate(node.index)}]`;
|
|
76
|
+
case "ImportExpr":
|
|
77
|
+
return `await __require(${JSON.stringify(node.path)})`;
|
|
78
|
+
case "ImportStmt":
|
|
79
|
+
return `await __require(${JSON.stringify(node.path)});`;
|
|
80
|
+
case "TryCatchStmt":
|
|
81
|
+
return `try ${generate(node.tryBlock)} catch(${node.catchParam}) ${generate(node.catchBlock)}`;
|
|
82
|
+
case "WhileStmt":
|
|
83
|
+
return `while(${generate(node.test)}) ${generate(node.body)}`;
|
|
84
|
+
case "BreakStmt":
|
|
85
|
+
return `break;`;
|
|
86
|
+
case "ContinueStmt":
|
|
87
|
+
return `continue;`;
|
|
88
|
+
case "Ternary":
|
|
89
|
+
return `(${generate(node.test)}?${generate(node.cons)}:${generate(node.alt)})`;
|
|
90
|
+
default:
|
|
91
|
+
throw new Error("Node não suportado: " + node.type);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function genForInit(init) {
|
|
96
|
+
if (!init) return "";
|
|
97
|
+
if (init.type === "VarDecl") {
|
|
98
|
+
return `let ${init.id} = ${generate(init.init)}`.replace(/;$/, "");
|
|
99
|
+
}
|
|
100
|
+
return generate(init);
|
|
101
|
+
}
|