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