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/src/lsp.js ADDED
@@ -0,0 +1,304 @@
1
+ import fs from "fs";
2
+ import { lex } from "./lexer.js";
3
+ import { parse } from "./parser.js";
4
+ import { setSource, XSError, formatError } from "./errors.js";
5
+
6
+ const KEYWORDS = [
7
+ "PARTIU", "ACABOU", "CRIA", "SE", "LIGA", "SO", "SENAO",
8
+ "REPETE", "NA", "MORAL", "CHAMA", "ESSE", "CARA", "VOLTA",
9
+ "IMPORTA", "EXPORTA", "SOLTA", "O", "GRITO", "FALA", "BAIXO",
10
+ "AGORA", "VAI", "ESPERA", "AI", "SORTEIA", "PARSEIA", "OUVE",
11
+ "AQUI", "VERDADEIRO", "FALSO", "NULO", "TENTA", "PEGA", "ERRO",
12
+ "ASSINCRONO", "VOA", "CONTINUA", "CLASSE", "HERDA", "CONSTRUTOR",
13
+ "ISTO", "NOVA", "METODO", "ESCOLHE", "CASO", "PADRAO", "COMBINA",
14
+ "CRIA", "SERVIDOR", "PARA", "TAMANHO", "DIVIDE", "TEXTO",
15
+ "ENCONTRA", "DECODIFICA", "URL", "JUNTAR", "TESTE", "AFIRMA",
16
+ "ASSUNTO", "TAREFA", "MACRO",
17
+ ];
18
+
19
+ const BUILTIN_FUNCTIONS = [
20
+ { name: "SOLTA_O_GRITO", params: "...args", doc: "console.log()" },
21
+ { name: "FALA_BAIXO", params: "...args", doc: "console.warn()" },
22
+ { name: "AGORA_VAI", params: "url", doc: "HTTP GET request" },
23
+ { name: "ESPERA_AI", params: "ms", doc: "setTimeout()" },
24
+ { name: "SORTEIA", params: "min, max", doc: "Random integer" },
25
+ { name: "PARSEIA", params: "json", doc: "JSON.parse()" },
26
+ { name: "OUVE_AQUI", params: "chave", doc: "ENV variable" },
27
+ { name: "TAMANHO", params: "valor", doc: "Length of array/string" },
28
+ { name: "DIVIDE_TEXTO", params: "texto, separador", doc: "String split" },
29
+ { name: "ENCONTRA", params: "texto, regex", doc: "Regex match" },
30
+ { name: "DECODIFICA_URL", params: "url", doc: "URL decode" },
31
+ { name: "JUNTAR", params: "array, separador", doc: "Array join" },
32
+ { name: "AGORA", params: "", doc: "Date.now()" },
33
+ { name: "AFIRMA", params: "condicao", doc: "Assert truthy" },
34
+ { name: "ASSUNTO", params: "a, b", doc: "Assert equal" },
35
+ { name: "CRIA_SERVIDOR", params: "porta, handler", doc: "Create HTTP server" },
36
+ { name: "PARA_SERVIDOR", params: "server", doc: "Stop HTTP server" },
37
+ ];
38
+
39
+ let documents = new Map();
40
+ let requestId = 0;
41
+
42
+ export async function startLSPServer() {
43
+ process.stdin.setEncoding("utf-8");
44
+ let buffer = "";
45
+
46
+ process.stdin.on("data", (chunk) => {
47
+ buffer += chunk;
48
+ const parts = buffer.split("\r\n\r\n");
49
+ if (parts.length < 2) return;
50
+
51
+ const header = parts[0];
52
+ const contentLength = parseInt(header.match(/Content-Length: (\d+)/)?.[1] || "0");
53
+ const bodyStart = header.length + 4;
54
+
55
+ if (buffer.length < bodyStart + contentLength) return;
56
+
57
+ const body = buffer.slice(bodyStart, bodyStart + contentLength);
58
+ buffer = buffer.slice(bodyStart + contentLength);
59
+
60
+ try {
61
+ const msg = JSON.parse(body);
62
+ handleMessage(msg);
63
+ } catch (e) {
64
+
65
+ }
66
+ });
67
+
68
+ process.stdin.on("end", () => process.exit(0));
69
+ }
70
+
71
+ function sendMessage(msg) {
72
+ const body = JSON.stringify(msg);
73
+ const header = `Content-Length: ${Buffer.byteLength(body, "utf-8")}\r\nContent-Type: application/vscode-jsonrpc;charset=utf-8\r\n\r\n`;
74
+ process.stdout.write(header + body);
75
+ }
76
+
77
+ function handleMessage(msg) {
78
+ const { method, id, params } = msg;
79
+
80
+ switch (method) {
81
+ case "initialize":
82
+ sendMessage({
83
+ jsonrpc: "2.0",
84
+ id,
85
+ result: {
86
+ capabilities: {
87
+ textDocumentSync: { openClose: true, change: 1 },
88
+ completionProvider: { triggerCharacters: [".", "(", " "] },
89
+ hoverProvider: true,
90
+ definitionProvider: true,
91
+ diagnosticProvider: true,
92
+ },
93
+ serverInfo: { name: "xanascript-lsp", version: "2.0.0" },
94
+ },
95
+ });
96
+ break;
97
+
98
+ case "initialized":
99
+ sendMessage({ jsonrpc: "2.0", method: "window/logMessage", params: { type: 3, message: "XanaScript LSP iniciado" } });
100
+ break;
101
+
102
+ case "textDocument/didOpen":
103
+ case "textDocument/didChange": {
104
+ const uri = params.textDocument?.uri || params.textDocument?.uri;
105
+ const text = params.textDocument?.text || params.contentChanges?.[0]?.text;
106
+ if (uri && text) {
107
+ documents.set(uri, text);
108
+ validateDocument(uri, text);
109
+ }
110
+ break;
111
+ }
112
+
113
+ case "textDocument/didClose": {
114
+ const uri = params.textDocument?.uri;
115
+ if (uri) documents.delete(uri);
116
+ break;
117
+ }
118
+
119
+ case "textDocument/completion": {
120
+ const uri = params.textDocument?.uri;
121
+ const line = params.position?.line || 0;
122
+ const col = params.position?.character || 0;
123
+ const text = documents.get(uri);
124
+ if (text) {
125
+ const items = getCompletions(text, line, col);
126
+ sendMessage({ jsonrpc: "2.0", id, result: { isIncomplete: false, items } });
127
+ }
128
+ break;
129
+ }
130
+
131
+ case "textDocument/hover": {
132
+ const uri = params.textDocument?.uri;
133
+ const line = params.position?.line || 0;
134
+ const col = params.position?.character || 0;
135
+ const text = documents.get(uri);
136
+ if (text) {
137
+ const hover = getHover(text, line, col);
138
+ sendMessage({ jsonrpc: "2.0", id, result: hover });
139
+ }
140
+ break;
141
+ }
142
+
143
+ case "textDocument/definition": {
144
+ const uri = params.textDocument?.uri;
145
+ const line = params.position?.line || 0;
146
+ const col = params.position?.character || 0;
147
+ const text = documents.get(uri);
148
+ if (text) {
149
+ const def = getDefinition(text, line, col);
150
+ sendMessage({ jsonrpc: "2.0", id, result: def });
151
+ }
152
+ break;
153
+ }
154
+
155
+ case "shutdown":
156
+ sendMessage({ jsonrpc: "2.0", id, result: null });
157
+ break;
158
+
159
+ case "exit":
160
+ process.exit(0);
161
+ break;
162
+ }
163
+ }
164
+
165
+ function validateDocument(uri, text) {
166
+ setSource(text, uri);
167
+ const diagnostics = [];
168
+
169
+ try {
170
+ const tokens = lex(text);
171
+ parse(tokens);
172
+ } catch (e) {
173
+ const loc = e.loc || { line: 1, column: 1 };
174
+ diagnostics.push({
175
+ range: {
176
+ start: { line: loc.line - 1, character: (loc.column || 1) - 1 },
177
+ end: { line: loc.line - 1, character: (loc.column || 1) + 10 },
178
+ },
179
+ severity: 1,
180
+ message: e.message,
181
+ source: "xanascript",
182
+ });
183
+ }
184
+
185
+ sendMessage({
186
+ jsonrpc: "2.0",
187
+ method: "textDocument/publishDiagnostics",
188
+ params: { uri, diagnostics },
189
+ });
190
+ }
191
+
192
+ function getCompletions(text, line, col) {
193
+ const items = [];
194
+
195
+ for (const kw of KEYWORDS) {
196
+ items.push({
197
+ label: kw,
198
+ kind: 14,
199
+ detail: "keyword",
200
+ insertText: kw,
201
+ });
202
+ }
203
+
204
+ for (const fn of BUILTIN_FUNCTIONS) {
205
+ items.push({
206
+ label: fn.name,
207
+ kind: 3,
208
+ detail: `fn(${fn.params})`,
209
+ documentation: fn.doc,
210
+ insertText: fn.name + "($1)",
211
+ insertTextFormat: 2,
212
+ });
213
+ }
214
+
215
+ const words = text.split(/[^a-zA-Z0-9_]/).filter(Boolean);
216
+ const seen = new Set();
217
+ for (const w of words) {
218
+ if (!seen.has(w) && w.length > 1 && !KEYWORDS.includes(w.toUpperCase())) {
219
+ seen.add(w);
220
+ items.push({
221
+ label: w,
222
+ kind: 6,
223
+ insertText: w,
224
+ });
225
+ }
226
+ }
227
+
228
+ return items;
229
+ }
230
+
231
+ function getHover(text, line, col) {
232
+ const word = getWordAt(text, line, col);
233
+ if (!word) return null;
234
+
235
+ const fn = BUILTIN_FUNCTIONS.find(f => f.name === word);
236
+ if (fn) {
237
+ return {
238
+ contents: {
239
+ kind: "markdown",
240
+ value: `\`\`\`xs\n${fn.name}(${fn.params})\n\`\`\`\n\n${fn.doc}`,
241
+ },
242
+ };
243
+ }
244
+
245
+ if (KEYWORDS.includes(word.toUpperCase())) {
246
+ return {
247
+ contents: {
248
+ kind: "markdown",
249
+ value: `\`\`\`xs\n${word}\n\`\`\`\n\nPalavra-chave XanaScript`,
250
+ },
251
+ };
252
+ }
253
+
254
+ return null;
255
+ }
256
+
257
+ function getDefinition(text, line, col) {
258
+ const word = getWordAt(text, line, col);
259
+ if (!word) return null;
260
+
261
+ const patterns = [
262
+ new RegExp(`CHAMA\\s+ESSE\\s+CARA\\s+${word}\\b`),
263
+ new RegExp(`CRIA\\s+${word}\\b`),
264
+ new RegExp(`CLASSE\\s+${word}\\b`),
265
+ new RegExp(`TAREFA\\s+${word}\\b`),
266
+ ];
267
+
268
+ const lines = text.split("\n");
269
+ for (let i = 0; i < lines.length; i++) {
270
+ for (const pat of patterns) {
271
+ const match = lines[i].match(pat);
272
+ if (match) {
273
+ const colIdx = match.index + match[0].indexOf(word);
274
+ return {
275
+ uri: "",
276
+ range: {
277
+ start: { line: i, character: colIdx },
278
+ end: { line: i, character: colIdx + word.length },
279
+ },
280
+ };
281
+ }
282
+ }
283
+ }
284
+
285
+ return null;
286
+ }
287
+
288
+ function getWordAt(text, line, col) {
289
+ const lines = text.split("\n");
290
+ if (line >= lines.length) return null;
291
+ const lineText = lines[line];
292
+ if (col >= lineText.length) return null;
293
+
294
+ let start = col;
295
+ let end = col;
296
+ while (start > 0 && /[a-zA-Z0-9_]/.test(lineText[start - 1])) start--;
297
+ while (end < lineText.length && /[a-zA-Z0-9_]/.test(lineText[end])) end++;
298
+
299
+ return start < end ? lineText.slice(start, end) : null;
300
+ }
301
+
302
+ export function startLSP() {
303
+ startLSPServer().catch(console.error);
304
+ }
package/src/macros.js ADDED
@@ -0,0 +1,132 @@
1
+ import { lex } from "./lexer.js";
2
+ import { parse } from "./parser.js";
3
+ import * as A from "./ast.js";
4
+
5
+ export function expandMacros(node, macros) {
6
+ if (!node || typeof node !== "object") return node;
7
+ if (!macros || macros.size === 0) return node;
8
+
9
+ switch (node.type) {
10
+ case "Program": {
11
+
12
+ const collected = new Map();
13
+ for (const stmt of node.body) {
14
+ if (stmt.type === "MacroDecl") {
15
+ collected.set(stmt.name, stmt);
16
+ }
17
+ }
18
+
19
+ return expandInNode({ ...node, body: node.body.map(stmt => {
20
+ if (stmt.type === "MacroDecl") return null;
21
+ return expandInNode(stmt, collected);
22
+ }).filter(Boolean) }, collected);
23
+ }
24
+
25
+ case "Block": {
26
+ return { ...node, body: node.body.map(s => expandInNode(s, macros)).filter(Boolean) };
27
+ }
28
+
29
+ case "VarDecl": {
30
+ return { ...node, init: expandInNode(node.init, macros) };
31
+ }
32
+
33
+ case "Call": {
34
+
35
+ if (node.callee.type === "Ident") {
36
+ const macro = macros.get(node.callee.name);
37
+ if (macro) {
38
+ return expandMacroCall(macro, node.args, macros);
39
+ }
40
+ }
41
+ return { ...node, callee: expandInNode(node.callee, macros), args: node.args.map(a => expandInNode(a, macros)) };
42
+ }
43
+
44
+ case "Binary":
45
+ return { ...node, left: expandInNode(node.left, macros), right: expandInNode(node.right, macros) };
46
+
47
+ case "Unary":
48
+ return { ...node, arg: expandInNode(node.arg, macros) };
49
+
50
+ case "Assign":
51
+ return { ...node, right: expandInNode(node.right, macros) };
52
+
53
+ case "IfStmt":
54
+ return { ...node, test: expandInNode(node.test, macros), cons: expandInNode(node.cons, macros), alt: node.alt ? expandInNode(node.alt, macros) : null };
55
+
56
+ case "ForStmt":
57
+ return { ...node, init: node.init ? expandInNode(node.init, macros) : null, test: expandInNode(node.test, macros), update: node.update ? expandInNode(node.update, macros) : null, body: expandInNode(node.body, macros) };
58
+
59
+ case "WhileStmt":
60
+ return { ...node, test: expandInNode(node.test, macros), body: expandInNode(node.body, macros) };
61
+
62
+ case "FunctionDecl":
63
+ return { ...node, body: expandInNode(node.body, macros) };
64
+
65
+ case "ReturnStmt":
66
+ return { ...node, arg: node.arg ? expandInNode(node.arg, macros) : null };
67
+
68
+ case "Ternary":
69
+ return { ...node, test: expandInNode(node.test, macros), cons: expandInNode(node.cons, macros), alt: expandInNode(node.alt, macros) };
70
+
71
+ case "ArrayExpr":
72
+ return { ...node, items: node.items.map(i => expandInNode(i, macros)) };
73
+
74
+ case "ObjectExpr":
75
+ return { ...node, props: node.props.map(p => ({ ...p, value: expandInNode(p.value, macros) })) };
76
+
77
+ case "Member":
78
+ return { ...node, obj: expandInNode(node.obj, macros) };
79
+
80
+ case "IndexExpr":
81
+ return { ...node, obj: expandInNode(node.obj, macros), index: expandInNode(node.index, macros) };
82
+
83
+ default:
84
+ return node;
85
+ }
86
+ }
87
+
88
+ function expandInNode(node, macros) {
89
+ if (!node || typeof node !== "object") return node;
90
+ return expandMacros(node, macros);
91
+ }
92
+
93
+ function expandMacroCall(macro, args, macros) {
94
+ const paramScope = {};
95
+ macro.params.forEach((p, i) => {
96
+ paramScope[p] = args[i] || A.Nil();
97
+ });
98
+
99
+ let expandedBody = replaceIdents(macro.body, paramScope);
100
+
101
+ if (expandedBody.type === "Block" && expandedBody.body?.length === 1) {
102
+ expandedBody = expandedBody.body[0];
103
+ }
104
+
105
+ return expandMacros(expandedBody, macros);
106
+ }
107
+
108
+ function replaceIdents(node, scope) {
109
+ if (!node || typeof node !== "object") return node;
110
+
111
+ if (node.type === "Ident") {
112
+ if (scope[node.name] !== undefined) {
113
+ return scope[node.name];
114
+ }
115
+ return node;
116
+ }
117
+
118
+ if (Array.isArray(node.body)) {
119
+ return { ...node, body: node.body.map(s => replaceIdents(s, scope)) };
120
+ }
121
+
122
+ const result = { ...node };
123
+ for (const [key, val] of Object.entries(node)) {
124
+ if (key === "type" || key === "loc") continue;
125
+ if (Array.isArray(val)) {
126
+ result[key] = val.map(v => replaceIdents(v, scope));
127
+ } else if (typeof val === "object" && val !== null) {
128
+ result[key] = replaceIdents(val, scope);
129
+ }
130
+ }
131
+ return result;
132
+ }
@@ -0,0 +1,175 @@
1
+ import { expandMacros } from "./macros.js";
2
+
3
+ function collectMacros(node) {
4
+ const macros = new Map();
5
+ if (!node || node.type !== "Program") return macros;
6
+ for (const stmt of node.body) {
7
+ if (stmt.type === "MacroDecl") {
8
+ macros.set(stmt.name, stmt);
9
+ }
10
+ }
11
+ return macros;
12
+ }
13
+
14
+ export function optimize(node) {
15
+
16
+ const macros = collectMacros(node);
17
+
18
+ if (macros.size > 0) {
19
+ node = expandMacros(node, macros);
20
+ }
21
+
22
+ if (!node || typeof node !== "object") return node;
23
+
24
+ switch (node.type) {
25
+
26
+ case "Program": {
27
+ node.body = node.body.map(s => optimize(s)).filter(Boolean);
28
+ return node;
29
+ }
30
+
31
+ case "Block": {
32
+ node.body = node.body.map(s => optimize(s)).filter(Boolean);
33
+ return node;
34
+ }
35
+
36
+ case "VarDecl": {
37
+ if (node.init) node.init = optimize(node.init);
38
+ return node;
39
+ }
40
+
41
+ case "Assign": {
42
+ node.right = optimize(node.right);
43
+ return node;
44
+ }
45
+
46
+ case "IfStmt": {
47
+ node.test = optimize(node.test);
48
+ node.cons = optimize(node.cons);
49
+ if (node.alt) node.alt = optimize(node.alt);
50
+ if (node.test.type === "Bool") {
51
+ if (node.test.value === true) return node.cons;
52
+ if (node.test.value === false) return node.alt || { type: "Nil" };
53
+ }
54
+ return node;
55
+ }
56
+
57
+ case "ForStmt": {
58
+ if (node.init) node.init = optimize(node.init);
59
+ node.test = optimize(node.test);
60
+ if (node.update) node.update = optimize(node.update);
61
+ node.body = optimize(node.body);
62
+ return node;
63
+ }
64
+
65
+ case "WhileStmt": {
66
+ node.test = optimize(node.test);
67
+ node.body = optimize(node.body);
68
+ return node;
69
+ }
70
+
71
+ case "FunctionDecl": {
72
+ node.body = optimize(node.body);
73
+ return node;
74
+ }
75
+
76
+ case "ReturnStmt": {
77
+ if (node.arg) node.arg = optimize(node.arg);
78
+ return node;
79
+ }
80
+
81
+ case "Ternary": {
82
+ node.test = optimize(node.test);
83
+ node.cons = optimize(node.cons);
84
+ node.alt = optimize(node.alt);
85
+ if (node.test.type === "Bool") {
86
+ return node.test.value ? node.cons : node.alt;
87
+ }
88
+ return node;
89
+ }
90
+
91
+ case "Call": {
92
+ node.callee = optimize(node.callee);
93
+ node.args = node.args.map(a => optimize(a));
94
+ return node;
95
+ }
96
+
97
+ case "Unary": {
98
+ node.arg = optimize(node.arg);
99
+ if (node.arg.type === "Num" && node.op === "-") {
100
+ return { type: "Num", value: -node.arg.value };
101
+ }
102
+ if (node.arg.type === "Bool" && node.op === "!") {
103
+ return { type: "Bool", value: !node.arg.value };
104
+ }
105
+ return node;
106
+ }
107
+
108
+ case "Binary": {
109
+ node.left = optimize(node.left);
110
+ node.right = optimize(node.right);
111
+
112
+ const isBothNum = node.left.type === "Num" && node.right.type === "Num";
113
+ const isBothBool = node.left.type === "Bool" && node.right.type === "Bool";
114
+
115
+ if (isBothNum) {
116
+ switch (node.op) {
117
+ case "+": return { type: "Num", value: node.left.value + node.right.value };
118
+ case "-": return { type: "Num", value: node.left.value - node.right.value };
119
+ case "*": return { type: "Num", value: node.left.value * node.right.value };
120
+ case "/": return { type: "Num", value: node.left.value / node.right.value };
121
+ case "%": return { type: "Num", value: node.left.value % node.right.value };
122
+ case "==": return { type: "Bool", value: node.left.value === node.right.value };
123
+ case "!=": return { type: "Bool", value: node.left.value !== node.right.value };
124
+ case ">": return { type: "Bool", value: node.left.value > node.right.value };
125
+ case "<": return { type: "Bool", value: node.left.value < node.right.value };
126
+ case ">=": return { type: "Bool", value: node.left.value >= node.right.value };
127
+ case "<=": return { type: "Bool", value: node.left.value <= node.right.value };
128
+ }
129
+ }
130
+
131
+ if (isBothBool) {
132
+ switch (node.op) {
133
+ case "==": return { type: "Bool", value: node.left.value === node.right.value };
134
+ case "!=": return { type: "Bool", value: node.left.value !== node.right.value };
135
+ case "&&": return { type: "Bool", value: node.left.value && node.right.value };
136
+ case "||": return { type: "Bool", value: node.left.value || node.right.value };
137
+ }
138
+ }
139
+
140
+ if (node.op === "+" && node.right.type === "Num" && node.right.value === 0) return node.left;
141
+ if (node.op === "+" && node.left.type === "Num" && node.left.value === 0) return node.right;
142
+ if (node.op === "*" && node.right.type === "Num" && node.right.value === 1) return node.left;
143
+ if (node.op === "*" && node.left.type === "Num" && node.left.value === 1) return node.right;
144
+ if (node.op === "*" && (node.right.type === "Num" && node.right.value === 0)) return { type: "Num", value: 0 };
145
+ if (node.op === "*" && (node.left.type === "Num" && node.left.value === 0)) return { type: "Num", value: 0 };
146
+
147
+ return node;
148
+ }
149
+
150
+ case "Member":
151
+ case "IndexExpr": {
152
+ node.obj = optimize(node.obj);
153
+ if (node.index) node.index = optimize(node.index);
154
+ return node;
155
+ }
156
+
157
+ case "ArrayExpr": {
158
+ node.items = node.items.map(i => optimize(i));
159
+ return node;
160
+ }
161
+
162
+ case "ObjectExpr": {
163
+ node.props = node.props.map(p => ({ key: p.key, value: optimize(p.value) }));
164
+ return node;
165
+ }
166
+
167
+ case "TryCatchStmt": {
168
+ node.tryBlock = optimize(node.tryBlock);
169
+ node.catchBlock = optimize(node.catchBlock);
170
+ return node;
171
+ }
172
+
173
+ default: return node;
174
+ }
175
+ }