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/docsgen.js ADDED
@@ -0,0 +1,262 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { lex } from "./lexer.js";
4
+ import { parse } from "./parser.js";
5
+
6
+ export async function generateDocs(srcDir = ".", outDir = "docs") {
7
+ const root = path.resolve(srcDir);
8
+ const output = path.resolve(outDir);
9
+
10
+ if (!fs.existsSync(output)) {
11
+ fs.mkdirSync(output, { recursive: true });
12
+ }
13
+
14
+ console.log(` Gerando documentação de ${root} → ${output}`);
15
+
16
+ const files = [];
17
+ collectXSFile(root, files);
18
+
19
+ if (files.length === 0) {
20
+ console.log(" Nenhum arquivo .xs encontrado");
21
+ return;
22
+ }
23
+
24
+ console.log(` ${files.length} arquivo(s) encontrado(s)`);
25
+
26
+ const allDocs = [];
27
+
28
+ for (const file of files) {
29
+ const relPath = path.relative(root, file);
30
+ const code = fs.readFileSync(file, "utf-8");
31
+ const doc = extractDocs(relPath, code);
32
+ if (doc) allDocs.push(doc);
33
+ }
34
+
35
+ const html = generateHTML(allDocs);
36
+ const outFile = path.join(output, "index.html");
37
+ fs.writeFileSync(outFile, html, "utf-8");
38
+
39
+ console.log(` Documentação gerada: ${outFile}`);
40
+ console.log(` Abra no navegador para ver`);
41
+ }
42
+
43
+ function collectXSFile(dir, files) {
44
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
45
+ for (const entry of entries) {
46
+ const fullPath = path.join(dir, entry.name);
47
+ if (entry.isDirectory() && entry.name !== "node_modules" && !entry.name.startsWith(".")) {
48
+ collectXSFile(fullPath, files);
49
+ } else if (entry.isFile() && entry.name.endsWith(".xs") && !entry.name.includes("node_modules")) {
50
+ files.push(fullPath);
51
+ }
52
+ }
53
+ }
54
+
55
+ function extractDocs(filePath, code) {
56
+ const lines = code.split("\n");
57
+ const functions = [];
58
+ const classes = [];
59
+ const tests = [];
60
+ const tasks = [];
61
+ const comments = [];
62
+
63
+ let currentComment = [];
64
+
65
+ for (let i = 0; i < lines.length; i++) {
66
+ const line = lines[i];
67
+ const trimmed = line.trim();
68
+
69
+ if (trimmed.startsWith("//")) {
70
+ currentComment.push(trimmed.slice(2).trim());
71
+ continue;
72
+ }
73
+
74
+ const docStr = currentComment.length > 0 ? currentComment.join("\n") : null;
75
+ currentComment = [];
76
+
77
+ if (trimmed.startsWith("CHAMA ESSE CARA ")) {
78
+ const match = trimmed.match(/CHAMA ESSE CARA (\w+)/);
79
+ if (match) {
80
+ functions.push({
81
+ name: match[1],
82
+ doc: docStr,
83
+ line: i + 1,
84
+ code: trimmed,
85
+ });
86
+ }
87
+ }
88
+
89
+ if (trimmed.startsWith("CLASSE ")) {
90
+ const match = trimmed.match(/CLASSE (\w+)/);
91
+ if (match) {
92
+ classes.push({
93
+ name: match[1],
94
+ doc: docStr,
95
+ line: i + 1,
96
+ code: trimmed,
97
+ });
98
+ }
99
+ }
100
+
101
+ if (trimmed.startsWith("TAREFA ")) {
102
+ const match = trimmed.match(/TAREFA (\w+)/);
103
+ if (match) {
104
+ tasks.push({
105
+ name: match[1],
106
+ doc: docStr,
107
+ line: i + 1,
108
+ code: trimmed,
109
+ });
110
+ }
111
+ }
112
+
113
+ if (trimmed.startsWith("TESTE ")) {
114
+ const match = trimmed.match(/TESTE "([^"]+)"/);
115
+ if (match) {
116
+ tests.push({
117
+ name: match[1],
118
+ doc: docStr,
119
+ line: i + 1,
120
+ code: trimmed,
121
+ });
122
+ }
123
+ }
124
+
125
+ if (docStr && !trimmed.match(/CHAMA ESSE CARA|CLASSE|TAREFA|TESTE/)) {
126
+ comments.push({ doc: docStr, line: i + 1 });
127
+ }
128
+ }
129
+
130
+ if (functions.length === 0 && classes.length === 0 && tests.length === 0 && tasks.length === 0) {
131
+ return null;
132
+ }
133
+
134
+ return {
135
+ file: filePath,
136
+ functions,
137
+ classes,
138
+ tests,
139
+ tasks,
140
+ comments,
141
+ };
142
+ }
143
+
144
+ function generateHTML(allDocs) {
145
+ let items = "";
146
+ let navItems = "";
147
+
148
+ for (const doc of allDocs) {
149
+ const fileId = doc.file.replace(/[^a-zA-Z0-9]/g, "-");
150
+ navItems += `<a href="#${fileId}">${doc.file}</a>\n`;
151
+
152
+ let funcs = "";
153
+ for (const f of doc.functions) {
154
+ funcs += `<div class="item">
155
+ <div class="item-header">
156
+ <span class="tag fn">function</span>
157
+ <code>${f.name}</code>
158
+ <span class="line">linha ${f.line}</span>
159
+ </div>
160
+ ${f.doc ? `<p class="doc">${escapeHtml(f.doc)}</p>` : ""}
161
+ <pre><code>${escapeHtml(f.code)}</code></pre>
162
+ </div>\n`;
163
+ }
164
+
165
+ let classes = "";
166
+ for (const c of doc.classes) {
167
+ classes += `<div class="item">
168
+ <div class="item-header">
169
+ <span class="tag cls">class</span>
170
+ <code>${c.name}</code>
171
+ <span class="line">linha ${c.line}</span>
172
+ </div>
173
+ ${c.doc ? `<p class="doc">${escapeHtml(c.doc)}</p>` : ""}
174
+ <pre><code>${escapeHtml(c.code)}</code></pre>
175
+ </div>\n`;
176
+ }
177
+
178
+ let tests = "";
179
+ for (const t of doc.tests) {
180
+ tests += `<div class="item">
181
+ <div class="item-header">
182
+ <span class="tag test">test</span>
183
+ <code>${escapeHtml(t.name)}</code>
184
+ <span class="line">linha ${t.line}</span>
185
+ </div>
186
+ </div>\n`;
187
+ }
188
+
189
+ let tasks = "";
190
+ for (const t of doc.tasks) {
191
+ tasks += `<div class="item">
192
+ <div class="item-header">
193
+ <span class="tag task">task</span>
194
+ <code>${t.name}</code>
195
+ <span class="line">linha ${t.line}</span>
196
+ </div>
197
+ ${t.doc ? `<p class="doc">${escapeHtml(t.doc)}</p>` : ""}
198
+ </div>\n`;
199
+ }
200
+
201
+ items += `<div class="file" id="${fileId}">
202
+ <h2> ${doc.file}</h2>
203
+ ${funcs ? `<h3>Funções</h3>${funcs}` : ""}
204
+ ${classes ? `<h3>Classes</h3>${classes}` : ""}
205
+ ${tests ? `<h3>Testes</h3>${tests}` : ""}
206
+ ${tasks ? `<h3>Tarefas</h3>${tasks}` : ""}
207
+ </div>\n`;
208
+ }
209
+
210
+ return `<!DOCTYPE html>
211
+ <html lang="pt-br">
212
+ <head>
213
+ <meta charset="UTF-8">
214
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
215
+ <title>XanaScript Docs</title>
216
+ <style>
217
+ * { margin: 0; padding: 0; box-sizing: border-box; }
218
+ body {
219
+ font-family: 'Inter', -apple-system, sans-serif;
220
+ background: #0d1117; color: #c9d1d9;
221
+ display: flex; min-height: 100vh;
222
+ }
223
+ nav {
224
+ width: 260px; padding: 24px; background: #161b22;
225
+ border-right: 1px solid #30363d; position: sticky; top: 0; height: 100vh; overflow-y: auto;
226
+ }
227
+ nav h1 { font-size: 18px; margin-bottom: 16px; }
228
+ nav a { display: block; color: #8b949e; text-decoration: none; font-size: 13px; padding: 4px 0; }
229
+ nav a:hover { color: #58a6ff; }
230
+ .content { flex: 1; padding: 32px; max-width: 900px; }
231
+ .file { margin-bottom: 48px; }
232
+ .file h2 { font-size: 20px; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 1px solid #30363d; }
233
+ h3 { font-size: 16px; margin: 24px 0 12px; color: #8b949e; }
234
+ .item { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; margin-bottom: 12px; }
235
+ .item-header { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
236
+ .tag { font-size: 11px; padding: 2px 8px; border-radius: 4px; font-weight: 600; }
237
+ .tag.fn { background: #1f6feb33; color: #58a6ff; }
238
+ .tag.cls { background: #23863633; color: #3fb950; }
239
+ .tag.test { background: #9e6a0333; color: #d29922; }
240
+ .tag.task { background: #da363333; color: #f85149; }
241
+ .line { color: #484f58; font-size: 12px; margin-left: auto; }
242
+ .doc { color: #8b949e; font-size: 13px; margin-bottom: 8px; white-space: pre-wrap; }
243
+ pre { background: #0d1117; border-radius: 4px; padding: 12px; overflow-x: auto; }
244
+ code { font-family: 'JetBrains Mono', monospace; font-size: 13px; }
245
+ </style>
246
+ </head>
247
+ <body>
248
+ <nav>
249
+ <h1> XanaScript Docs</h1>
250
+ ${navItems}
251
+ </nav>
252
+ <div class="content">
253
+ ${items}
254
+ </div>
255
+ </body>
256
+ </html>`;
257
+ }
258
+
259
+ function escapeHtml(s) {
260
+ if (!s) return "";
261
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
262
+ }
package/src/errors.js ADDED
@@ -0,0 +1,162 @@
1
+ let SOURCE_LINES = [];
2
+ let SOURCE_FILE = "input.xs";
3
+ let SOURCE_CODE = "";
4
+
5
+ export function setSource(code, file) {
6
+ SOURCE_CODE = code;
7
+ SOURCE_FILE = file || "input.xs";
8
+ SOURCE_LINES = code.split("\n");
9
+ }
10
+
11
+ export class XSError extends Error {
12
+ constructor(message, options = {}) {
13
+ super(message);
14
+ this.name = "XSError";
15
+ this.loc = options.loc || null;
16
+ this.hint = options.hint || "";
17
+ this.help = options.help || "";
18
+ this.code = options.code || "";
19
+ this.severity = options.severity || "error";
20
+ }
21
+
22
+ toString() {
23
+ return formatError(this);
24
+ }
25
+ }
26
+
27
+ export function formatError(err) {
28
+ const loc = err.loc;
29
+ const lines = [];
30
+
31
+ lines.push("");
32
+ lines.push(`\x1b[1;31m╔═══ XanaScript ${err.severity.toUpperCase()} \x1b[0m`);
33
+ lines.push(`\x1b[1;31m║\x1b[0m ${err.message}`);
34
+
35
+ if (err.code) {
36
+ lines.push(`\x1b[1;31m║\x1b[0m \x1b[2mCódigo: ${err.code}\x1b[0m`);
37
+ }
38
+
39
+ if (loc && loc.line) {
40
+ const line = loc.line;
41
+ const col = loc.column || 1;
42
+ const context = 2;
43
+
44
+ const start = Math.max(0, line - context - 1);
45
+ const end = Math.min(SOURCE_LINES.length, line + context);
46
+
47
+ lines.push(`\x1b[1;31m║\x1b[0m`);
48
+ lines.push(`\x1b[1;31m║\x1b[0m \x1b[2m--> ${SOURCE_FILE}:${line}:${col}\x1b[0m`);
49
+ lines.push(`\x1b[1;31m║\x1b[0m`);
50
+
51
+ for (let i = start; i < end; i++) {
52
+ const lineNum = i + 1;
53
+ const prefix = lineNum === line ? "\x1b[1;31m║\x1b[0m" : "\x1b[2;31m║\x1b[0m";
54
+ const numStr = String(lineNum).padStart(4, " ");
55
+ const marker = lineNum === line ? "\x1b[1;31m>\x1b[0m" : " ";
56
+ const content = SOURCE_LINES[i] || "";
57
+
58
+ lines.push(`${prefix} ${marker} ${numStr} \x1b[0m│ ${content}`);
59
+
60
+ if (lineNum === line && col > 0) {
61
+ const arrow = " ".repeat(col - 1) + "\x1b[1;31m^\x1b[0m";
62
+ lines.push(`${prefix} \x1b[2m│\x1b[0m ${arrow}`);
63
+ }
64
+ }
65
+ }
66
+
67
+ if (err.hint) {
68
+ lines.push(`\x1b[1;31m║\x1b[0m`);
69
+ lines.push(`\x1b[1;33m║ ${err.hint}\x1b[0m`);
70
+ }
71
+ if (err.help) {
72
+ lines.push(`\x1b[1;34m║ ${err.help}\x1b[0m`);
73
+ }
74
+
75
+ lines.push(`\x1b[1;31m╚══════════════════════════════════\x1b[0m`);
76
+ lines.push("");
77
+
78
+ return lines.join("\n");
79
+ }
80
+
81
+ export function expected(found, expected, loc) {
82
+ return new XSError(
83
+ `Esperado \`${expected}\`, encontrado \`${found}\``,
84
+ {
85
+ loc,
86
+ hint: `XanaScript esperava "${expected}" aqui`,
87
+ help: `Tente adicionar "${expected}" neste local`,
88
+ code: "E001",
89
+ }
90
+ );
91
+ }
92
+
93
+ export function undefinedVar(name, loc) {
94
+ return new XSError(
95
+ `Variável \`${name}\` não foi definida`,
96
+ {
97
+ loc,
98
+ hint: `Você esqueceu de declarar "${name}" com CRIA?`,
99
+ help: `Adicione \`CRIA ${name} = valor\` antes de usar`,
100
+ code: "E002",
101
+ }
102
+ );
103
+ }
104
+
105
+ export function notAFunction(name, loc) {
106
+ return new XSError(
107
+ `\`${name}\` não é uma função`,
108
+ {
109
+ loc,
110
+ hint: `Você está tentando chamar "${name}" como função, mas não é`,
111
+ help: `Verifique se "${name}" foi declarada com CHAMA ESSE CARA`,
112
+ code: "E003",
113
+ }
114
+ );
115
+ }
116
+
117
+ export function typeMismatch(expected, found, loc) {
118
+ return new XSError(
119
+ `Tipo incompatível: esperado \`${expected}\`, recebeu \`${found}\``,
120
+ {
121
+ loc,
122
+ hint: `Os tipos não correspondem`,
123
+ help: `Verifique o tipo da variável ou use coerção explícita`,
124
+ code: "E004",
125
+ }
126
+ );
127
+ }
128
+
129
+ export function invalidSyntax(detail, loc) {
130
+ return new XSError(
131
+ `Sintaxe inválida: ${detail}`,
132
+ {
133
+ loc,
134
+ hint: "Verifique a sintaxe ao redor deste ponto",
135
+ code: "E005",
136
+ }
137
+ );
138
+ }
139
+
140
+ export function suggestion(msg, loc) {
141
+ return new XSError(msg, { loc, severity: "info", code: "I001" });
142
+ }
143
+
144
+ export function wrapError(fn) {
145
+ return async (...args) => {
146
+ try {
147
+ return await fn(...args);
148
+ } catch (e) {
149
+ if (e instanceof XSError) throw e;
150
+ if (e instanceof Error) {
151
+ const xsErr = new XSError(e.message, {
152
+ loc: e.loc || null,
153
+ hint: "Erro interno do interpretador",
154
+ code: "E999",
155
+ });
156
+ xsErr.stack = e.stack;
157
+ throw xsErr;
158
+ }
159
+ throw e;
160
+ }
161
+ };
162
+ }