starlight-cli 1.0.25 → 1.0.27

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/evaluator.js CHANGED
@@ -4,390 +4,484 @@ const Lexer = require('./lexer');
4
4
  const Parser = require('./parser');
5
5
  const path = require('path');
6
6
 
7
- class ReturnValue { constructor(value) { this.value = value; } }
7
+ class ReturnValue {
8
+ constructor(value) { this.value = value; }
9
+ }
8
10
  class BreakSignal {}
9
11
  class ContinueSignal {}
10
12
 
11
13
  class Environment {
12
- constructor(parent = null) {
13
- this.store = Object.create(null);
14
- this.parent = parent;
15
- }
14
+ constructor(parent = null) {
15
+ this.store = Object.create(null);
16
+ this.parent = parent;
17
+ }
18
+
19
+ has(name) {
20
+ if (name in this.store) return true;
21
+ if (this.parent) return this.parent.has(name);
22
+ return false;
23
+ }
24
+
25
+ get(name) {
26
+ if (name in this.store) return this.store[name];
27
+ if (this.parent) return this.parent.get(name);
28
+ throw new Error(`Undefined variable: ${name}`);
29
+ }
30
+
31
+ set(name, value) {
32
+ if (name in this.store) { this.store[name] = value; return value; }
33
+ if (this.parent && this.parent.has(name)) { return this.parent.set(name, value); }
34
+ this.store[name] = value;
35
+ return value;
36
+ }
37
+
38
+ define(name, value) {
39
+ this.store[name] = value;
40
+ return value;
41
+ }
42
+ }
16
43
 
