vladx 1.0.1 → 1.1.1
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/examples/moduli/main.vx +19 -0
- package/examples/moduli/math.vx +13 -0
- package/package.json +1 -1
- package/src/cli/cli.js +1 -0
- package/src/interpreter/interpreter.js +71 -0
- package/src/parser/ast.js +26 -0
- package/src/parser/parser.js +34 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* main.vx - Applicazione che usa i moduli
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
importa { somma, sottrai, PI } da "./math.vx";
|
|
6
|
+
|
|
7
|
+
stampa("Test Moduli VladX");
|
|
8
|
+
stampa("-----------------");
|
|
9
|
+
|
|
10
|
+
variabile r1 = somma(10, 5);
|
|
11
|
+
stampa("10 + 5 = " + r1);
|
|
12
|
+
|
|
13
|
+
variabile r2 = sottrai(20, 8);
|
|
14
|
+
stampa("20 - 8 = " + r2);
|
|
15
|
+
|
|
16
|
+
stampa("Valore di PI: " + PI);
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
stampa("Fine del test.");
|
package/package.json
CHANGED
package/src/cli/cli.js
CHANGED
|
@@ -69,6 +69,7 @@ function runFile(filePath) {
|
|
|
69
69
|
const ast = parser.parse();
|
|
70
70
|
|
|
71
71
|
const interpreter = new Interpreter();
|
|
72
|
+
interpreter.currentPath = absolutePath;
|
|
72
73
|
interpreter.interpret(ast);
|
|
73
74
|
} catch (error) {
|
|
74
75
|
console.error(colorize(`✗ Errore: ${error.message}`, 'red'));
|
|
@@ -116,11 +116,19 @@ class ArrowFunc {
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
const fs = require('fs');
|
|
120
|
+
const path = require('path');
|
|
121
|
+
const { Lexer } = require('../lexer/lexer.js');
|
|
122
|
+
const { Parser } = require('../parser/parser.js');
|
|
123
|
+
|
|
119
124
|
class Interpreter {
|
|
120
125
|
constructor() {
|
|
121
126
|
this.globals = new Environment();
|
|
122
127
|
this.environment = this.globals;
|
|
123
128
|
this.output = [];
|
|
129
|
+
this.modules = new Map(); // path -> exports
|
|
130
|
+
this.currentPath = process.cwd();
|
|
131
|
+
this.exportedSymbols = new Map(); // symbols exported by the current module
|
|
124
132
|
|
|
125
133
|
// Built-in functions
|
|
126
134
|
this.globals.define('lunghezza', {
|
|
@@ -184,6 +192,10 @@ class Interpreter {
|
|
|
184
192
|
throw new BreakSignal();
|
|
185
193
|
case 'ContinueStatement':
|
|
186
194
|
throw new ContinueSignal();
|
|
195
|
+
case 'ExportNamedDeclaration':
|
|
196
|
+
return this.executeExportNamedDeclaration(node);
|
|
197
|
+
case 'ImportDeclaration':
|
|
198
|
+
return this.executeImportDeclaration(node);
|
|
187
199
|
case 'PrintStatement':
|
|
188
200
|
return this.executePrintStatement(node);
|
|
189
201
|
case 'ExpressionStatement':
|
|
@@ -261,6 +273,65 @@ class Interpreter {
|
|
|
261
273
|
}
|
|
262
274
|
}
|
|
263
275
|
|
|
276
|
+
executeExportNamedDeclaration(node) {
|
|
277
|
+
this.execute(node.declaration);
|
|
278
|
+
const name = node.declaration.name;
|
|
279
|
+
const value = this.environment.get(name);
|
|
280
|
+
this.exportedSymbols.set(name, value);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
executeImportDeclaration(node) {
|
|
284
|
+
const sourcePath = node.source;
|
|
285
|
+
let fullPath;
|
|
286
|
+
|
|
287
|
+
if (sourcePath.startsWith('./') || sourcePath.startsWith('../')) {
|
|
288
|
+
fullPath = path.resolve(path.dirname(this.currentPath), sourcePath);
|
|
289
|
+
} else {
|
|
290
|
+
// Check vladx_modules
|
|
291
|
+
fullPath = path.resolve(process.cwd(), 'vladx_modules', sourcePath);
|
|
292
|
+
if (!fullPath.endsWith('.vx')) fullPath += '.vx';
|
|
293
|
+
if (!fs.existsSync(fullPath)) {
|
|
294
|
+
// Try index.vx inside the module folder
|
|
295
|
+
const folderPath = path.resolve(process.cwd(), 'vladx_modules', sourcePath);
|
|
296
|
+
fullPath = path.join(folderPath, 'index.vx');
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (!fs.existsSync(fullPath)) {
|
|
301
|
+
throw new Error(`Modulo non trovato: ${sourcePath}`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
let moduleExports;
|
|
305
|
+
if (this.modules.has(fullPath)) {
|
|
306
|
+
moduleExports = this.modules.get(fullPath);
|
|
307
|
+
} else {
|
|
308
|
+
// Load and execute module
|
|
309
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
310
|
+
const lexer = new Lexer(content);
|
|
311
|
+
const tokens = lexer.tokenize();
|
|
312
|
+
const parser = new Parser(tokens);
|
|
313
|
+
const program = parser.parse();
|
|
314
|
+
|
|
315
|
+
const subInterpreter = new Interpreter();
|
|
316
|
+
// Sharing the same modules cache
|
|
317
|
+
subInterpreter.modules = this.modules;
|
|
318
|
+
subInterpreter.currentPath = fullPath;
|
|
319
|
+
|
|
320
|
+
subInterpreter.interpret(program);
|
|
321
|
+
moduleExports = subInterpreter.exportedSymbols;
|
|
322
|
+
this.modules.set(fullPath, moduleExports);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Import specified symbols
|
|
326
|
+
for (const specifier of node.specifiers) {
|
|
327
|
+
if (moduleExports.has(specifier)) {
|
|
328
|
+
this.environment.define(specifier, moduleExports.get(specifier));
|
|
329
|
+
} else {
|
|
330
|
+
throw new Error(`Il modulo "${sourcePath}" non esporta "${specifier}"`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
264
335
|
executePrintStatement(node) {
|
|
265
336
|
const value = this.evaluate(node.argument);
|
|
266
337
|
const output = this.stringify(value);
|
package/src/parser/ast.js
CHANGED
|
@@ -232,6 +232,29 @@ class LogicalExpression extends ASTNode {
|
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
+
class ExportNamedDeclaration {
|
|
236
|
+
constructor(declaration) {
|
|
237
|
+
this.type = 'ExportNamedDeclaration';
|
|
238
|
+
this.declaration = declaration;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
class ImportDeclaration {
|
|
243
|
+
constructor(specifiers, source) {
|
|
244
|
+
this.type = 'ImportDeclaration';
|
|
245
|
+
this.specifiers = specifiers; // Array di nomi
|
|
246
|
+
this.source = source; // StringLiteral path
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
class ImportSpecifier {
|
|
251
|
+
constructor(imported, local) {
|
|
252
|
+
this.type = 'ImportSpecifier';
|
|
253
|
+
this.imported = imported;
|
|
254
|
+
this.local = local;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
235
258
|
module.exports = {
|
|
236
259
|
ASTNode,
|
|
237
260
|
Program,
|
|
@@ -246,6 +269,9 @@ module.exports = {
|
|
|
246
269
|
ContinueStatement,
|
|
247
270
|
ExpressionStatement,
|
|
248
271
|
PrintStatement,
|
|
272
|
+
ExportNamedDeclaration,
|
|
273
|
+
ImportDeclaration,
|
|
274
|
+
ImportSpecifier,
|
|
249
275
|
Identifier,
|
|
250
276
|
NumericLiteral,
|
|
251
277
|
StringLiteral,
|
package/src/parser/parser.js
CHANGED
|
@@ -69,6 +69,8 @@ class Parser {
|
|
|
69
69
|
|
|
70
70
|
declaration() {
|
|
71
71
|
try {
|
|
72
|
+
if (this.match(TokenType.IMPORTA)) return this.importDeclaration();
|
|
73
|
+
if (this.match(TokenType.ESPORTA)) return this.exportDeclaration();
|
|
72
74
|
if (this.match(TokenType.VARIABILE)) return this.variableDeclaration(false);
|
|
73
75
|
if (this.match(TokenType.COSTANTE)) return this.variableDeclaration(true);
|
|
74
76
|
if (this.match(TokenType.FUNZIONE)) return this.functionDeclaration();
|
|
@@ -89,6 +91,38 @@ class Parser {
|
|
|
89
91
|
return new AST.VariableDeclaration(name, value, isConstant);
|
|
90
92
|
}
|
|
91
93
|
|
|
94
|
+
importDeclaration() {
|
|
95
|
+
this.consume(TokenType.GRAFFA_APERTA, 'Atteso "{" dopo "importa"');
|
|
96
|
+
const specifiers = [];
|
|
97
|
+
if (!this.check(TokenType.GRAFFA_CHIUSA)) {
|
|
98
|
+
do {
|
|
99
|
+
const name = this.consume(TokenType.IDENTIFICATORE, 'Nome import atteso').value;
|
|
100
|
+
specifiers.push(name);
|
|
101
|
+
} while (this.match(TokenType.VIRGOLA));
|
|
102
|
+
}
|
|
103
|
+
this.consume(TokenType.GRAFFA_CHIUSA, 'Atteso "}" dopo i nomi importati');
|
|
104
|
+
this.consume(TokenType.DA, 'Attesa parola chiave "da" dopo gli import');
|
|
105
|
+
const source = this.consume(TokenType.STRINGA, 'Percorso modulo atteso come stringa').value;
|
|
106
|
+
this.match(TokenType.PUNTO_VIRGOLA);
|
|
107
|
+
return new AST.ImportDeclaration(specifiers, source);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
exportDeclaration() {
|
|
111
|
+
if (this.match(TokenType.FUNZIONE)) {
|
|
112
|
+
const decl = this.functionDeclaration();
|
|
113
|
+
return new AST.ExportNamedDeclaration(decl);
|
|
114
|
+
}
|
|
115
|
+
if (this.match(TokenType.VARIABILE)) {
|
|
116
|
+
const decl = this.variableDeclaration(false);
|
|
117
|
+
return new AST.ExportNamedDeclaration(decl);
|
|
118
|
+
}
|
|
119
|
+
if (this.match(TokenType.COSTANTE)) {
|
|
120
|
+
const decl = this.variableDeclaration(true);
|
|
121
|
+
return new AST.ExportNamedDeclaration(decl);
|
|
122
|
+
}
|
|
123
|
+
throw new ParserError('Atteso funzione o variabile dopo "esporta"', this.peek());
|
|
124
|
+
}
|
|
125
|
+
|
|
92
126
|
functionDeclaration() {
|
|
93
127
|
const name = this.consume(TokenType.IDENTIFICATORE, 'Nome funzione atteso').value;
|
|
94
128
|
this.consume(TokenType.PARENTESI_APERTA, 'Atteso "(" dopo nome funzione');
|