stone-lang 0.1.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/README.md +52 -0
- package/StoneEngine.js +879 -0
- package/StoneEngineService.js +1727 -0
- package/adapters/FileSystemAdapter.js +230 -0
- package/adapters/OutputAdapter.js +208 -0
- package/adapters/index.js +6 -0
- package/cli/CLIOutputAdapter.js +196 -0
- package/cli/DaemonClient.js +349 -0
- package/cli/JSONOutputAdapter.js +135 -0
- package/cli/ReplSession.js +567 -0
- package/cli/ViewerServer.js +590 -0
- package/cli/commands/check.js +84 -0
- package/cli/commands/daemon.js +189 -0
- package/cli/commands/kill.js +66 -0
- package/cli/commands/package.js +713 -0
- package/cli/commands/ps.js +65 -0
- package/cli/commands/run.js +537 -0
- package/cli/entry.js +169 -0
- package/cli/index.js +14 -0
- package/cli/stonec.js +358 -0
- package/cli/test-compiler.js +181 -0
- package/cli/viewer/index.html +495 -0
- package/daemon/IPCServer.js +455 -0
- package/daemon/ProcessManager.js +327 -0
- package/daemon/ProcessRunner.js +307 -0
- package/daemon/daemon.js +398 -0
- package/daemon/index.js +16 -0
- package/frontend/analysis/index.js +5 -0
- package/frontend/analysis/livenessAnalyzer.js +568 -0
- package/frontend/analysis/treeShaker.js +265 -0
- package/frontend/index.js +20 -0
- package/frontend/parsing/astBuilder.js +2196 -0
- package/frontend/parsing/index.js +7 -0
- package/frontend/parsing/sonParser.js +592 -0
- package/frontend/parsing/stoneAstTypes.js +703 -0
- package/frontend/parsing/terminal-registry.js +435 -0
- package/frontend/parsing/tokenizer.js +692 -0
- package/frontend/type-checker/OverloadedFunctionType.js +43 -0
- package/frontend/type-checker/TypeEnvironment.js +165 -0
- package/frontend/type-checker/bidirectionalInference.js +149 -0
- package/frontend/type-checker/index.js +10 -0
- package/frontend/type-checker/moduleAnalysis.js +248 -0
- package/frontend/type-checker/operatorMappings.js +35 -0
- package/frontend/type-checker/overloadResolution.js +605 -0
- package/frontend/type-checker/typeChecker.js +452 -0
- package/frontend/type-checker/typeCompatibility.js +389 -0
- package/frontend/type-checker/visitors/controlFlow.js +483 -0
- package/frontend/type-checker/visitors/functions.js +604 -0
- package/frontend/type-checker/visitors/index.js +38 -0
- package/frontend/type-checker/visitors/literals.js +341 -0
- package/frontend/type-checker/visitors/modules.js +159 -0
- package/frontend/type-checker/visitors/operators.js +109 -0
- package/frontend/type-checker/visitors/statements.js +768 -0
- package/frontend/types/index.js +5 -0
- package/frontend/types/operatorMap.js +134 -0
- package/frontend/types/types.js +2046 -0
- package/frontend/utils/errorCollector.js +244 -0
- package/frontend/utils/index.js +5 -0
- package/frontend/utils/moduleResolver.js +479 -0
- package/package.json +50 -0
- package/packages/browserCache.js +359 -0
- package/packages/fetcher.js +236 -0
- package/packages/index.js +130 -0
- package/packages/lockfile.js +271 -0
- package/packages/manifest.js +291 -0
- package/packages/packageResolver.js +356 -0
- package/packages/resolver.js +310 -0
- package/packages/semver.js +635 -0
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stone REPL Session
|
|
3
|
+
*
|
|
4
|
+
* Interactive Read-Eval-Print Loop for Stone language.
|
|
5
|
+
* Maintains state between commands and supports meta-commands.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* $ stone
|
|
9
|
+
* Stone v1.0.0
|
|
10
|
+
* Type "help" for commands, "exit" to quit.
|
|
11
|
+
*
|
|
12
|
+
* stone> print("hello")
|
|
13
|
+
* hello
|
|
14
|
+
* stone> x = 5
|
|
15
|
+
* stone> x * 2
|
|
16
|
+
* 10
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import readline from 'readline';
|
|
20
|
+
import fs from 'fs';
|
|
21
|
+
import path from 'path';
|
|
22
|
+
import { StoneEngine } from '../StoneEngine.js';
|
|
23
|
+
import { CLIOutputAdapter } from './CLIOutputAdapter.js';
|
|
24
|
+
import { ReplVM } from '../backends/js-vm/vm/index.js';
|
|
25
|
+
import { Lexer } from '../frontend/parsing/tokenizer.js';
|
|
26
|
+
import { Parser } from '../frontend/parsing/astBuilder.js';
|
|
27
|
+
import { Compiler } from '../backends/js-vm/compiler.js';
|
|
28
|
+
|
|
29
|
+
// ANSI colors
|
|
30
|
+
const colors = {
|
|
31
|
+
reset: '\x1b[0m',
|
|
32
|
+
bold: '\x1b[1m',
|
|
33
|
+
dim: '\x1b[2m',
|
|
34
|
+
red: '\x1b[31m',
|
|
35
|
+
green: '\x1b[32m',
|
|
36
|
+
yellow: '\x1b[33m',
|
|
37
|
+
blue: '\x1b[34m',
|
|
38
|
+
cyan: '\x1b[36m',
|
|
39
|
+
magenta: '\x1b[35m',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const c = (color, text) => `${colors[color]}${text}${colors.reset}`;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Stone REPL Session
|
|
46
|
+
*/
|
|
47
|
+
export class ReplSession {
|
|
48
|
+
constructor(options = {}) {
|
|
49
|
+
this.options = options;
|
|
50
|
+
this.version = options.version || '1.0.0';
|
|
51
|
+
this.prompt = options.prompt || 'stone> ';
|
|
52
|
+
this.continuationPrompt = options.continuationPrompt || ' ... ';
|
|
53
|
+
|
|
54
|
+
// Multiline input state
|
|
55
|
+
this.history = [];
|
|
56
|
+
this.multilineBuffer = '';
|
|
57
|
+
this.bracketDepth = 0;
|
|
58
|
+
this.parenDepth = 0;
|
|
59
|
+
|
|
60
|
+
// Working directory for file operations
|
|
61
|
+
this.cwd = process.cwd();
|
|
62
|
+
|
|
63
|
+
// Create output adapter
|
|
64
|
+
this.adapter = new CLIOutputAdapter({
|
|
65
|
+
colorize: true,
|
|
66
|
+
quiet: false,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Create ReplVM for persistent state execution
|
|
70
|
+
this.replVM = new ReplVM({
|
|
71
|
+
outputAdapter: this.adapter,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Create engine for file operations (run command)
|
|
75
|
+
this.engine = new StoneEngine({
|
|
76
|
+
outputAdapter: this.adapter,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Readline interface
|
|
80
|
+
this.rl = null;
|
|
81
|
+
this.running = false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Start the REPL session
|
|
86
|
+
*/
|
|
87
|
+
async start() {
|
|
88
|
+
this.running = true;
|
|
89
|
+
|
|
90
|
+
// Print banner
|
|
91
|
+
this.printBanner();
|
|
92
|
+
|
|
93
|
+
// Create readline interface
|
|
94
|
+
this.rl = readline.createInterface({
|
|
95
|
+
input: process.stdin,
|
|
96
|
+
output: process.stdout,
|
|
97
|
+
prompt: this.prompt,
|
|
98
|
+
historySize: 1000,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Handle line input
|
|
102
|
+
this.rl.on('line', async (line) => {
|
|
103
|
+
await this.handleLine(line);
|
|
104
|
+
if (this.running) {
|
|
105
|
+
this.rl.prompt();
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Handle close (Ctrl+D)
|
|
110
|
+
this.rl.on('close', () => {
|
|
111
|
+
this.exit();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Handle SIGINT (Ctrl+C)
|
|
115
|
+
this.rl.on('SIGINT', () => {
|
|
116
|
+
if (this.multilineBuffer) {
|
|
117
|
+
// Cancel multiline input
|
|
118
|
+
this.multilineBuffer = '';
|
|
119
|
+
this.bracketDepth = 0;
|
|
120
|
+
this.parenDepth = 0;
|
|
121
|
+
console.log('\n' + c('dim', '(input cancelled)'));
|
|
122
|
+
this.rl.setPrompt(this.prompt);
|
|
123
|
+
this.rl.prompt();
|
|
124
|
+
} else {
|
|
125
|
+
console.log('\n' + c('dim', 'Use "exit" or Ctrl+D to quit'));
|
|
126
|
+
this.rl.prompt();
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Start prompting
|
|
131
|
+
this.rl.prompt();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Print welcome banner
|
|
136
|
+
*/
|
|
137
|
+
printBanner() {
|
|
138
|
+
console.log('');
|
|
139
|
+
console.log(c('bold', `Stone v${this.version}`));
|
|
140
|
+
console.log(c('dim', 'Type "help" for commands, "exit" to quit.'));
|
|
141
|
+
console.log('');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Handle a line of input
|
|
146
|
+
*/
|
|
147
|
+
async handleLine(line) {
|
|
148
|
+
const trimmed = line.trim();
|
|
149
|
+
|
|
150
|
+
// Handle empty input
|
|
151
|
+
if (!trimmed && !this.multilineBuffer) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Check for meta-commands (only if not in multiline mode)
|
|
156
|
+
if (!this.multilineBuffer && trimmed.startsWith(':')) {
|
|
157
|
+
await this.handleMetaCommand(trimmed.slice(1));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Check for simple commands without colon prefix
|
|
162
|
+
if (!this.multilineBuffer) {
|
|
163
|
+
const firstWord = trimmed.split(/\s+/)[0].toLowerCase();
|
|
164
|
+
if (['help', 'exit', 'quit', 'clear', 'reset', 'vars', 'pwd', 'cd'].includes(firstWord)) {
|
|
165
|
+
await this.handleMetaCommand(trimmed);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
// Handle 'run' command
|
|
169
|
+
if (firstWord === 'run') {
|
|
170
|
+
await this.handleMetaCommand(trimmed);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Accumulate multiline input
|
|
176
|
+
this.multilineBuffer += (this.multilineBuffer ? '\n' : '') + line;
|
|
177
|
+
|
|
178
|
+
// Update bracket tracking
|
|
179
|
+
this.updateBracketDepth(line);
|
|
180
|
+
|
|
181
|
+
// Check if input is complete
|
|
182
|
+
if (this.isInputComplete()) {
|
|
183
|
+
const code = this.multilineBuffer;
|
|
184
|
+
this.multilineBuffer = '';
|
|
185
|
+
this.bracketDepth = 0;
|
|
186
|
+
this.parenDepth = 0;
|
|
187
|
+
this.rl.setPrompt(this.prompt);
|
|
188
|
+
|
|
189
|
+
await this.executeCode(code);
|
|
190
|
+
} else {
|
|
191
|
+
// Continue multiline input
|
|
192
|
+
this.rl.setPrompt(this.continuationPrompt);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Update bracket depth tracking
|
|
198
|
+
*/
|
|
199
|
+
updateBracketDepth(line) {
|
|
200
|
+
// Simple bracket counting (doesn't handle strings/comments perfectly)
|
|
201
|
+
for (const char of line) {
|
|
202
|
+
if (char === '{' || char === '[') this.bracketDepth++;
|
|
203
|
+
if (char === '}' || char === ']') this.bracketDepth--;
|
|
204
|
+
if (char === '(') this.parenDepth++;
|
|
205
|
+
if (char === ')') this.parenDepth--;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Check if multiline input is complete
|
|
211
|
+
*/
|
|
212
|
+
isInputComplete() {
|
|
213
|
+
// If brackets are balanced, input is complete
|
|
214
|
+
return this.bracketDepth <= 0 && this.parenDepth <= 0;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Handle meta-commands
|
|
219
|
+
*/
|
|
220
|
+
async handleMetaCommand(cmd) {
|
|
221
|
+
const parts = cmd.trim().split(/\s+/);
|
|
222
|
+
const command = parts[0].toLowerCase();
|
|
223
|
+
const args = parts.slice(1);
|
|
224
|
+
|
|
225
|
+
switch (command) {
|
|
226
|
+
case 'help':
|
|
227
|
+
case 'h':
|
|
228
|
+
case '?':
|
|
229
|
+
this.printHelp();
|
|
230
|
+
break;
|
|
231
|
+
|
|
232
|
+
case 'exit':
|
|
233
|
+
case 'quit':
|
|
234
|
+
case 'q':
|
|
235
|
+
this.exit();
|
|
236
|
+
break;
|
|
237
|
+
|
|
238
|
+
case 'clear':
|
|
239
|
+
case 'cls':
|
|
240
|
+
console.clear();
|
|
241
|
+
break;
|
|
242
|
+
|
|
243
|
+
case 'reset':
|
|
244
|
+
this.replVM.reset();
|
|
245
|
+
console.log(c('dim', 'State cleared'));
|
|
246
|
+
break;
|
|
247
|
+
|
|
248
|
+
case 'vars':
|
|
249
|
+
case 'variables':
|
|
250
|
+
this.printVariables();
|
|
251
|
+
break;
|
|
252
|
+
|
|
253
|
+
case 'run':
|
|
254
|
+
case 'r':
|
|
255
|
+
if (args.length === 0) {
|
|
256
|
+
console.log(c('red', 'Usage: run <filename.stn>'));
|
|
257
|
+
} else {
|
|
258
|
+
await this.runFile(args[0]);
|
|
259
|
+
}
|
|
260
|
+
break;
|
|
261
|
+
|
|
262
|
+
case 'load':
|
|
263
|
+
case 'l':
|
|
264
|
+
if (args.length === 0) {
|
|
265
|
+
console.log(c('red', 'Usage: load <filename.stn>'));
|
|
266
|
+
} else {
|
|
267
|
+
await this.loadFile(args[0]);
|
|
268
|
+
}
|
|
269
|
+
break;
|
|
270
|
+
|
|
271
|
+
case 'pwd':
|
|
272
|
+
console.log(this.cwd);
|
|
273
|
+
break;
|
|
274
|
+
|
|
275
|
+
case 'cd':
|
|
276
|
+
if (args.length === 0) {
|
|
277
|
+
this.cwd = process.cwd();
|
|
278
|
+
} else {
|
|
279
|
+
const newPath = path.resolve(this.cwd, args[0]);
|
|
280
|
+
if (fs.existsSync(newPath) && fs.statSync(newPath).isDirectory()) {
|
|
281
|
+
this.cwd = newPath;
|
|
282
|
+
} else {
|
|
283
|
+
console.log(c('red', `Directory not found: ${args[0]}`));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
break;
|
|
287
|
+
|
|
288
|
+
case 'ls':
|
|
289
|
+
case 'dir':
|
|
290
|
+
try {
|
|
291
|
+
const files = fs.readdirSync(this.cwd);
|
|
292
|
+
const stoneFiles = files.filter(f => f.endsWith('.stn'));
|
|
293
|
+
const otherFiles = files.filter(f => !f.endsWith('.stn'));
|
|
294
|
+
|
|
295
|
+
if (stoneFiles.length > 0) {
|
|
296
|
+
console.log(c('cyan', 'Stone files:'));
|
|
297
|
+
stoneFiles.forEach(f => console.log(' ' + c('green', f)));
|
|
298
|
+
}
|
|
299
|
+
if (otherFiles.length > 0) {
|
|
300
|
+
console.log(c('dim', 'Other:'));
|
|
301
|
+
otherFiles.slice(0, 10).forEach(f => console.log(' ' + f));
|
|
302
|
+
if (otherFiles.length > 10) {
|
|
303
|
+
console.log(c('dim', ` ... and ${otherFiles.length - 10} more`));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
} catch (e) {
|
|
307
|
+
console.log(c('red', `Error: ${e.message}`));
|
|
308
|
+
}
|
|
309
|
+
break;
|
|
310
|
+
|
|
311
|
+
default:
|
|
312
|
+
console.log(c('red', `Unknown command: ${command}`));
|
|
313
|
+
console.log(c('dim', 'Type "help" for available commands'));
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Execute Stone code using ReplVM with incremental compilation
|
|
319
|
+
*/
|
|
320
|
+
async executeCode(code) {
|
|
321
|
+
try {
|
|
322
|
+
// 1. Parse the code
|
|
323
|
+
const lexer = new Lexer(code, '<repl>');
|
|
324
|
+
const tokens = lexer.tokenize();
|
|
325
|
+
const parser = new Parser(tokens, '<repl>');
|
|
326
|
+
const ast = parser.parse();
|
|
327
|
+
|
|
328
|
+
// 2. Compile with incremental compilation (preserving existing state)
|
|
329
|
+
const compiler = new Compiler({
|
|
330
|
+
existingLocals: this.replVM.getExistingLocals(),
|
|
331
|
+
existingFunctions: this.replVM.getExistingFunctions(),
|
|
332
|
+
});
|
|
333
|
+
const compiled = compiler.compile(ast);
|
|
334
|
+
|
|
335
|
+
if (!compiled.success) {
|
|
336
|
+
const errorMsg = compiled.errors.map(e => e.message).join('\n');
|
|
337
|
+
console.log(c('red', `Compile error: ${errorMsg}`));
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// 3. Execute with ReplVM (maintains state between commands)
|
|
342
|
+
const result = this.replVM.executeCommand(compiled);
|
|
343
|
+
|
|
344
|
+
if (result.success) {
|
|
345
|
+
// Print result if it's an expression (not assignment/statement)
|
|
346
|
+
if (result.result !== undefined && result.result !== null) {
|
|
347
|
+
// Don't print if it was just printed by the script
|
|
348
|
+
const isPrintStatement = code.trim().startsWith('print(');
|
|
349
|
+
if (!isPrintStatement) {
|
|
350
|
+
this.printValue(result.result);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
console.log(c('red', `Error: ${result.error}`));
|
|
355
|
+
}
|
|
356
|
+
} catch (error) {
|
|
357
|
+
console.log(c('red', `Error: ${error.message}`));
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Print a value with formatting
|
|
363
|
+
*/
|
|
364
|
+
printValue(value) {
|
|
365
|
+
if (value === undefined || value === null) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (typeof value === 'number') {
|
|
370
|
+
console.log(c('yellow', String(value)));
|
|
371
|
+
} else if (typeof value === 'string') {
|
|
372
|
+
console.log(c('green', `"${value}"`));
|
|
373
|
+
} else if (typeof value === 'boolean') {
|
|
374
|
+
console.log(c('magenta', String(value)));
|
|
375
|
+
} else if (Array.isArray(value)) {
|
|
376
|
+
console.log(c('cyan', JSON.stringify(value)));
|
|
377
|
+
} else if (value && value._type === 'StoneArray') {
|
|
378
|
+
console.log(c('cyan', value.toString()));
|
|
379
|
+
} else if (typeof value === 'object') {
|
|
380
|
+
console.log(c('cyan', JSON.stringify(value, null, 2)));
|
|
381
|
+
} else {
|
|
382
|
+
console.log(String(value));
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Run a Stone file
|
|
388
|
+
*/
|
|
389
|
+
async runFile(filename) {
|
|
390
|
+
const fullPath = path.resolve(this.cwd, filename);
|
|
391
|
+
|
|
392
|
+
if (!fs.existsSync(fullPath)) {
|
|
393
|
+
console.log(c('red', `File not found: ${filename}`));
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
try {
|
|
398
|
+
const source = fs.readFileSync(fullPath, 'utf8');
|
|
399
|
+
console.log(c('dim', `Running ${filename}...`));
|
|
400
|
+
console.log('');
|
|
401
|
+
|
|
402
|
+
// Get current REPL variables to pass to file execution
|
|
403
|
+
const currentVars = {};
|
|
404
|
+
for (const name of this.replVM.getVariableNames()) {
|
|
405
|
+
currentVars[name] = this.replVM.getVariable(name);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const result = await this.engine.execute(source, {
|
|
409
|
+
filename: fullPath,
|
|
410
|
+
currentPath: path.dirname(fullPath),
|
|
411
|
+
variables: currentVars,
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
if (result.success) {
|
|
415
|
+
console.log('');
|
|
416
|
+
console.log(c('green', `Completed in ${result.duration || 0}ms`));
|
|
417
|
+
} else {
|
|
418
|
+
console.log(c('red', `Execution failed: ${result.error}`));
|
|
419
|
+
}
|
|
420
|
+
} catch (error) {
|
|
421
|
+
console.log(c('red', `Error: ${error.message}`));
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Load a file into the current context (execute and import variables)
|
|
427
|
+
*/
|
|
428
|
+
async loadFile(filename) {
|
|
429
|
+
const fullPath = path.resolve(this.cwd, filename);
|
|
430
|
+
|
|
431
|
+
if (!fs.existsSync(fullPath)) {
|
|
432
|
+
console.log(c('red', `File not found: ${filename}`));
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
try {
|
|
437
|
+
const source = fs.readFileSync(fullPath, 'utf8');
|
|
438
|
+
console.log(c('dim', `Loading ${filename}...`));
|
|
439
|
+
|
|
440
|
+
// Track variable count before loading
|
|
441
|
+
const varCountBefore = this.replVM.getVariableNames().length;
|
|
442
|
+
|
|
443
|
+
// Parse and compile with incremental compilation
|
|
444
|
+
const lexer = new Lexer(source, fullPath);
|
|
445
|
+
const tokens = lexer.tokenize();
|
|
446
|
+
const parser = new Parser(tokens, fullPath);
|
|
447
|
+
const ast = parser.parse();
|
|
448
|
+
|
|
449
|
+
const compiler = new Compiler({
|
|
450
|
+
existingLocals: this.replVM.getExistingLocals(),
|
|
451
|
+
existingFunctions: this.replVM.getExistingFunctions(),
|
|
452
|
+
});
|
|
453
|
+
const compiled = compiler.compile(ast);
|
|
454
|
+
|
|
455
|
+
if (!compiled.success) {
|
|
456
|
+
const errorMsg = compiled.errors.map(e => e.message).join('\n');
|
|
457
|
+
console.log(c('red', `Compile error: ${errorMsg}`));
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Execute through ReplVM to load variables into current context
|
|
462
|
+
const result = this.replVM.executeCommand(compiled);
|
|
463
|
+
|
|
464
|
+
if (result.success) {
|
|
465
|
+
const varCountAfter = this.replVM.getVariableNames().length;
|
|
466
|
+
const newVarCount = varCountAfter - varCountBefore;
|
|
467
|
+
console.log(c('green', `Loaded ${newVarCount >= 0 ? newVarCount : varCountAfter} variable(s)`));
|
|
468
|
+
} else {
|
|
469
|
+
console.log(c('red', `Load failed: ${result.error}`));
|
|
470
|
+
}
|
|
471
|
+
} catch (error) {
|
|
472
|
+
console.log(c('red', `Error: ${error.message}`));
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Print available variables
|
|
478
|
+
*/
|
|
479
|
+
printVariables() {
|
|
480
|
+
const vars = this.replVM.getVariableNames();
|
|
481
|
+
|
|
482
|
+
if (vars.length === 0) {
|
|
483
|
+
console.log(c('dim', 'No variables defined'));
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
console.log(c('bold', 'Variables:'));
|
|
488
|
+
for (const name of vars) {
|
|
489
|
+
const value = this.replVM.getVariable(name);
|
|
490
|
+
const type = typeof value;
|
|
491
|
+
const preview = this.getValuePreview(value);
|
|
492
|
+
console.log(` ${c('cyan', name)}: ${c('dim', type)} = ${preview}`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Get a short preview of a value
|
|
498
|
+
*/
|
|
499
|
+
getValuePreview(value) {
|
|
500
|
+
if (value === undefined) return c('dim', 'undefined');
|
|
501
|
+
if (value === null) return c('dim', 'null');
|
|
502
|
+
if (typeof value === 'number') return c('yellow', String(value));
|
|
503
|
+
if (typeof value === 'string') {
|
|
504
|
+
if (value.length > 30) {
|
|
505
|
+
return c('green', `"${value.slice(0, 30)}..."`);
|
|
506
|
+
}
|
|
507
|
+
return c('green', `"${value}"`);
|
|
508
|
+
}
|
|
509
|
+
if (typeof value === 'boolean') return c('magenta', String(value));
|
|
510
|
+
if (typeof value === 'function') return c('blue', '[function]');
|
|
511
|
+
if (Array.isArray(value)) {
|
|
512
|
+
return c('cyan', `[${value.length} items]`);
|
|
513
|
+
}
|
|
514
|
+
if (value && value._type === 'StoneArray') {
|
|
515
|
+
return c('cyan', `StoneArray(${value.shape.join('x')})`);
|
|
516
|
+
}
|
|
517
|
+
if (typeof value === 'object') {
|
|
518
|
+
return c('cyan', `{${Object.keys(value).length} keys}`);
|
|
519
|
+
}
|
|
520
|
+
return String(value);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Print help
|
|
525
|
+
*/
|
|
526
|
+
printHelp() {
|
|
527
|
+
console.log('');
|
|
528
|
+
console.log(c('bold', 'Stone REPL Commands:'));
|
|
529
|
+
console.log('');
|
|
530
|
+
console.log(' ' + c('cyan', 'help, ?') + ' Show this help');
|
|
531
|
+
console.log(' ' + c('cyan', 'exit, quit') + ' Exit the REPL');
|
|
532
|
+
console.log(' ' + c('cyan', 'clear') + ' Clear the screen');
|
|
533
|
+
console.log(' ' + c('cyan', 'reset') + ' Clear all variables');
|
|
534
|
+
console.log(' ' + c('cyan', 'vars') + ' Show defined variables');
|
|
535
|
+
console.log('');
|
|
536
|
+
console.log(c('bold', 'File Commands:'));
|
|
537
|
+
console.log('');
|
|
538
|
+
console.log(' ' + c('cyan', 'run <file>') + ' Execute a Stone script');
|
|
539
|
+
console.log(' ' + c('cyan', 'load <file>') + ' Load script into current context');
|
|
540
|
+
console.log(' ' + c('cyan', 'ls, dir') + ' List files in current directory');
|
|
541
|
+
console.log(' ' + c('cyan', 'cd <dir>') + ' Change directory');
|
|
542
|
+
console.log(' ' + c('cyan', 'pwd') + ' Print working directory');
|
|
543
|
+
console.log('');
|
|
544
|
+
console.log(c('bold', 'Keyboard:'));
|
|
545
|
+
console.log('');
|
|
546
|
+
console.log(' ' + c('cyan', 'Ctrl+C') + ' Cancel current input');
|
|
547
|
+
console.log(' ' + c('cyan', 'Ctrl+D') + ' Exit the REPL');
|
|
548
|
+
console.log(' ' + c('cyan', 'Up/Down') + ' Navigate history');
|
|
549
|
+
console.log('');
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Exit the REPL
|
|
554
|
+
*/
|
|
555
|
+
exit() {
|
|
556
|
+
if (!this.running) return; // Already exiting
|
|
557
|
+
this.running = false;
|
|
558
|
+
console.log('');
|
|
559
|
+
console.log(c('dim', 'Goodbye!'));
|
|
560
|
+
if (this.rl) {
|
|
561
|
+
this.rl.close();
|
|
562
|
+
}
|
|
563
|
+
process.exit(0);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
export default ReplSession;
|