17
- has(name) {
18
- if (name in this.store) return true;
19
- if (this.parent) return this.parent.has(name);
20
- return false;
21
- }
44
+ class Evaluator {
45
+ constructor() {
46
+ this.global = new Environment();
47
+ this.setupBuiltins();
48
+ }
49
+
50
+ setupBuiltins() {
51
+ this.global.define('len', arg => {
52
+ if (Array.isArray(arg) || typeof arg === 'string') return arg.length;
53
+ if (arg && typeof arg === 'object') return Object.keys(arg).length;
54
+ return 0;
55
+ });
56
+
57
+ this.global.define('print', arg => { console.log(arg); return null; });
58
+ this.global.define('type', arg => {
59
+ if (Array.isArray(arg)) return 'array';
60
+ return typeof arg;
61
+ });
62
+ this.global.define('keys', arg => arg && typeof arg === 'object' ? Object.keys(arg) : []);
63
+ this.global.define('values', arg => arg && typeof arg === 'object' ? Object.values(arg) : []);
64
+
65
+ this.global.define('ask', prompt => {
66
+ const readlineSync = require('readline-sync');
67
+ return readlineSync.question(prompt + ' ');
68
+ });
69
+ this.global.define('num', arg => {
70
+ const n = Number(arg);
71
+ if (Number.isNaN(n)) {
72
+ throw new Error('Cannot convert value to number');
73
+ }
74
+ return n;
75
+ });
76
+ this.global.define('fetch', async (url, options = {}) => {
77
+ const res = await fetch(url, options);
78
+ return {
79
+ status: res.status,
80
+ ok: res.ok,
81
+ text: async () => await res.text(),
82
+ json: async () => await res.json()
83
+ };
84
+ });
85
+
86
+ // GET request
87
+ this.global.define('get', async (url) => {
88
+ const res = await fetch(url);
89
+ return await res.json();
90
+ });
91
+
92
+ this.global.define('post', async (url, data) => {
93
+ const res = await fetch(url, {
94
+ method: 'POST',
95
+ headers: { 'Content-Type': 'application/json' },
96
+ body: JSON.stringify(data)
97
+ });
98
+ return await res.json();
99
+ });
100
+
101
+ this.global.define('sleep', async (ms) => {
102
+ return new Promise(resolve => setTimeout(resolve, ms));
103
+ });
104
+ this.global.define('str', arg => {
105
+ return String(arg);
106
+ });
22
107
 
23
- get(name) {
24
- if (name in this.store) return this.store[name];
25
- if (this.parent) return this.parent.get(name);
26
- throw new Error(`Undefined variable: ${name}`);
27
- }
108
+ }
28
109
 
29
- set(name, value) {
30
- if (name in this.store) { this.store[name] = value; return value; }
31
- if (this.parent && this.parent.has(name)) { return this.parent.set(name, value); }
32
- this.store[name] = value;
33
- return value;
34
- }
110
+ async evaluate(node, env = this.global) {
111
+ switch (node.type) {
112
+ case 'Program': return await this.evalProgram(node, env);
113
+ case 'BlockStatement': return await this.evalBlock(node, new Environment(env));
114
+ case 'VarDeclaration': return await this.evalVarDeclaration(node, env);
115
+ case 'AssignmentExpression': return await this.evalAssignment(node, env);
116
+ case 'CompoundAssignment': return await this.evalCompoundAssignment(node, env);
117
+ case 'SldeployStatement': return await this.evalSldeploy(node, env);
118
+ case 'AskStatement': return await this.evalAsk(node, env);
119
+ case 'DefineStatement': return await this.evalDefine(node, env);
120
+ case 'ExpressionStatement': return await this.evaluate(node.expression, env);
121
+ case 'BinaryExpression': return await this.evalBinary(node, env);
122
+ case 'LogicalExpression': return await this.evalLogical(node, env);
123
+ case 'UnaryExpression': return await this.evalUnary(node, env);
124
+ case 'Literal': return node.value;
125
+ case 'Identifier': return env.get(node.name);
126
+ case 'IfStatement': return await this.evalIf(node, env);
127
+ case 'WhileStatement': return await this.evalWhile(node, env);
128
+ case 'ForStatement': return await this.evalFor(node, env);
129
+ case 'BreakStatement': throw new BreakSignal();
130
+ case 'ContinueStatement': throw new ContinueSignal();
131
+ case 'ImportStatement': return await this.evalImport(node, env);
132
+ case 'FunctionDeclaration': return this.evalFunctionDeclaration(node, env);
133
+ case 'CallExpression': return await this.evalCall(node, env);
134
+ case 'ArrowFunctionExpression': return this.evalArrowFunction(node, env);
135
+ case 'ReturnStatement': {
136
+ const val = node.argument ? await this.evaluate(node.argument, env) : null;
137
+ throw new ReturnValue(val);
138
+ }
139
+
140
+ case 'ArrayExpression':
141
+ return await Promise.all(node.elements.map(el => this.evaluate(el, env)));
142
+ case 'IndexExpression': return await this.evalIndex(node, env);
143
+ case 'ObjectExpression': {
144
+ const out = {};
145
+ for (const p of node.props) {
146
+ out[p.key] = await this.evaluate(p.value, env);
147
+ }
148
+ return out;
149
+ }
150
+ case 'MemberExpression': return await this.evalMember(node, env);
151
+ case 'UpdateExpression': return await this.evalUpdate(node, env);
152
+ case 'AwaitExpression': {
153
+ const val = await this.evaluate(node.argument, env);
154
+ return val;
155
+ }
156
+ default:
157
+ throw new Error(`Unknown node type in evaluator: ${node.type}`);
158
+ }
159
+ }
35
160
 
36
- define(name, value) {
37
- this.store[name] = value;
38
- return value;
39
- }
161
+ async evalProgram(node, env) {
162
+ let result = null;
163
+ for (const stmt of node.body) {
164
+ result = await this.evaluate(stmt, env);
165
+ }
166
+ return result;
40
167
  }
41
168
 
42
- class Evaluator {
43
- constructor() {
44
- this.global = new Environment();
45
- this.setupBuiltins();
46
- }
169
+ async evalImport(node, env) {
170
+ const spec = node.path;
171
+ let lib;
47
172
 
48
- setupBuiltins() {
49
- this.global.define('len', arg => {
50
- if (Array.isArray(arg) || typeof arg === 'string') return arg.length;
51
- if (arg && typeof arg === 'object') return Object.keys(arg).length;
52
- return 0;
53
- });
54
-
55
- this.global.define('print', arg => { console.log(arg); return null; });
56
- this.global.define('type', arg => Array.isArray(arg) ? 'array' : typeof arg);
57
- this.global.define('keys', arg => arg && typeof arg === 'object' ? Object.keys(arg) : []);
58
- this.global.define('values', arg => arg && typeof arg === 'object' ? Object.values(arg) : []);
59
-
60
- this.global.define('ask', prompt => readlineSync.question(prompt + ' '));
61
- this.global.define('num', arg => {
62
- const n = Number(arg);
63
- if (Number.isNaN(n)) throw new Error('Cannot convert value to number');
64
- return n;
65
- });
66
- this.global.define('str', arg => String(arg));
67
-
68
- // Async fetch built-in for API requests
69
- this.global.define('fetch', async (url, options) => {
70
- const fetch = require('node-fetch'); // Make sure node-fetch is installed
71
- const res = await fetch(url, options);
72
- return res;
73
- });
74
- }
173
+ try {
174
+ const resolved = require.resolve(spec, {
175
+ paths: [process.cwd()]
176
+ });
177
+ lib = require(resolved);
178
+ } catch (e) {
179
+ const fullPath = path.isAbsolute(spec)
180
+ ? spec
181
+ : path.join(process.cwd(), spec.endsWith('.sl') ? spec : spec + '.sl');
75
182
 
76
- async evaluate(node, env = this.global) {
77
- switch (node.type) {
78
- case 'Program': return await this.evalProgram(node, env);
79
- case 'BlockStatement': return await this.evalBlock(node, new Environment(env));
80
- case 'VarDeclaration': return await this.evalVarDeclaration(node, env);
81
- case 'AssignmentExpression': return await this.evalAssignment(node, env);
82
- case 'CompoundAssignment': return await this.evalCompoundAssignment(node, env);
83
- case 'SldeployStatement': return await this.evalSldeploy(node, env);
84
- case 'AskStatement': return await this.evalAsk(node, env);
85
- case 'DefineStatement': return await this.evalDefine(node, env);
86
- case 'ExpressionStatement': return await this.evaluate(node.expression, env);
87
- case 'BinaryExpression': return await this.evalBinary(node, env);
88
- case 'LogicalExpression': return await this.evalLogical(node, env);
89
- case 'UnaryExpression': return await this.evalUnary(node, env);
90
- case 'Literal': return node.value;
91
- case 'Identifier': return env.get(node.name);
92
- case 'IfStatement': return await this.evalIf(node, env);
93
- case 'WhileStatement': return await this.evalWhile(node, env);
94
- case 'ForStatement': return await this.evalFor(node, env);
95
- case 'BreakStatement': throw new BreakSignal();
96
- case 'ContinueStatement': throw new ContinueSignal();
97
- case 'ImportStatement': return await this.evalImport(node, env);
98
- case 'FunctionDeclaration': return await this.evalFunctionDeclaration(node, env);
99
- case 'CallExpression': return await this.evalCall(node, env);
100
- case 'ArrowFunctionExpression': return await this.evalArrowFunction(node, env);
101
- case 'ReturnStatement': {
102
- const val = node.argument ? await this.evaluate(node.argument, env) : null;
103
- throw new ReturnValue(val);
104
- }
105
- case 'ArrayExpression': return await Promise.all(node.elements.map(el => this.evaluate(el, env)));
106
- case 'IndexExpression': return await this.evalIndex(node, env);
107
- case 'ObjectExpression': return await this.evalObject(node, env);
108
- case 'MemberExpression': return await this.evalMember(node, env);
109
- case 'UpdateExpression': return await this.evalUpdate(node, env);
110
- default:
111
- throw new Error(`Unknown node type in evaluator: ${node.type}`);
112
- }
183
+ if (!fs.existsSync(fullPath)) {
184
+ throw new Error(`Import not found: ${spec}`);
113
185
  }
114
186
 
115
- async evalProgram(node, env) {
116
- let result = null;
117
- for (const stmt of node.body) result = await this.evaluate(stmt, env);
118
- return result;
119
- }
187
+ const code = fs.readFileSync(fullPath, 'utf-8');
188
+ const tokens = new Lexer(code).getTokens();
189
+ const ast = new Parser(tokens).parse();
120
190
 
121
- async evalBlock(node, env) {
122
- let result = null;
123
- for (const stmt of node.body) {
124
- try { result = await this.evaluate(stmt, env); }
125
- catch (e) {
126
- if (e instanceof ReturnValue || e instanceof BreakSignal || e instanceof ContinueSignal) throw e;
127
- throw e;
128
- }
129
- }
130
- return result;
131
- }
191
+ const moduleEnv = new Environment(env);
192
+ await this.evaluate(ast, moduleEnv);
132
193
 
133
- async evalVarDeclaration(node, env) {
134
- const val = await this.evaluate(node.expr, env);
135
- return env.define(node.id, val);
194
+ lib = {};
195
+ for (const key of Object.keys(moduleEnv.store)) {
196
+ lib[key] = moduleEnv.store[key];
136
197
  }
137
198
 
138
- async evalArrowFunction(node, env) {
139
- return { params: node.params, body: node.body, env, arrow: true };
140
- }
199
+ lib.default = lib;
200
+ }
141
201
 
142
- async evalAssignment(node, env) {
143
- const rightVal = await this.evaluate(node.right, env);
144
- const left = node.left;
145
-
146
- if (left.type === 'Identifier') return env.set(left.name, rightVal);
147
- if (left.type === 'MemberExpression') {
148
- const obj = await this.evalMemberObj(left, env);
149
- obj[left.property] = rightVal;
150
- return rightVal;
151
- }
152
- if (left.type === 'IndexExpression') {
153
- const obj = await this.evalIndexObj(left, env);
154
- const idx = await this.evaluate(left.indexer, env);
155
- obj[idx] = rightVal;
156
- return rightVal;
157
- }
158
- throw new Error('Invalid assignment target');
202
+ for (const imp of node.specifiers) {
203
+ if (imp.type === 'DefaultImport') {
204
+ env.define(imp.local, lib.default ?? lib);
159
205
  }
160
-
161
- async evalCompoundAssignment(node, env) {
162
- const left = node.left;
163
- let current;
164
- if (left.type === 'Identifier') current = env.get(left.name);
165
- else if (left.type === 'MemberExpression') current = await this.evalMember(left, env);
166
- else if (left.type === 'IndexExpression') current = await this.evalIndex(left, env);
167
- else throw new Error('Invalid compound assignment target');
168
-
169
- const rhs = await this.evaluate(node.right, env);
170
- let computed;
171
- switch (node.operator) {
172
- case 'PLUSEQ': computed = current + rhs; break;
173
- case 'MINUSEQ': computed = current - rhs; break;
174
- case 'STAREQ': computed = current * rhs; break;
175
- case 'SLASHEQ': computed = current / rhs; break;
176
- case 'MODEQ': computed = current % rhs; break;
177
- default: throw new Error('Unknown compound operator');
178
- }
179
-
180
- if (left.type === 'Identifier') env.set(left.name, computed);
181
- else await this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
182
-
183
- return computed;
206
+ if (imp.type === 'NamespaceImport') {
207
+ env.define(imp.local, lib);
184
208
  }
185
-
186
- async evalSldeploy(node, env) {
187
- const val = await this.evaluate(node.expr, env);
188
- console.log(val);
189
- return val;
209
+ if (imp.type === 'NamedImport') {
210
+ if (!(imp.imported in lib)) {
211
+ throw new Error(`Module '${spec}' has no export '${imp.imported}'`);
212
+ }
213
+ env.define(imp.local, lib[imp.imported]);
190
214
  }
215
+ }
191
216
 
192
- async evalAsk(node, env) {
193
- const prompt = await this.evaluate(node.prompt, env);
194
- return readlineSync.question(prompt + ' ');
195
- }
217
+ return null;
218
+ }
196
219
 
197
- async evalDefine(node, env) {
198
- const val = node.expr ? await this.evaluate(node.expr, env) : null;
199
- return this.global.define(node.id, val);
200
- }
220
+ async evalBlock(node, env) {
221
+ let result = null;
222
+ for (const stmt of node.body) {
223
+ try {
224
+ result = await this.evaluate(stmt, env);
225
+ } catch (e) {
226
+ if (e instanceof ReturnValue || e instanceof BreakSignal || e instanceof ContinueSignal) throw e;
227
+ throw e;
228
+ }
229
+ }
230
+ return result;
231
+ }
201
232
 
202
- async evalBinary(node, env) {
203
- const l = await this.evaluate(node.left, env);
204
- const r = await this.evaluate(node.right, env);
205
-
206
- if (node.operator === 'SLASH' && r === 0) throw new Error('Division by zero');
207
-
208
- switch (node.operator) {
209
- case 'PLUS': return l + r;
210
- case 'MINUS': return l - r;
211
- case 'STAR': return l * r;
212
- case 'SLASH': return l / r;
213
- case 'MOD': return l % r;
214
- case 'EQEQ': return l === r;
215
- case 'NOTEQ': return l !== r;
216
- case 'LT': return l < r;
217
- case 'LTE': return l <= r;
218
- case 'GT': return l > r;
219
- case 'GTE': return l >= r;
220
- default: throw new Error(`Unknown binary operator ${node.operator}`);
221
- }
222
- }
233
+ async evalVarDeclaration(node, env) {
234
+ const val = await this.evaluate(node.expr, env);
235
+ return env.define(node.id, val);
236
+ }
223
237
 
224
- async evalLogical(node, env) {
225
- const l = await this.evaluate(node.left, env);
226
- if (node.operator === 'AND') return l && await this.evaluate(node.right, env);
227
- if (node.operator === 'OR') return l || await this.evaluate(node.right, env);
228
- throw new Error(`Unknown logical operator ${node.operator}`);
229
- }
238
+ evalArrowFunction(node, env) {
239
+ return {
240
+ params: node.params,
241
+ body: node.body,
242
+ env: env,
243
+ arrow: true,
244
+ async: node.async || false
245
+ };
246
+ }
230
247
 
231
- async evalUnary(node, env) {
232
- const val = await this.evaluate(node.argument, env);
233
- switch (node.operator) {
234
- case 'NOT': return !val;
235
- case 'MINUS': return -val;
236
- case 'PLUS': return +val;
237
- default: throw new Error(`Unknown unary operator ${node.operator}`);
238
- }
239
- }
248
+ async evalAssignment(node, env) {
249
+ const rightVal = await this.evaluate(node.right, env);
250
+ const left = node.left;
251
+
252
+ if (left.type === 'Identifier') return env.set(left.name, rightVal);
253
+ if (left.type === 'MemberExpression') {
254
+ const obj = await this.evaluate(left.object, env);
255
+ obj[left.property] = rightVal;
256
+ return rightVal;
257
+ }
258
+ if (left.type === 'IndexExpression') {
259
+ const obj = await this.evaluate(left.object, env);
260
+ const idx = await this.evaluate(left.indexer, env);
261
+ obj[idx] = rightVal;
262
+ return rightVal;
263
+ }
264
+
265
+ throw new Error('Invalid assignment target');
266
+ }
240
267
 
241
- async evalIf(node, env) {
242
- const test = await this.evaluate(node.test, env);
243
- if (test) return await this.evaluate(node.consequent, env);
244
- if (node.alternate) return await this.evaluate(node.alternate, env);
245
- return null;
246
- }
268
+ async evalCompoundAssignment(node, env) {
269
+ const left = node.left;
270
+ let current;
271
+
272
+ if (left.type === 'Identifier') current = env.get(left.name);
273
+ else if (left.type === 'MemberExpression') current = await this.evalMember(left, env);
274
+ else if (left.type === 'IndexExpression') current = await this.evalIndex(left, env);
275
+ else throw new Error('Invalid compound assignment target');
276
+
277
+ const rhs = await this.evaluate(node.right, env);
278
+ let computed;
279
+ switch (node.operator) {
280
+ case 'PLUSEQ': computed = current + rhs; break;
281
+ case 'MINUSEQ': computed = current - rhs; break;
282
+ case 'STAREQ': computed = current * rhs; break;
283
+ case 'SLASHEQ': computed = current / rhs; break;
284
+ case 'MODEQ': computed = current % rhs; break;
285
+ default: throw new Error('Unknown compound operator');
286
+ }
287
+
288
+ if (left.type === 'Identifier') env.set(left.name, computed);
289
+ else if (left.type === 'MemberExpression') await this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
290
+ else await this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
291
+
292
+ return computed;
293
+ }
247
294
 
248
- async evalWhile(node, env) {
249
- while (await this.evaluate(node.test, env)) {
250
- try { await this.evaluate(node.body, env); }
251
- catch (e) { if (e instanceof BreakSignal) break; if (e instanceof ContinueSignal) continue; throw e; }
252
- }
253
- return null;
254
- }
295
+ async evalSldeploy(node, env) {
296
+ const val = await this.evaluate(node.expr, env);
255
297
 
256
- async evalFor(node, env) {
257
- const local = new Environment(env);
258
- if (node.init) await this.evaluate(node.init, local);
259
- while (!node.test || await this.evaluate(node.test, local)) {
260
- try { await this.evaluate(node.body, local); }
261
- catch (e) {
262
- if (e instanceof BreakSignal) break;
263
- if (e instanceof ContinueSignal) { if (node.update) await this.evaluate(node.update, local); continue; }
264
- throw e;
265
- }
266
- if (node.update) await this.evaluate(node.update, local);
267
- }
268
- return null;
269
- }
298
+ if (typeof val === 'object' && val !== null) {
299
+ console.log(JSON.stringify(val, null, 2));
300
+ } else {
301
+ console.log(val);
302
+ }
270
303
 
271
- async evalFunctionDeclaration(node, env) {
272
- const fn = { params: node.params, body: node.body, env };
273
- env.define(node.name, fn);
274
- return null;
275
- }
304
+ return val;
305
+ }
276
306
 
277
- async evalCall(node, env) {
278
- const calleeEvaluated = await this.evaluate(node.callee, env);
307
+ async evalAsk(node, env) {
308
+ const prompt = await this.evaluate(node.prompt, env);
309
+ const input = readlineSync.question(prompt + ' ');
310
+ return input;
311
+ }
279
312
 
280
- if (typeof calleeEvaluated === 'function') {
281
- const args = [];
282
- for (const a of node.arguments) args.push(await this.evaluate(a, env));
283
- return await calleeEvaluated(...args);
284
- }
313
+ async evalDefine(node, env) {
314
+ const val = node.expr ? await this.evaluate(node.expr, env) : null;
315
+ return this.global.define(node.id, val);
316
+ }
285
317
 
286
- if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body) throw new Error('Call to non-function');
318
+ async evalBinary(node, env) {
319
+ const l = await this.evaluate(node.left, env);
320
+ const r = await this.evaluate(node.right, env);
321
+
322
+ if (node.operator === 'SLASH' && r === 0) {
323
+ throw new Error('Division by zero');
324
+ }
325
+
326
+ switch (node.operator) {
327
+ case 'PLUS': return l + r;
328
+ case 'MINUS': return l - r;
329
+ case 'STAR': return l * r;
330
+ case 'SLASH': return l / r;
331
+ case 'MOD': return l % r;
332
+ case 'EQEQ': return l === r;
333
+ case 'NOTEQ': return l !== r;
334
+ case 'LT': return l < r;
335
+ case 'LTE': return l <= r;
336
+ case 'GT': return l > r;
337
+ case 'GTE': return l >= r;
338
+ default: throw new Error(`Unknown binary operator ${node.operator}`);
339
+ }
340
+ }
287
341
 
288
- const fn = calleeEvaluated;
289
- const callEnv = new Environment(fn.env);
290
- for (let i = 0; i < fn.params.length; i++) {
291
- const argVal = node.arguments[i] ? await this.evaluate(node.arguments[i], env) : null;
292
- callEnv.define(fn.params[i], argVal);
293
- }
342
+ async evalLogical(node, env) {
343
+ const l = await this.evaluate(node.left, env);
344
+ if (node.operator === 'AND') return l && await this.evaluate(node.right, env);
345
+ if (node.operator === 'OR') return l || await this.evaluate(node.right, env);
346
+ throw new Error(`Unknown logical operator ${node.operator}`);
347
+ }
294
348
 
295
- try {
296
- const result = await this.evaluate(fn.body, callEnv);
297
- return fn.arrow ? result : result;
298
- } catch (e) { if (e instanceof ReturnValue) return e.value; throw e; }
299
- }
349
+ async evalUnary(node, env) {
350
+ const val = await this.evaluate(node.argument, env);
351
+ switch (node.operator) {
352
+ case 'NOT': return !val;
353
+ case 'MINUS': return -val;
354
+ case 'PLUS': return +val;
355
+ default: throw new Error(`Unknown unary operator ${node.operator}`);
356
+ }
357
+ }
300
358
 
301
- async evalIndex(node, env) {
302
- const obj = await this.evaluate(node.object, env);
303
- const idx = await this.evaluate(node.indexer, env);
359
+ async evalIf(node, env) {
360
+ const test = await this.evaluate(node.test, env);
361
+ if (test) return await this.evaluate(node.consequent, env);
362
+ if (node.alternate) return await this.evaluate(node.alternate, env);
363
+ return null;
364
+ }
304
365
 
305
- if (obj == null) throw new Error('Indexing null or undefined');
306
- if (Array.isArray(obj) && (idx < 0 || idx >= obj.length)) throw new Error('Array index out of bounds');
307
- if (typeof obj === 'object' && !(idx in obj)) throw new Error(`Property '${idx}' does not exist`);
308
- return obj[idx];
366
+ async evalWhile(node, env) {
367
+ while (await this.evaluate(node.test, env)) {
368
+ try { await this.evaluate(node.body, env); }
369
+ catch (e) {
370
+ if (e instanceof BreakSignal) break;
371
+ if (e instanceof ContinueSignal) continue;
372
+ throw e;
309
373
  }
374
+ }
375
+ return null;
376
+ }
310
377
 
311
- async evalObject(node, env) {
312
- const out = {};
313
- for (const p of node.props) out[p.key] = await this.evaluate(p.value, env);
314
- return out;
315
- }
378
+ async evalFor(node, env) {
379
+ const local = new Environment(env);
380
+ if (node.init) await this.evaluate(node.init, local);
381
+ while (!node.test || await this.evaluate(node.test, local)) {
382
+ try { await this.evaluate(node.body, local); }
383
+ catch (e) {
384
+ if (e instanceof BreakSignal) break;
385
+ if (e instanceof ContinueSignal) {
386
+ if (node.update) await this.evaluate(node.update, local);
387
+ continue;
388
+ }
389
+ throw e;
390
+ }
391
+ if (node.update) await this.evaluate(node.update, local);
392
+ }
393
+ return null;
394
+ }
316
395
 
317
- async evalMember(node, env) {
318
- const obj = await this.evaluate(node.object, env);
319
- if (obj == null) throw new Error('Member access of null or undefined');
320
- if (!(node.property in obj)) throw new Error(`Property '${node.property}' does not exist`);
321
- return obj[node.property];
322
- }
396
+ evalFunctionDeclaration(node, env) {
397
+ const fn = { params: node.params, body: node.body, env, async: node.async || false };
398
+ env.define(node.name, fn);
399
+ return null;
400
+ }
323
401
 
324
- async evalMemberObj(node, env) {
325
- const obj = await this.evaluate(node.object, env);
326
- if (obj == null) throw new Error('Member access of null or undefined');
327
- return obj;
328
- }
402
+ async evalCall(node, env) {
403
+ const calleeEvaluated = await this.evaluate(node.callee, env);
404
+ if (typeof calleeEvaluated === 'function') {
405
+ const args = [];
406
+ for (const a of node.arguments) args.push(await this.evaluate(a, env));
407
+ return calleeEvaluated(...args);
408
+ }
409
+ if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body) {
410
+ throw new Error('Call to non-function');
411
+ }
412
+
413
+ const fn = calleeEvaluated;
414
+ const callEnv = new Environment(fn.env);
415
+
416
+ for (let i = 0; i < fn.params.length; i++) {
417
+ const argVal = node.arguments[i] ? await this.evaluate(node.arguments[i], env) : null;
418
+ callEnv.define(fn.params[i], argVal);
419
+ }
420
+
421
+ try {
422
+ const result = await this.evaluate(fn.body, callEnv);
423
+ return fn.arrow ? result : result;
424
+ } catch (e) {
425
+ if (e instanceof ReturnValue) return e.value;
426
+ throw e;
427
+ }
428
+ }
329
429
 
330
- async evalIndexObj(node, env) {
331
- const obj = await this.evaluate(node.object, env);
332
- if (obj == null) throw new Error('Indexing null or undefined');
333
- return obj;
334
- }
430
+ async evalIndex(node, env) {
431
+ const obj = await this.evaluate(node.object, env);
432
+ const idx = await this.evaluate(node.indexer, env);
335
433
 
336
- async evalUpdate(node, env) {
337
- const arg = node.argument;
338
- const getCurrent = async () => {
339
- if (arg.type === 'Identifier') return env.get(arg.name);
340
- if (arg.type === 'MemberExpression') return await this.evalMember(arg, env);
341
- if (arg.type === 'IndexExpression') return await this.evalIndex(arg, env);
342
- throw new Error('Invalid update target');
343
- };
344
- const setValue = async (v) => {
345
- if (arg.type === 'Identifier') env.set(arg.name, v);
346
- else if (arg.type === 'MemberExpression') { const obj = await this.evalMemberObj(arg, env); obj[arg.property] = v; }
347
- else if (arg.type === 'IndexExpression') { const obj = await this.evalIndexObj(arg, env); const idx = await this.evaluate(arg.indexer, env); obj[idx] = v; }
348
- };
349
-
350
- const current = await getCurrent();
351
- const newVal = (node.operator === 'PLUSPLUS') ? current + 1 : current - 1;
352
-
353
- if (node.prefix) { await setValue(newVal); return newVal; }
354
- else { await setValue(newVal); return current; }
355
- }
434
+ if (obj == null) throw new Error('Indexing null or undefined');
435
+ if (Array.isArray(obj) && (idx < 0 || idx >= obj.length)) {
436
+ throw new Error('Array index out of bounds');
437
+ }
438
+ if (typeof obj === 'object' && !(idx in obj)) {
439
+ throw new Error(`Property '${idx}' does not exist`);
440
+ }
441
+
442
+ return obj[idx];
443
+ }
444
+
445
+ async evalObject(node, env) {
446
+ const out = {};
447
+ for (const p of node.props) out[p.key] = await this.evaluate(p.value, env);
448
+ return out;
449
+ }
450
+
451
+ async evalMember(node, env) {
452
+ const obj = await this.evaluate(node.object, env);
453
+ if (obj == null) throw new Error('Member access of null or undefined');
454
+ if (!(node.property in obj)) throw new Error(`Property '${node.property}' does not exist`);
455
+ return obj[node.property];
456
+ }
457
+
458
+ async evalUpdate(node, env) {
459
+ const arg = node.argument;
460
+ const getCurrent = async () => {
461
+ if (arg.type === 'Identifier') return env.get(arg.name);
462
+ if (arg.type === 'MemberExpression') return await this.evalMember(arg, env);
463
+ if (arg.type === 'IndexExpression') return await this.evalIndex(arg, env);
464
+ throw new Error('Invalid update target');
465
+ };
466
+ const setValue = async (v) => {
467
+ if (arg.type === 'Identifier') env.set(arg.name, v);
468
+ else if (arg.type === 'MemberExpression') {
469
+ const obj = await this.evaluate(arg.object, env);
470
+ obj[arg.property] = v;
471
+ } else if (arg.type === 'IndexExpression') {
472
+ const obj = await this.evaluate(arg.object, env);
473
+ const idx = await this.evaluate(arg.indexer, env);
474
+ obj[idx] = v;
475
+ }
476
+ };
477
+
478
+ const current = await getCurrent();
479
+ const newVal = (node.operator === 'PLUSPLUS') ? current + 1 : current - 1;
480
+
481
+ if (node.prefix) { await setValue(newVal); return newVal; }
482
+ else { await setValue(newVal); return current; }
483
+ }
356
484
 
357
- async evalImport(node, env) {
358
- const spec = node.path;
359
- let lib;
360
-
361
- try {
362
- const resolved = require.resolve(spec, { paths: [process.cwd()] });
363
- lib = require(resolved);
364
- } catch (e) {
365
- const fullPath = path.isAbsolute(spec) ? spec : path.join(process.cwd(), spec.endsWith('.sl') ? spec : spec + '.sl');
366
- if (!fs.existsSync(fullPath)) throw new Error(`Import not found: ${spec}`);
367
-
368
- const code = fs.readFileSync(fullPath, 'utf-8');
369
- const tokens = new Lexer(code).getTokens();
370
- const ast = new Parser(tokens).parse();
371
-
372
- const moduleEnv = new Environment(env);
373
- await this.evaluate(ast, moduleEnv);
374
-
375
- lib = {};
376
- for (const key of Object.keys(moduleEnv.store)) lib[key] = moduleEnv.store[key];
377
- lib.default = lib;
378
- }
379
-
380
- for (const imp of node.specifiers) {
381
- if (imp.type === 'DefaultImport') env.define(imp.local, lib.default ?? lib);
382
- if (imp.type === 'NamespaceImport') env.define(imp.local, lib);
383
- if (imp.type === 'NamedImport') {
384
- if (!(imp.imported in lib)) throw new Error(`Module '${spec}' has no export '${imp.imported}'`);
385
- env.define(imp.local, lib[imp.imported]);
386
- }
387
- }
388
-
389
- return null;
390
- }
391
485
  }
392
486
 
393
- module.exports = Evaluator;
487
+ module.exports = Evaluator;