novac 1.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/LICENSE +0 -0
- package/README.md +0 -0
- package/bin/nv+ +84 -0
- package/cli.js +97 -0
- package/package.json +24 -0
- package/scripts/update-bin.js +24 -0
- package/src/core/bstd.js +14 -0
- package/src/core/describe.js +187 -0
- package/src/core/emitter.js +499 -0
- package/src/core/environment.js +0 -0
- package/src/core/error.js +86 -0
- package/src/core/executor.js +1005 -0
- package/src/core/lexer.js +506 -0
- package/src/core/parser.js +762 -0
- package/src/core/types.js +133 -0
- package/src/index.js +0 -0
- package/src/runtime/stdlib.js +3 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
const { Parser } = require("./parser.js");
|
|
2
|
+
const { CustomError, formatError } = require("./error.js");
|
|
3
|
+
|
|
4
|
+
class Emitter {
|
|
5
|
+
constructor(source) {
|
|
6
|
+
this.source = source;
|
|
7
|
+
this.parser = new Parser(source);
|
|
8
|
+
this.ast = this.parser.parse();
|
|
9
|
+
this.output = []; // Array of code segments
|
|
10
|
+
this.indent = 0;
|
|
11
|
+
this.rawsrc = source;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Main entry point to compile the AST to JS code.
|
|
16
|
+
* @returns {string} The transpiled JavaScript code.
|
|
17
|
+
*/
|
|
18
|
+
emit() {
|
|
19
|
+
this.emitProgram(this.ast);
|
|
20
|
+
return this.output.join("");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// --- Utility Methods ---
|
|
24
|
+
|
|
25
|
+
indentUp() {
|
|
26
|
+
this.indent++;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
indentDown() {
|
|
30
|
+
this.indent--;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
line(code = "") {
|
|
34
|
+
this.output.push("\n");
|
|
35
|
+
this.output.push(" ".repeat(this.indent));
|
|
36
|
+
this.output.push(code);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// --- AST Node Emitters ---
|
|
40
|
+
|
|
41
|
+
emitProgram(node) {
|
|
42
|
+
this.line("// Transpiled from Nova Language");
|
|
43
|
+
|
|
44
|
+
// Wrap the entire transpiled code in an async IIFE
|
|
45
|
+
// to simulate a simple execution environment.
|
|
46
|
+
this.line("(async function() {");
|
|
47
|
+
this.indentUp();
|
|
48
|
+
|
|
49
|
+
for (const statement of node.nodes) {
|
|
50
|
+
this.emitStatement(statement);
|
|
51
|
+
// Ensure there's a semicolon after every statement (if it wasn't added by the statement emitter)
|
|
52
|
+
if (this.output.at(-1) !== ";") this.output.push(";");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this.indentDown();
|
|
56
|
+
this.line("})();");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
emitStatement(node) {
|
|
60
|
+
if (!node) return;
|
|
61
|
+
|
|
62
|
+
switch (node.kind) {
|
|
63
|
+
case "declare":
|
|
64
|
+
this.emitDeclaration(node);
|
|
65
|
+
break;
|
|
66
|
+
case "branch":
|
|
67
|
+
this.emitBranch(node);
|
|
68
|
+
break;
|
|
69
|
+
case "function":
|
|
70
|
+
this.emitFunctionDeclaration(node);
|
|
71
|
+
break;
|
|
72
|
+
case "class":
|
|
73
|
+
this.emitClassDeclaration(node);
|
|
74
|
+
break;
|
|
75
|
+
case "return":
|
|
76
|
+
this.emitReturn(node);
|
|
77
|
+
break;
|
|
78
|
+
case "throw":
|
|
79
|
+
this.emitThrow(node);
|
|
80
|
+
break;
|
|
81
|
+
case "try":
|
|
82
|
+
this.emitTry(node);
|
|
83
|
+
break;
|
|
84
|
+
case "exec":
|
|
85
|
+
this.emitExecution(node);
|
|
86
|
+
break;
|
|
87
|
+
default:
|
|
88
|
+
this.error(`Unknown statement kind: ${node.kind}`, node);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
emitExpression(node) {
|
|
93
|
+
if (!node) return;
|
|
94
|
+
|
|
95
|
+
switch (node.kind) {
|
|
96
|
+
case "value":
|
|
97
|
+
this.emitValue(node);
|
|
98
|
+
break;
|
|
99
|
+
case "ref":
|
|
100
|
+
this.output.push(node.name);
|
|
101
|
+
break;
|
|
102
|
+
case "array":
|
|
103
|
+
this.emitArray(node);
|
|
104
|
+
break;
|
|
105
|
+
case "object":
|
|
106
|
+
this.emitObject(node);
|
|
107
|
+
break;
|
|
108
|
+
case "binary":
|
|
109
|
+
this.emitBinary(node);
|
|
110
|
+
break;
|
|
111
|
+
case "unary":
|
|
112
|
+
this.emitUnary(node);
|
|
113
|
+
break;
|
|
114
|
+
case "call":
|
|
115
|
+
this.emitCall(node);
|
|
116
|
+
break;
|
|
117
|
+
case "prop":
|
|
118
|
+
this.emitPropertyAccess(node);
|
|
119
|
+
break;
|
|
120
|
+
case "subscript":
|
|
121
|
+
this.emitSubscript(node);
|
|
122
|
+
break;
|
|
123
|
+
case "assign":
|
|
124
|
+
this.emitAssignment(node);
|
|
125
|
+
break;
|
|
126
|
+
case "postfix":
|
|
127
|
+
this.emitPostfix(node);
|
|
128
|
+
break;
|
|
129
|
+
case "arrowfunc":
|
|
130
|
+
this.emitArrowFunction(node);
|
|
131
|
+
break;
|
|
132
|
+
case "template":
|
|
133
|
+
this.emitTemplateLiteral(node);
|
|
134
|
+
break;
|
|
135
|
+
case "await":
|
|
136
|
+
this.output.push("await ");
|
|
137
|
+
this.emitExpression(node.operand);
|
|
138
|
+
break;
|
|
139
|
+
default:
|
|
140
|
+
this.error(`Unknown expression kind: ${node.kind}`, node);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// --- Specific Statement Emitters ---
|
|
145
|
+
|
|
146
|
+
emitDeclaration(node) {
|
|
147
|
+
this.line(`${node.isConst ? "const" : "let"} `);
|
|
148
|
+
|
|
149
|
+
// Handle destructuring
|
|
150
|
+
if (node.destructure) {
|
|
151
|
+
if (node.destructure.kind === "objpattern") {
|
|
152
|
+
this.output.push("{");
|
|
153
|
+
node.destructure.props.forEach(({ key, alias }, i) => {
|
|
154
|
+
this.output.push(key);
|
|
155
|
+
if (key !== alias) this.output.push(`: ${alias}`);
|
|
156
|
+
if (i < node.destructure.props.length - 1) this.output.push(", ");
|
|
157
|
+
});
|
|
158
|
+
this.output.push("}");
|
|
159
|
+
} else if (node.destructure.kind === "arrpattern") {
|
|
160
|
+
this.output.push("[");
|
|
161
|
+
node.destructure.elements.forEach((name, i) => {
|
|
162
|
+
this.output.push(name);
|
|
163
|
+
if (i < node.destructure.elements.length - 1) this.output.push(", ");
|
|
164
|
+
});
|
|
165
|
+
this.output.push("]");
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
// Normal declaration
|
|
169
|
+
this.output.push(node.name);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (node.value) {
|
|
173
|
+
this.output.push(" = ");
|
|
174
|
+
this.emitExpression(node.value);
|
|
175
|
+
}
|
|
176
|
+
this.output.push(";");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
emitBranch(node) {
|
|
180
|
+
let current = node;
|
|
181
|
+
|
|
182
|
+
while (current) {
|
|
183
|
+
this.line(); // New line for the block structure
|
|
184
|
+
|
|
185
|
+
switch (current.type) {
|
|
186
|
+
case "if":
|
|
187
|
+
case "unless": // In Nova, 'unless' is a branch, but in JS it's '!condition'
|
|
188
|
+
this.output.push(current.type === "if" ? "if (" : "if (!(");
|
|
189
|
+
this.emitExpression(current.args);
|
|
190
|
+
this.output.push(current.type === "if" ? ") " : ")) ");
|
|
191
|
+
this.emitBlock(current.body);
|
|
192
|
+
break;
|
|
193
|
+
case "else":
|
|
194
|
+
this.output.push("else ");
|
|
195
|
+
this.emitBlock(current.body);
|
|
196
|
+
break;
|
|
197
|
+
case "while":
|
|
198
|
+
case "until": // In Nova, 'until' is a loop, in JS it's 'while (!condition)'
|
|
199
|
+
this.output.push(current.type === "while" ? "while (" : "while (!(");
|
|
200
|
+
this.emitExpression(current.args[0]);
|
|
201
|
+
this.output.push(current.type === "while" ? ") " : ")) ");
|
|
202
|
+
this.emitBlock(current.body);
|
|
203
|
+
break;
|
|
204
|
+
case "do":
|
|
205
|
+
this.output.push("do ");
|
|
206
|
+
this.emitBlock(current.body);
|
|
207
|
+
this.line(`while (`);
|
|
208
|
+
this.emitExpression(current.args[0]);
|
|
209
|
+
this.output.push(");");
|
|
210
|
+
break;
|
|
211
|
+
case "repeat":
|
|
212
|
+
// Transpile 'repeat (N) { ... }' to a 'for' loop
|
|
213
|
+
const countName = `_i_${node.line}_${node.column}`;
|
|
214
|
+
this.output.push(`for (let ${countName} = 0; ${countName} < `);
|
|
215
|
+
this.emitExpression(current.args[0]);
|
|
216
|
+
this.output.push(`; ${countName}++) `);
|
|
217
|
+
this.emitBlock(current.body);
|
|
218
|
+
break;
|
|
219
|
+
case "for":
|
|
220
|
+
// for loops: args = [init, condition, update]
|
|
221
|
+
const [initNode, condNode, updateNode] = current.args;
|
|
222
|
+
this.output.push("for (");
|
|
223
|
+
this.emitStatement(initNode); // Emits with 'let/const' and ';'
|
|
224
|
+
this.output.push(" ");
|
|
225
|
+
|
|
226
|
+
if (condNode) this.emitExpression(condNode); // Condition
|
|
227
|
+
this.output.push("; ");
|
|
228
|
+
|
|
229
|
+
// Update statement: We only want the expression part, no 'let/var' or ';'
|
|
230
|
+
if (updateNode) {
|
|
231
|
+
const temp = this.output;
|
|
232
|
+
this.output = [];
|
|
233
|
+
this.emitStatement(updateNode);
|
|
234
|
+
const updateExpr = this.output.join("").replace(/;$/, "").trim();
|
|
235
|
+
this.output = temp;
|
|
236
|
+
this.output.push(updateExpr);
|
|
237
|
+
}
|
|
238
|
+
this.output.push(") ");
|
|
239
|
+
this.emitBlock(current.body);
|
|
240
|
+
break;
|
|
241
|
+
default:
|
|
242
|
+
this.error(`Unknown branch type '${current.type}'`, node);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Handle chaining (e.g., else if, else)
|
|
246
|
+
if (current.next && current.next.type !== "else") {
|
|
247
|
+
this.output.push(" else ");
|
|
248
|
+
}
|
|
249
|
+
current = current.next;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
emitBlock(body) {
|
|
254
|
+
this.output.push("{");
|
|
255
|
+
this.indentUp();
|
|
256
|
+
for (const stmt of body) {
|
|
257
|
+
this.emitStatement(stmt);
|
|
258
|
+
if (this.output.at(-1) !== ";") this.output.push(";");
|
|
259
|
+
}
|
|
260
|
+
this.indentDown();
|
|
261
|
+
this.line("}");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
emitFunctionDeclaration(node) {
|
|
265
|
+
// If it's an 'async' function, add the keyword.
|
|
266
|
+
this.line(
|
|
267
|
+
`${node.isAsync ? "async " : ""}function ${node.name}(${node.args.join(", ")}) `,
|
|
268
|
+
);
|
|
269
|
+
this.emitBlock(node.body);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
emitClassDeclaration(node) {
|
|
273
|
+
this.line(`class ${node.name}`);
|
|
274
|
+
|
|
275
|
+
if (node.superClass) {
|
|
276
|
+
this.output.push(" extends ");
|
|
277
|
+
this.emitExpression(node.superClass); // superClass is a 'ref' node
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
this.output.push(" {");
|
|
281
|
+
this.indentUp();
|
|
282
|
+
|
|
283
|
+
// Find constructor (if defined as a method)
|
|
284
|
+
const constructorMethod = node.members.find(
|
|
285
|
+
(m) => m.name === "constructor",
|
|
286
|
+
);
|
|
287
|
+
if (constructorMethod) {
|
|
288
|
+
this.line(`constructor(${constructorMethod.args.join(", ")}) {`);
|
|
289
|
+
this.indentUp();
|
|
290
|
+
|
|
291
|
+
// Handle 'super' call if extending
|
|
292
|
+
if (node.superClass) {
|
|
293
|
+
this.line("super(...arguments);");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Emit constructor body
|
|
297
|
+
for (const stmt of constructorMethod.body) {
|
|
298
|
+
this.emitStatement(stmt);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
this.indentDown();
|
|
302
|
+
this.line("}");
|
|
303
|
+
} else if (node.superClass) {
|
|
304
|
+
// Nova requires an explicit constructor for 'super' (or implicit no-arg call in JS)
|
|
305
|
+
// For simplicity in transpilation, we add a base constructor if extending.
|
|
306
|
+
this.line(`constructor(...args) { super(...args); }`);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Emit all other members
|
|
310
|
+
for (const member of node.members) {
|
|
311
|
+
if (member.name === "constructor") continue; // Skip if handled above
|
|
312
|
+
|
|
313
|
+
if (member.kind === "function") {
|
|
314
|
+
this.line(` ${member.name}(${member.args.join(", ")}) {`);
|
|
315
|
+
this.indentUp();
|
|
316
|
+
for (const stmt of member.body) {
|
|
317
|
+
this.emitStatement(stmt);
|
|
318
|
+
}
|
|
319
|
+
this.indentDown();
|
|
320
|
+
this.line(" }");
|
|
321
|
+
} else if (member.kind === "declare") {
|
|
322
|
+
// Simple property declaration (will be initialized in the constructor for full ES5/ES6 compatibility)
|
|
323
|
+
// For ES2022+ property support, you could use: this.line(`${member.name} = `); this.emitExpression(member.value); this.output.push(';');
|
|
324
|
+
// For simplicity, we'll assume the executor handles field initialization. For a pure JS target, we emit nothing here.
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
this.indentDown();
|
|
329
|
+
this.line("}");
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
emitReturn(node) {
|
|
333
|
+
this.line(node.terminate ? "return " : "return ");
|
|
334
|
+
if (node.value) this.emitExpression(node.value);
|
|
335
|
+
this.output.push(";");
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
emitThrow(node) {
|
|
339
|
+
this.line("throw ");
|
|
340
|
+
this.emitExpression(node.value);
|
|
341
|
+
this.output.push(";");
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
emitTry(node) {
|
|
345
|
+
this.line("try ");
|
|
346
|
+
this.emitBlock(node.tryBody);
|
|
347
|
+
|
|
348
|
+
if (node.catchBody) {
|
|
349
|
+
this.line(`catch (${node.catchName}) `);
|
|
350
|
+
this.emitBlock(node.catchBody);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (node.finallyBody) {
|
|
354
|
+
this.line("finally ");
|
|
355
|
+
this.emitBlock(node.finallyBody);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
emitExecution(node) {
|
|
360
|
+
this.line();
|
|
361
|
+
this.emitExpression(node.expr);
|
|
362
|
+
this.output.push(";");
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// --- Specific Expression Emitters ---
|
|
366
|
+
|
|
367
|
+
emitValue(node) {
|
|
368
|
+
const val = node.value;
|
|
369
|
+
switch (typeof val) {
|
|
370
|
+
case "number":
|
|
371
|
+
this.output.push(val);
|
|
372
|
+
break;
|
|
373
|
+
case "string":
|
|
374
|
+
this.output.push(`"${val}"`);
|
|
375
|
+
break;
|
|
376
|
+
case "symbol":
|
|
377
|
+
// Map Nova literals to JS literals
|
|
378
|
+
if (val.toString() === "Symbol(NOVA_TRUE)") this.output.push("true");
|
|
379
|
+
else if (val.toString() === "Symbol(NOVA_FALSE)")
|
|
380
|
+
this.output.push("false");
|
|
381
|
+
else if (val.toString() === "Symbol(NOVA_NULL)")
|
|
382
|
+
this.output.push("null");
|
|
383
|
+
else this.error(`Unknown literal symbol: ${val.toString()}`, node);
|
|
384
|
+
break;
|
|
385
|
+
default:
|
|
386
|
+
this.error(`Unknown value type: ${typeof val}`, node);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
emitTemplateLiteral(node) {
|
|
391
|
+
// Use standard JS template literal syntax: backticks and ${} for expressions
|
|
392
|
+
this.output.push("`");
|
|
393
|
+
for (const part of node.parts) {
|
|
394
|
+
if (part.kind === "value" && typeof part.value === "string") {
|
|
395
|
+
this.output.push(part.value);
|
|
396
|
+
} else {
|
|
397
|
+
this.output.push("${");
|
|
398
|
+
this.emitExpression(part);
|
|
399
|
+
this.output.push("}");
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
this.output.push("`");
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
emitArray(node) {
|
|
406
|
+
this.output.push("[");
|
|
407
|
+
node.elements.forEach((el, i) => {
|
|
408
|
+
this.emitExpression(el);
|
|
409
|
+
if (i < node.elements.length - 1) this.output.push(", ");
|
|
410
|
+
});
|
|
411
|
+
this.output.push("]");
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
emitObject(node) {
|
|
415
|
+
this.output.push("{");
|
|
416
|
+
const props = Object.entries(node.props);
|
|
417
|
+
props.forEach(([key, value], i) => {
|
|
418
|
+
this.output.push(`${key}: `);
|
|
419
|
+
this.emitExpression(value);
|
|
420
|
+
if (i < props.length - 1) this.output.push(", ");
|
|
421
|
+
});
|
|
422
|
+
this.output.push("}");
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
emitBinary(node) {
|
|
426
|
+
this.output.push("(");
|
|
427
|
+
this.emitExpression(node.left);
|
|
428
|
+
this.output.push(` ${node.operator} `);
|
|
429
|
+
this.emitExpression(node.right);
|
|
430
|
+
this.output.push(")");
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
emitUnary(node) {
|
|
434
|
+
this.output.push(node.operator);
|
|
435
|
+
this.emitExpression(node.operand);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
emitCall(node) {
|
|
439
|
+
if (typeof node.name === "string") {
|
|
440
|
+
this.output.push(node.name);
|
|
441
|
+
} else {
|
|
442
|
+
this.emitExpression(node.name); // Handles prop/subscript calls
|
|
443
|
+
}
|
|
444
|
+
this.output.push("(");
|
|
445
|
+
node.args.forEach((arg, i) => {
|
|
446
|
+
this.emitExpression(arg);
|
|
447
|
+
if (i < node.args.length - 1) this.output.push(", ");
|
|
448
|
+
});
|
|
449
|
+
this.output.push(")");
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
emitPropertyAccess(node) {
|
|
453
|
+
this.emitExpression(node.object);
|
|
454
|
+
this.output.push(`.${node.name}`);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
emitSubscript(node) {
|
|
458
|
+
this.emitExpression(node.object);
|
|
459
|
+
this.output.push("[");
|
|
460
|
+
this.emitExpression(node.index);
|
|
461
|
+
this.output.push("]");
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
emitAssignment(node) {
|
|
465
|
+
this.emitExpression(node.name); // LHS: ref, prop, or subscript node
|
|
466
|
+
this.output.push(` = `);
|
|
467
|
+
this.emitExpression(node.value);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
emitPostfix(node) {
|
|
471
|
+
this.emitExpression(node.operand);
|
|
472
|
+
this.output.push(node.operator);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
emitArrowFunction(node) {
|
|
476
|
+
this.output.push(`(${node.args.join(", ")}) => `);
|
|
477
|
+
// If the body is a single return statement, use concise body
|
|
478
|
+
const isConcise =
|
|
479
|
+
node.body.length === 1 &&
|
|
480
|
+
node.body[0].kind === "return" &&
|
|
481
|
+
node.body[0].terminate;
|
|
482
|
+
|
|
483
|
+
if (isConcise) {
|
|
484
|
+
this.emitExpression(node.body[0].value);
|
|
485
|
+
} else {
|
|
486
|
+
this.emitBlock(node.body);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// --- Error Handling ---
|
|
491
|
+
|
|
492
|
+
error(msg, node) {
|
|
493
|
+
throw new (CustomError("EmitterError"))(
|
|
494
|
+
formatError("EmitterError", msg, node.line, node.column, this.rawsrc),
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
module.exports = { Emitter };
|
|
File without changes
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// ===== error.js =====
|
|
2
|
+
const RESERVED_WORDS = new Set([
|
|
3
|
+
"break",
|
|
4
|
+
"case",
|
|
5
|
+
"catch",
|
|
6
|
+
"class",
|
|
7
|
+
"const",
|
|
8
|
+
"continue",
|
|
9
|
+
"debugger",
|
|
10
|
+
"default",
|
|
11
|
+
"delete",
|
|
12
|
+
"do",
|
|
13
|
+
"else",
|
|
14
|
+
"export",
|
|
15
|
+
"extends",
|
|
16
|
+
"finally",
|
|
17
|
+
"for",
|
|
18
|
+
"function",
|
|
19
|
+
"if",
|
|
20
|
+
"import",
|
|
21
|
+
"in",
|
|
22
|
+
"instanceof",
|
|
23
|
+
"new",
|
|
24
|
+
"return",
|
|
25
|
+
"super",
|
|
26
|
+
"switch",
|
|
27
|
+
"this",
|
|
28
|
+
"throw",
|
|
29
|
+
"try",
|
|
30
|
+
"typeof",
|
|
31
|
+
"var",
|
|
32
|
+
"void",
|
|
33
|
+
"while",
|
|
34
|
+
"with",
|
|
35
|
+
"yield",
|
|
36
|
+
"let",
|
|
37
|
+
"enum",
|
|
38
|
+
"await",
|
|
39
|
+
"implements",
|
|
40
|
+
"package",
|
|
41
|
+
"protected",
|
|
42
|
+
"static",
|
|
43
|
+
"interface",
|
|
44
|
+
"private",
|
|
45
|
+
"public",
|
|
46
|
+
"null",
|
|
47
|
+
"true",
|
|
48
|
+
"false",
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
function sanitizeName(name) {
|
|
52
|
+
let sanitized = name.replace(/[^a-zA-Z0-9_$]/g, "");
|
|
53
|
+
if (!/^[a-zA-Z_$]/.test(sanitized)) sanitized = "_" + sanitized;
|
|
54
|
+
if (RESERVED_WORDS.has(sanitized)) sanitized += "_";
|
|
55
|
+
return sanitized || "_CustomError";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function CustomError(name) {
|
|
59
|
+
const safeName = sanitizeName(name);
|
|
60
|
+
return eval(`
|
|
61
|
+
(class ${safeName} extends Error {
|
|
62
|
+
constructor(message) {
|
|
63
|
+
super(message);
|
|
64
|
+
this.name = "${safeName}";
|
|
65
|
+
if (Error.captureStackTrace) {
|
|
66
|
+
Error.captureStackTrace(this, this.constructor);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let NovaException = CustomError("Exception");
|
|
74
|
+
|
|
75
|
+
// 🔥 new addition — shared formatter
|
|
76
|
+
function formatError(kind, msg, line, column, rawsrc) {
|
|
77
|
+
const lineText = rawsrc.split("\n")[line - 1] ?? "";
|
|
78
|
+
return (
|
|
79
|
+
`[Nova${kind} ${line}:${column}] ${msg}\n` +
|
|
80
|
+
` error at:\n` +
|
|
81
|
+
` ${lineText}\n` +
|
|
82
|
+
` ${" ".repeat(Math.max(column - 1, 0))}^^`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = { CustomError, formatError, NovaException };
|