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
package/StoneEngine.js
ADDED
|
@@ -0,0 +1,879 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StoneEngine - Platform-agnostic Stone execution engine
|
|
3
|
+
*
|
|
4
|
+
* Provides a thin wrapper around the Stone compiler/VM/AST executor.
|
|
5
|
+
* Uses OutputAdapter for streaming output and FileSystemAdapter for file access.
|
|
6
|
+
*
|
|
7
|
+
* This engine is designed to work identically in:
|
|
8
|
+
* - CLI (with CLIOutputAdapter)
|
|
9
|
+
* - Web app (with WebOutputAdapter)
|
|
10
|
+
* - Daemon (with JSON streaming adapter)
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* const engine = new StoneEngine({
|
|
14
|
+
* outputAdapter: new CLIOutputAdapter(),
|
|
15
|
+
* fileSystemAdapter: new NodeFileSystemAdapter(),
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* const result = await engine.execute(sourceCode, { filename: 'script.stn' });
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { Lexer } from './frontend/parsing/tokenizer.js';
|
|
22
|
+
import { Parser } from './frontend/parsing/astBuilder.js';
|
|
23
|
+
import { TypeChecker } from './frontend/type-checker/typeChecker.js';
|
|
24
|
+
import { ModuleResolver } from './frontend/utils/moduleResolver.js';
|
|
25
|
+
import { Compiler } from './backends/js-vm/compiler.js';
|
|
26
|
+
import { VM } from './backends/js-vm/vm/index.js';
|
|
27
|
+
import { CompiledFunction } from './backends/js-vm/bytecode.js';
|
|
28
|
+
import { BufferedOutputAdapter } from './adapters/OutputAdapter.js';
|
|
29
|
+
import { createTerminalConstructors } from './frontend/parsing/terminal-registry.js';
|
|
30
|
+
import { createTreeShaker } from './frontend/analysis/treeShaker.js';
|
|
31
|
+
import { setFileSystemAdapter } from './backends/js-vm/primitives/file.js';
|
|
32
|
+
import { registerOverload, clearDynamicOverloads } from './backends/js-vm/primitives/overloads.js';
|
|
33
|
+
|
|
34
|
+
export class StoneEngine {
|
|
35
|
+
/**
|
|
36
|
+
* Create a new Stone engine instance
|
|
37
|
+
* @param {Object} options - Engine configuration
|
|
38
|
+
* @param {OutputAdapter} options.outputAdapter - Output adapter for streaming
|
|
39
|
+
* @param {FileSystemAdapter} options.fileSystemAdapter - File system adapter
|
|
40
|
+
* @param {boolean} options.debug - Enable debug logging
|
|
41
|
+
*/
|
|
42
|
+
constructor(options = {}) {
|
|
43
|
+
this.outputAdapter = options.outputAdapter || null;
|
|
44
|
+
this.fileSystemAdapter = options.fileSystemAdapter || null;
|
|
45
|
+
this.debug = options.debug || false;
|
|
46
|
+
|
|
47
|
+
// Core components
|
|
48
|
+
this.compiler = new Compiler();
|
|
49
|
+
this.vm = new VM();
|
|
50
|
+
this.moduleResolver = new ModuleResolver();
|
|
51
|
+
|
|
52
|
+
// Configure module resolver with file system adapter
|
|
53
|
+
if (this.fileSystemAdapter) {
|
|
54
|
+
this.moduleResolver.setFileLoader(async (modulePath) => {
|
|
55
|
+
return this.fileSystemAdapter.readFile(modulePath);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Cached builtins.stn exports
|
|
60
|
+
this.stnBuiltinsExports = null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Set the output adapter
|
|
65
|
+
* @param {OutputAdapter} adapter
|
|
66
|
+
*/
|
|
67
|
+
setOutputAdapter(adapter) {
|
|
68
|
+
this.outputAdapter = adapter;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Set the file system adapter
|
|
73
|
+
* @param {FileSystemAdapter} adapter
|
|
74
|
+
*/
|
|
75
|
+
setFileSystemAdapter(adapter) {
|
|
76
|
+
this.fileSystemAdapter = adapter;
|
|
77
|
+
if (adapter) {
|
|
78
|
+
this.moduleResolver.setFileLoader(async (modulePath) => {
|
|
79
|
+
return adapter.readFile(modulePath);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Set the package context for stone_modules resolution
|
|
86
|
+
* @param {PackageContext} context
|
|
87
|
+
*/
|
|
88
|
+
setPackageContext(context) {
|
|
89
|
+
this.moduleResolver.setPackageContext(context);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Recursively tag a CompiledFunction and its nested functions with moduleIdx
|
|
94
|
+
* This is needed for proper upvalue resolution when functions are called from different contexts
|
|
95
|
+
* @param {CompiledFunction} fn - The function to tag
|
|
96
|
+
* @param {number} moduleIdx - The module index
|
|
97
|
+
* @param {Set} visited - Set of already-visited functions (to avoid cycles)
|
|
98
|
+
*/
|
|
99
|
+
_tagFunctionWithModuleIdx(fn, moduleIdx, visited = new Set()) {
|
|
100
|
+
if (!fn || !(fn instanceof CompiledFunction) || visited.has(fn)) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
visited.add(fn);
|
|
104
|
+
fn.moduleIdx = moduleIdx;
|
|
105
|
+
|
|
106
|
+
// Recursively tag any nested functions in constants
|
|
107
|
+
if (fn.constants) {
|
|
108
|
+
for (const constant of fn.constants) {
|
|
109
|
+
if (constant instanceof CompiledFunction) {
|
|
110
|
+
this._tagFunctionWithModuleIdx(constant, moduleIdx, visited);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Parse source code to AST
|
|
118
|
+
* @param {string} source - Stone source code
|
|
119
|
+
* @param {Object} options - Parse options
|
|
120
|
+
* @returns {Object} AST
|
|
121
|
+
*/
|
|
122
|
+
parse(source, options = {}) {
|
|
123
|
+
const { filename = '<stdin>' } = options;
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const lexer = new Lexer(source, filename);
|
|
127
|
+
const tokens = lexer.tokenize();
|
|
128
|
+
const parser = new Parser(tokens, filename);
|
|
129
|
+
return parser.parse();
|
|
130
|
+
} catch (error) {
|
|
131
|
+
throw new Error(`Parse error: ${error.message}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Type check an AST
|
|
137
|
+
* @param {Object} ast - AST to check
|
|
138
|
+
* @param {Object} options - Type check options
|
|
139
|
+
* @returns {Object} Type check result { success, errors }
|
|
140
|
+
*/
|
|
141
|
+
typeCheck(ast, options = {}) {
|
|
142
|
+
const typeChecker = new TypeChecker({
|
|
143
|
+
moduleResolver: this.moduleResolver,
|
|
144
|
+
parser: (source, opts) => this.parse(source, opts),
|
|
145
|
+
...options,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
return typeChecker.check(ast);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Precheck Stone source code for syntax and type errors
|
|
153
|
+
* Does not execute - only validates
|
|
154
|
+
* @param {string} source - Stone source code
|
|
155
|
+
* @param {Object} options - Options { filename }
|
|
156
|
+
* @returns {Object} { valid: boolean, errors: [], warnings: [] }
|
|
157
|
+
*/
|
|
158
|
+
precheck(source, options = {}) {
|
|
159
|
+
const { filename = '<stdin>' } = options;
|
|
160
|
+
const errors = [];
|
|
161
|
+
const warnings = [];
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
// 1. Parse (syntax check)
|
|
165
|
+
const ast = this.parse(source, { filename });
|
|
166
|
+
|
|
167
|
+
// 2. Type check
|
|
168
|
+
const typeResult = this.typeCheck(ast, options);
|
|
169
|
+
|
|
170
|
+
if (!typeResult.success) {
|
|
171
|
+
errors.push(...typeResult.errors.map(err => ({
|
|
172
|
+
type: 'type',
|
|
173
|
+
message: err.message,
|
|
174
|
+
location: err.location || null
|
|
175
|
+
})));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (typeResult.warnings) {
|
|
179
|
+
warnings.push(...typeResult.warnings);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
valid: errors.length === 0,
|
|
184
|
+
errors,
|
|
185
|
+
warnings
|
|
186
|
+
};
|
|
187
|
+
} catch (parseError) {
|
|
188
|
+
// Parse failed - syntax error
|
|
189
|
+
errors.push({
|
|
190
|
+
type: 'syntax',
|
|
191
|
+
message: parseError.message,
|
|
192
|
+
location: null
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
valid: false,
|
|
197
|
+
errors,
|
|
198
|
+
warnings
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Analyze imports in an AST for tree shaking optimization
|
|
205
|
+
* Returns information about which exports are used from each module,
|
|
206
|
+
* and detects unused imports.
|
|
207
|
+
*
|
|
208
|
+
* @param {Object} ast - AST to analyze
|
|
209
|
+
* @returns {Object} Analysis result
|
|
210
|
+
* @returns {Map<string, Set<string>>} result.usedExports - Map from module path to used export names
|
|
211
|
+
* @returns {Array} result.unusedImports - Array of { localName, modulePath, importedName }
|
|
212
|
+
* @returns {boolean} result.hasUnusedImports - True if there are unused imports
|
|
213
|
+
* @returns {Function} result.filterExports - Function to filter module exports
|
|
214
|
+
*/
|
|
215
|
+
analyzeImports(ast) {
|
|
216
|
+
const shaker = createTreeShaker(ast);
|
|
217
|
+
return {
|
|
218
|
+
usedExports: shaker.usedExports,
|
|
219
|
+
unusedImports: shaker.unusedImports,
|
|
220
|
+
hasUnusedImports: shaker.unusedImports.length > 0,
|
|
221
|
+
filterExports: shaker.filterExports,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Execute Stone source code
|
|
227
|
+
* @param {string} source - Stone source code
|
|
228
|
+
* @param {Object} options - Execution options
|
|
229
|
+
* @param {string} options.filename - Source filename (for error reporting)
|
|
230
|
+
* @param {Object} options.variables - Initial variable bindings
|
|
231
|
+
* @param {string} options.currentPath - Current file path (for module resolution)
|
|
232
|
+
* @param {OutputAdapter} options.outputAdapter - Override output adapter for this execution
|
|
233
|
+
* @param {number} options.maxIterations - Maximum loop iterations
|
|
234
|
+
* @returns {Promise<Object>} Execution result
|
|
235
|
+
*/
|
|
236
|
+
async execute(source, options = {}) {
|
|
237
|
+
const {
|
|
238
|
+
filename = '<stdin>',
|
|
239
|
+
variables = {},
|
|
240
|
+
currentPath = null,
|
|
241
|
+
maxIterations = 100000,
|
|
242
|
+
} = options;
|
|
243
|
+
|
|
244
|
+
// Use per-execution adapter override or instance adapter
|
|
245
|
+
// If neither, create a buffered adapter for backward compat
|
|
246
|
+
const adapter = options.outputAdapter || this.outputAdapter || new BufferedOutputAdapter();
|
|
247
|
+
const startTime = Date.now();
|
|
248
|
+
|
|
249
|
+
// Clear dynamic overloads from previous executions
|
|
250
|
+
// This ensures a clean slate for each execution
|
|
251
|
+
clearDynamicOverloads();
|
|
252
|
+
|
|
253
|
+
// Set up file system adapter for file module (if available)
|
|
254
|
+
if (this.fileSystemAdapter) {
|
|
255
|
+
setFileSystemAdapter(this.fileSystemAdapter);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (this.debug) {
|
|
259
|
+
console.log('[StoneEngine] Starting execution:', filename);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
// 1. Parse
|
|
264
|
+
if (this.debug) console.log('[StoneEngine] Step 1: Parsing...');
|
|
265
|
+
const ast = this.parse(source, { filename });
|
|
266
|
+
if (this.debug) {
|
|
267
|
+
console.log('[StoneEngine] Parsed successfully, statements:', ast.statements?.length);
|
|
268
|
+
console.log('[StoneEngine] Imports:', ast.imports?.map(i => i.modulePath));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// 2. Set up module resolution
|
|
272
|
+
// Use full file path for package resolution
|
|
273
|
+
// Handle cases where filename is already an absolute path
|
|
274
|
+
let fullFilePath;
|
|
275
|
+
if (filename.includes('/') || filename.includes('\\')) {
|
|
276
|
+
// filename is already a path (absolute or relative with directories)
|
|
277
|
+
fullFilePath = filename;
|
|
278
|
+
} else if (currentPath) {
|
|
279
|
+
// filename is just a name, combine with currentPath
|
|
280
|
+
fullFilePath = `${currentPath}/${filename}`;
|
|
281
|
+
} else {
|
|
282
|
+
fullFilePath = filename;
|
|
283
|
+
}
|
|
284
|
+
this.moduleResolver.setCurrentFilePath(fullFilePath);
|
|
285
|
+
|
|
286
|
+
// 3. Load builtins (auto-imported functions like sin, cos, etc.)
|
|
287
|
+
if (this.debug) console.log('[StoneEngine] Step 3a: Loading builtins...');
|
|
288
|
+
const builtinsExports = await this.loadBuiltins();
|
|
289
|
+
const combinedVariables = { ...builtinsExports, ...variables };
|
|
290
|
+
if (this.debug) {
|
|
291
|
+
console.log('[StoneEngine] Builtins loaded:', Object.keys(builtinsExports).length, 'exports');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// 3b. Load module dependencies
|
|
295
|
+
if (this.debug) console.log('[StoneEngine] Step 3b: Loading module dependencies...');
|
|
296
|
+
const hasImports = ast.imports && ast.imports.length > 0;
|
|
297
|
+
if (hasImports) {
|
|
298
|
+
await this.loadModuleDependencies(ast, fullFilePath);
|
|
299
|
+
|
|
300
|
+
// Execute imported modules
|
|
301
|
+
for (const importNode of ast.imports) {
|
|
302
|
+
const modulePath = importNode.modulePath;
|
|
303
|
+
if (this.debug) console.log('[StoneEngine] Processing import:', modulePath);
|
|
304
|
+
if (!this.moduleResolver.isBuiltinModule(modulePath)) {
|
|
305
|
+
if (this.debug) console.log('[StoneEngine] Executing module:', modulePath);
|
|
306
|
+
await this.executeModule(modulePath, { variables: combinedVariables });
|
|
307
|
+
} else {
|
|
308
|
+
if (this.debug) console.log('[StoneEngine] Skipping JS builtin:', modulePath);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// 4. Type check
|
|
314
|
+
if (this.debug) console.log('[StoneEngine] Step 4: Type checking...');
|
|
315
|
+
const typeResult = this.typeCheck(ast);
|
|
316
|
+
if (!typeResult.success) {
|
|
317
|
+
const errors = typeResult.errors.map(err => {
|
|
318
|
+
const loc = err.location
|
|
319
|
+
? `[${err.location.line}:${err.location.column}] `
|
|
320
|
+
: '';
|
|
321
|
+
return `${loc}${err.message}`;
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// Report type errors via adapter
|
|
325
|
+
for (const err of typeResult.errors) {
|
|
326
|
+
adapter.error(err.message, err.location);
|
|
327
|
+
}
|
|
328
|
+
adapter.finish(false, null);
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
success: false,
|
|
332
|
+
error: 'Type check failed',
|
|
333
|
+
errors: typeResult.errors,
|
|
334
|
+
output: errors,
|
|
335
|
+
terminalsData: {},
|
|
336
|
+
duration: Date.now() - startTime,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// 5. Execute using bytecode VM (unified execution path)
|
|
341
|
+
if (this.debug) console.log('[StoneEngine] Step 5: Executing with VM...');
|
|
342
|
+
const result = await this.executeWithVM(ast, {
|
|
343
|
+
maxIterations,
|
|
344
|
+
outputAdapter: adapter,
|
|
345
|
+
externalVars: combinedVariables,
|
|
346
|
+
extensions: typeResult.extensions, // Pass extension info to compiler
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
if (this.debug) {
|
|
350
|
+
console.log('[StoneEngine] Execution complete:');
|
|
351
|
+
console.log('[StoneEngine] success:', result.success);
|
|
352
|
+
console.log('[StoneEngine] error:', result.error);
|
|
353
|
+
console.log('[StoneEngine] output:', result.output?.length, 'lines');
|
|
354
|
+
console.log('[StoneEngine] terminalsData keys:', Object.keys(result.terminalsData || {}));
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
...result,
|
|
359
|
+
duration: Date.now() - startTime,
|
|
360
|
+
};
|
|
361
|
+
} catch (error) {
|
|
362
|
+
// Report error via adapter
|
|
363
|
+
adapter.error(error.message, null);
|
|
364
|
+
adapter.finish(false, error);
|
|
365
|
+
|
|
366
|
+
// Include error in output and terminalsData for display
|
|
367
|
+
return {
|
|
368
|
+
success: false,
|
|
369
|
+
error: error.message,
|
|
370
|
+
output: [`Error: ${error.message}`],
|
|
371
|
+
terminalsData: {
|
|
372
|
+
main: {
|
|
373
|
+
type: 'console',
|
|
374
|
+
config: { title: 'Main', max_lines: 200 },
|
|
375
|
+
lines: [`Error: ${error.message}`],
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
duration: Date.now() - startTime,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Execute using bytecode VM
|
|
385
|
+
* @param {Object} ast - Parsed AST
|
|
386
|
+
* @param {Object} options - Execution options
|
|
387
|
+
* @param {Object} options.externalVars - External variables to inject
|
|
388
|
+
* @param {OutputAdapter} options.outputAdapter - Output adapter
|
|
389
|
+
* @returns {Promise<Object>} Execution result
|
|
390
|
+
*/
|
|
391
|
+
async executeWithVM(ast, options = {}) {
|
|
392
|
+
const adapter = options.outputAdapter || this.outputAdapter;
|
|
393
|
+
const externalVars = options.externalVars || {};
|
|
394
|
+
const extensions = options.extensions || new Set();
|
|
395
|
+
|
|
396
|
+
try {
|
|
397
|
+
// Get external variable names for compiler
|
|
398
|
+
const externalVarNames = Object.keys(externalVars);
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
// Create a new compiler instance with external var names
|
|
402
|
+
const compiler = new Compiler({
|
|
403
|
+
externalVarNames
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// Compile AST to bytecode, passing extensions from type checking
|
|
407
|
+
const compiled = compiler.compile(ast, { extensions });
|
|
408
|
+
|
|
409
|
+
if (!compiled.success) {
|
|
410
|
+
const errorMsg = compiled.errors.map(e => e.message).join('\n');
|
|
411
|
+
if (adapter) {
|
|
412
|
+
adapter.error(errorMsg, null);
|
|
413
|
+
adapter.finish(false, null);
|
|
414
|
+
}
|
|
415
|
+
// Include error in output and terminalsData for display
|
|
416
|
+
return {
|
|
417
|
+
success: false,
|
|
418
|
+
error: errorMsg,
|
|
419
|
+
output: [`Error: ${errorMsg}`],
|
|
420
|
+
result: null,
|
|
421
|
+
terminalsData: {
|
|
422
|
+
main: {
|
|
423
|
+
type: 'console',
|
|
424
|
+
config: { title: 'Main', max_lines: 200 },
|
|
425
|
+
lines: [`Error: ${errorMsg}`],
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Reset VM modules before loading (indices are relative to each execution)
|
|
432
|
+
this.vm.modules = [];
|
|
433
|
+
this.vm.moduleLocals = [];
|
|
434
|
+
|
|
435
|
+
// Load imported modules into VM before execution
|
|
436
|
+
if (compiled.imports && compiled.imports.length > 0) {
|
|
437
|
+
for (const importInfo of compiled.imports) {
|
|
438
|
+
const modulePath = importInfo.module;
|
|
439
|
+
const importedNames = importInfo.names;
|
|
440
|
+
|
|
441
|
+
// Get module exports (from cache or builtin)
|
|
442
|
+
let moduleExports;
|
|
443
|
+
|
|
444
|
+
// Special handling for 'plots' module - needs runtime terminal constructors
|
|
445
|
+
if (modulePath === 'plots') {
|
|
446
|
+
// Create terminal constructors with the current adapter
|
|
447
|
+
const terminalConstructors = createTerminalConstructors({
|
|
448
|
+
outputAdapter: adapter,
|
|
449
|
+
terminalCounter: this.terminalCounter || 0
|
|
450
|
+
});
|
|
451
|
+
moduleExports = {
|
|
452
|
+
graph2d: terminalConstructors.graph2d,
|
|
453
|
+
graph3d: terminalConstructors.graph3d,
|
|
454
|
+
console_terminal: terminalConstructors.console_terminal
|
|
455
|
+
};
|
|
456
|
+
} else if (this.moduleResolver.isBuiltinModule(modulePath)) {
|
|
457
|
+
moduleExports = this.moduleResolver.getBuiltinModule(modulePath);
|
|
458
|
+
if (this.debug) {
|
|
459
|
+
console.log(`[executeWithVM] Builtin module '${modulePath}':`, Object.keys(moduleExports || {}));
|
|
460
|
+
}
|
|
461
|
+
} else {
|
|
462
|
+
const cachedModule = this.moduleResolver.cache.get(modulePath);
|
|
463
|
+
moduleExports = cachedModule?.exports;
|
|
464
|
+
if (this.debug) {
|
|
465
|
+
console.log(`[executeWithVM] Cached module '${modulePath}':`, Object.keys(moduleExports || {}));
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (!moduleExports) {
|
|
470
|
+
throw new Error(`Module '${modulePath}' not found or not loaded`);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Create a module structure for the VM
|
|
474
|
+
const exportsMap = new Map();
|
|
475
|
+
const moduleIdx = this.vm.modules.length; // Index where this module will be stored
|
|
476
|
+
|
|
477
|
+
// Get cached module data
|
|
478
|
+
const cachedModule = this.moduleResolver.cache.get(modulePath);
|
|
479
|
+
const allLocals = cachedModule?.allLocals || [];
|
|
480
|
+
const exportSlots = cachedModule?.exportSlots || {};
|
|
481
|
+
const compiledExports = cachedModule?.compiledExports || new Map();
|
|
482
|
+
|
|
483
|
+
// Use allLocals for moduleStorage to preserve original slot positions for upvalues
|
|
484
|
+
const moduleStorage = allLocals.length > 0 ? [...allLocals] : [];
|
|
485
|
+
|
|
486
|
+
// Handle namespace imports (import math) vs selective imports (import x from math)
|
|
487
|
+
const isNamespaceImport = importedNames.length === 1 && importedNames[0] === '*';
|
|
488
|
+
|
|
489
|
+
if (isNamespaceImport) {
|
|
490
|
+
// For namespace imports, create an object with all exports
|
|
491
|
+
const namespaceObj = { ...moduleExports };
|
|
492
|
+
exportsMap.set('*', { slot: 0 });
|
|
493
|
+
moduleStorage[0] = namespaceObj;
|
|
494
|
+
|
|
495
|
+
// Tag any CompiledFunctions in the namespace
|
|
496
|
+
for (const value of Object.values(namespaceObj)) {
|
|
497
|
+
if (value instanceof CompiledFunction) {
|
|
498
|
+
this._tagFunctionWithModuleIdx(value, moduleIdx);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
} else {
|
|
502
|
+
// Selective imports - original logic
|
|
503
|
+
for (let i = 0; i < importedNames.length; i++) {
|
|
504
|
+
const name = importedNames[i];
|
|
505
|
+
const value = moduleExports[name];
|
|
506
|
+
if (value === undefined && !(name in moduleExports)) {
|
|
507
|
+
throw new Error(`'${name}' is not exported from module '${modulePath}'`);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Use full export info from compiledExports if available (preserves overload info)
|
|
511
|
+
const fullExportInfo = compiledExports.get(name);
|
|
512
|
+
if (fullExportInfo) {
|
|
513
|
+
exportsMap.set(name, fullExportInfo);
|
|
514
|
+
} else {
|
|
515
|
+
// Fall back to cached export slot if available, otherwise use sequential slot
|
|
516
|
+
const originalSlot = exportSlots[name] !== undefined ? exportSlots[name] : i;
|
|
517
|
+
exportsMap.set(name, { slot: originalSlot });
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// If no allLocals, store at sequential slot
|
|
521
|
+
if (allLocals.length === 0) {
|
|
522
|
+
moduleStorage[i] = value;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Tag CompiledFunctions with moduleIdx (including nested functions in constants)
|
|
526
|
+
if (value instanceof CompiledFunction) {
|
|
527
|
+
this._tagFunctionWithModuleIdx(value, moduleIdx);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Tag all functions in allLocals with moduleIdx
|
|
533
|
+
for (const value of moduleStorage) {
|
|
534
|
+
if (value instanceof CompiledFunction) {
|
|
535
|
+
this._tagFunctionWithModuleIdx(value, moduleIdx);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Load into VM
|
|
540
|
+
this.vm.modules.push({
|
|
541
|
+
id: modulePath,
|
|
542
|
+
exports: exportsMap,
|
|
543
|
+
initializer: null,
|
|
544
|
+
});
|
|
545
|
+
this.vm.moduleLocals.push(moduleStorage);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Execute bytecode with adapter and external variables
|
|
550
|
+
const vmResult = await this.vm.execute(compiled, {
|
|
551
|
+
outputAdapter: adapter,
|
|
552
|
+
externalVars
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
// Get output from adapter if it's a BufferedOutputAdapter, otherwise from VM
|
|
556
|
+
const outputFromAdapter = adapter?.output || [];
|
|
557
|
+
const terminalsFromAdapter = adapter?.terminalsData || {};
|
|
558
|
+
let combinedOutput = vmResult.output?.length > 0 ? vmResult.output : outputFromAdapter;
|
|
559
|
+
|
|
560
|
+
// If execution failed, add error to output
|
|
561
|
+
if (!vmResult.success && vmResult.error) {
|
|
562
|
+
combinedOutput = [...combinedOutput, `Error: ${vmResult.error}`];
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Build terminalsData - prefer adapter's data if available
|
|
566
|
+
let terminalsData = Object.keys(terminalsFromAdapter).length > 0
|
|
567
|
+
? terminalsFromAdapter
|
|
568
|
+
: (combinedOutput.length > 0 ? {
|
|
569
|
+
main: {
|
|
570
|
+
type: 'console',
|
|
571
|
+
config: { title: 'Main', max_lines: 200 },
|
|
572
|
+
lines: combinedOutput,
|
|
573
|
+
}
|
|
574
|
+
} : {});
|
|
575
|
+
|
|
576
|
+
// Ensure errors are in terminalsData even if no other output
|
|
577
|
+
if (!vmResult.success && vmResult.error && Object.keys(terminalsData).length === 0) {
|
|
578
|
+
terminalsData = {
|
|
579
|
+
main: {
|
|
580
|
+
type: 'console',
|
|
581
|
+
config: { title: 'Main', max_lines: 200 },
|
|
582
|
+
lines: [`Error: ${vmResult.error}`],
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Capture all locals for module caching (needed for closure upvalues)
|
|
588
|
+
const localCount = compiled.main?.localCount || 0;
|
|
589
|
+
const allLocals = [];
|
|
590
|
+
for (let i = 0; i < localCount; i++) {
|
|
591
|
+
allLocals[i] = this.vm.locals[i];
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return {
|
|
595
|
+
success: vmResult.success,
|
|
596
|
+
result: vmResult.result,
|
|
597
|
+
output: combinedOutput,
|
|
598
|
+
error: vmResult.error,
|
|
599
|
+
variables: {},
|
|
600
|
+
terminalsData,
|
|
601
|
+
exports: vmResult.exports, // Pass through exports from VM
|
|
602
|
+
exportSlots: vmResult.exportSlots, // Original slot positions for exports
|
|
603
|
+
compiledExports: compiled.exports, // Full export info including overload data
|
|
604
|
+
allLocals, // All locals for module caching
|
|
605
|
+
};
|
|
606
|
+
} catch (e) {
|
|
607
|
+
if (adapter) {
|
|
608
|
+
adapter.error(e.message, null);
|
|
609
|
+
adapter.finish(false, e);
|
|
610
|
+
}
|
|
611
|
+
// Add error to terminalsData so it displays in viewport
|
|
612
|
+
const errorTerminalsData = {
|
|
613
|
+
main: {
|
|
614
|
+
type: 'console',
|
|
615
|
+
config: { title: 'Main', max_lines: 200 },
|
|
616
|
+
lines: [`Error: ${e.message}`],
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
return {
|
|
620
|
+
success: false,
|
|
621
|
+
error: e.message,
|
|
622
|
+
output: [`Error: ${e.message}`],
|
|
623
|
+
result: null,
|
|
624
|
+
terminalsData: errorTerminalsData,
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Load module dependencies recursively
|
|
631
|
+
* @param {Object} ast - AST with imports
|
|
632
|
+
* @param {string} currentFilePath - Current file path
|
|
633
|
+
* @param {Set} visited - Visited modules (for cycle detection)
|
|
634
|
+
*/
|
|
635
|
+
async loadModuleDependencies(ast, currentFilePath = null, visited = new Set()) {
|
|
636
|
+
if (currentFilePath) {
|
|
637
|
+
this.moduleResolver.setCurrentFilePath(currentFilePath);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Collect all modules to load (with inferredFilename for directory inference)
|
|
641
|
+
const modulesToLoad = [];
|
|
642
|
+
|
|
643
|
+
for (const importNode of ast.imports || []) {
|
|
644
|
+
modulesToLoad.push({
|
|
645
|
+
modulePath: importNode.modulePath,
|
|
646
|
+
inferredFilename: importNode.inferredFilename || null
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
for (const exportNode of ast.exports || []) {
|
|
651
|
+
if (exportNode.type === 'SelectiveReExportStatement' ||
|
|
652
|
+
exportNode.type === 'NamespaceReExportStatement') {
|
|
653
|
+
modulesToLoad.push({
|
|
654
|
+
modulePath: exportNode.modulePath,
|
|
655
|
+
inferredFilename: null // Re-exports don't use directory inference
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// Load all modules
|
|
661
|
+
for (const { modulePath, inferredFilename } of modulesToLoad) {
|
|
662
|
+
if (visited.has(modulePath)) continue;
|
|
663
|
+
if (this.moduleResolver.isBuiltinModule(modulePath)) continue;
|
|
664
|
+
if (this.moduleResolver.cache.has(modulePath)) continue;
|
|
665
|
+
|
|
666
|
+
visited.add(modulePath);
|
|
667
|
+
|
|
668
|
+
const moduleData = await this.moduleResolver.loadModule(
|
|
669
|
+
modulePath,
|
|
670
|
+
(source, options) => this.parse(source, options),
|
|
671
|
+
inferredFilename // Pass inferredFilename for directory + inferred filename resolution
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
if (moduleData.ast && moduleData.fileId) {
|
|
675
|
+
await this.loadModuleDependencies(moduleData.ast, moduleData.fileId, visited);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Execute a module and cache its exports
|
|
682
|
+
* @param {string} modulePath - Module path
|
|
683
|
+
* @param {Object} options - Execution options
|
|
684
|
+
* @returns {Promise<Object>} Module exports
|
|
685
|
+
*/
|
|
686
|
+
async executeModule(modulePath, options = {}) {
|
|
687
|
+
const cachedModule = this.moduleResolver.cache.get(modulePath);
|
|
688
|
+
if (cachedModule && cachedModule.exports) {
|
|
689
|
+
return cachedModule.exports;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
if (this.moduleResolver.isBuiltinModule(modulePath)) {
|
|
693
|
+
return this.moduleResolver.getBuiltinModule(modulePath);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
if (!cachedModule) {
|
|
697
|
+
await this.moduleResolver.loadModule(
|
|
698
|
+
modulePath,
|
|
699
|
+
(source, opts) => this.parse(source, opts)
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const moduleData = this.moduleResolver.cache.get(modulePath);
|
|
704
|
+
if (!moduleData || !moduleData.ast) {
|
|
705
|
+
throw new Error(`Failed to load module: ${modulePath}`);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Load dependencies
|
|
709
|
+
await this.loadModuleDependencies(moduleData.ast, moduleData.fileId);
|
|
710
|
+
|
|
711
|
+
// Execute import dependencies
|
|
712
|
+
for (const importNode of moduleData.ast.imports || []) {
|
|
713
|
+
const depPath = importNode.modulePath;
|
|
714
|
+
if (!this.moduleResolver.isBuiltinModule(depPath)) {
|
|
715
|
+
const depModule = this.moduleResolver.cache.get(depPath);
|
|
716
|
+
if (depModule && !depModule.exports && depModule.ast) {
|
|
717
|
+
await this.executeModule(depPath, options);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// Type check module
|
|
723
|
+
const typeResult = this.typeCheck(moduleData.ast);
|
|
724
|
+
if (!typeResult.success) {
|
|
725
|
+
const errorMessages = typeResult.errors.map(err => {
|
|
726
|
+
const loc = err.location ? `[${err.location.line}:${err.location.column}] ` : '';
|
|
727
|
+
return `${loc}${err.message}`;
|
|
728
|
+
}).join('\n');
|
|
729
|
+
const error = new Error(`Type check failed in module '${modulePath}':\n${errorMessages}`);
|
|
730
|
+
error.stage = 'type_check';
|
|
731
|
+
error.type = 'type';
|
|
732
|
+
error.module = modulePath;
|
|
733
|
+
error.errors = typeResult.errors;
|
|
734
|
+
throw error;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Execute module using bytecode VM
|
|
738
|
+
const vmResult = await this.executeWithVM(moduleData.ast, {
|
|
739
|
+
externalVars: options.variables || {},
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
if (!vmResult.success) {
|
|
743
|
+
throw new Error(`Error executing module '${modulePath}': ${vmResult.error}`);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
const exports = vmResult.exports || {};
|
|
747
|
+
|
|
748
|
+
// Resolve re-exports to actual values
|
|
749
|
+
await this.resolveReExports(exports, modulePath);
|
|
750
|
+
|
|
751
|
+
moduleData.exports = exports;
|
|
752
|
+
// Cache all locals for closure upvalue capture when module is imported
|
|
753
|
+
moduleData.allLocals = vmResult.allLocals || [];
|
|
754
|
+
// Cache original export slots for correct IMPORT_GET resolution
|
|
755
|
+
moduleData.exportSlots = vmResult.exportSlots || {};
|
|
756
|
+
// Cache full export info for overloaded functions
|
|
757
|
+
moduleData.compiledExports = vmResult.compiledExports || new Map();
|
|
758
|
+
|
|
759
|
+
// Register complex module's arithmetic functions as dynamic overloads
|
|
760
|
+
// This enables operators (+, -, *, /, etc.) to work with complex numbers
|
|
761
|
+
if (modulePath === 'complex') {
|
|
762
|
+
this._registerComplexOverloads(exports, moduleData.allLocals, moduleData.compiledExports);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
return exports;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Register complex module's arithmetic functions as dynamic overloads.
|
|
770
|
+
* This allows the VM's operator handlers to dispatch to Stone functions
|
|
771
|
+
* for complex number operations.
|
|
772
|
+
*/
|
|
773
|
+
_registerComplexOverloads(exports, allLocals, compiledExports) {
|
|
774
|
+
// Arithmetic operations with two Complex arguments
|
|
775
|
+
const binaryOps = [
|
|
776
|
+
{ name: 'add', sig: { in: ['complex', 'complex'], out: 'complex' } },
|
|
777
|
+
{ name: 'sub', sig: { in: ['complex', 'complex'], out: 'complex' } },
|
|
778
|
+
{ name: 'mul', sig: { in: ['complex', 'complex'], out: 'complex' } },
|
|
779
|
+
{ name: 'div', sig: { in: ['complex', 'complex'], out: 'complex' } },
|
|
780
|
+
{ name: 'pow', sig: { in: ['complex', 'complex'], out: 'complex' } },
|
|
781
|
+
];
|
|
782
|
+
|
|
783
|
+
// Unary operations
|
|
784
|
+
const unaryOps = [
|
|
785
|
+
{ name: 'neg', sig: { in: ['complex'], out: 'complex' } },
|
|
786
|
+
];
|
|
787
|
+
|
|
788
|
+
const allOps = [...binaryOps, ...unaryOps];
|
|
789
|
+
|
|
790
|
+
for (const { name, sig } of allOps) {
|
|
791
|
+
const fn = exports[name];
|
|
792
|
+
if (fn instanceof CompiledFunction) {
|
|
793
|
+
registerOverload(name, fn, sig);
|
|
794
|
+
} else {
|
|
795
|
+
// Try to get from compiledExports which has slot info
|
|
796
|
+
const exportInfo = compiledExports.get(name);
|
|
797
|
+
if (exportInfo && allLocals[exportInfo.slot] instanceof CompiledFunction) {
|
|
798
|
+
registerOverload(name, allLocals[exportInfo.slot], sig);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Clear the module cache
|
|
806
|
+
*/
|
|
807
|
+
clearModuleCache() {
|
|
808
|
+
this.moduleResolver.clearCache();
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Resolve re-export metadata to actual values
|
|
813
|
+
* Re-exports are stored as { _isReExport: true, _sourceName, _sourceModule, ... }
|
|
814
|
+
* This method replaces them with the actual exported values from the source modules
|
|
815
|
+
*/
|
|
816
|
+
async resolveReExports(exports, currentModulePath) {
|
|
817
|
+
for (const [exportName, exportValue] of Object.entries(exports)) {
|
|
818
|
+
if (exportValue && exportValue._isReExport) {
|
|
819
|
+
const { _reExportType, _sourceName, _sourceModule } = exportValue;
|
|
820
|
+
|
|
821
|
+
// Source modules should already be loaded and executed at this point
|
|
822
|
+
let sourceModule = this.moduleResolver.cache.get(_sourceModule);
|
|
823
|
+
let sourceExports = sourceModule?.exports;
|
|
824
|
+
|
|
825
|
+
// Check if it's a builtin module (like 'primitives')
|
|
826
|
+
if (!sourceExports && this.moduleResolver.isBuiltinModule(_sourceModule)) {
|
|
827
|
+
sourceExports = this.moduleResolver.getBuiltinModule(_sourceModule);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
if (!sourceExports) {
|
|
831
|
+
throw new Error(`Re-export failed: source module '${_sourceModule}' not found or not executed`);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
if (_reExportType === 'selective') {
|
|
835
|
+
// Get specific export from source module
|
|
836
|
+
if (!(_sourceName in sourceExports)) {
|
|
837
|
+
throw new Error(`Re-export failed: '${_sourceName}' is not exported from '${_sourceModule}'`);
|
|
838
|
+
}
|
|
839
|
+
exports[exportName] = sourceExports[_sourceName];
|
|
840
|
+
} else if (_reExportType === 'namespace') {
|
|
841
|
+
// Re-export entire module as namespace
|
|
842
|
+
exports[exportName] = Object.freeze({ ...sourceExports });
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Load builtins.stn exports (auto-imported primitives)
|
|
850
|
+
*/
|
|
851
|
+
async loadBuiltins() {
|
|
852
|
+
if (this.stnBuiltinsExports) {
|
|
853
|
+
return this.stnBuiltinsExports;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
try {
|
|
857
|
+
await this.moduleResolver.loadModule(
|
|
858
|
+
'builtins',
|
|
859
|
+
(source, opts) => this.parse(source, opts)
|
|
860
|
+
);
|
|
861
|
+
|
|
862
|
+
const exports = await this.executeModule('builtins', {});
|
|
863
|
+
this.stnBuiltinsExports = exports || {};
|
|
864
|
+
|
|
865
|
+
if (this.debug) {
|
|
866
|
+
console.log('Loaded builtins.stn exports:', Object.keys(this.stnBuiltinsExports));
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
return this.stnBuiltinsExports;
|
|
870
|
+
} catch (error) {
|
|
871
|
+
if (this.debug) {
|
|
872
|
+
console.error('Failed to load builtins.stn:', error);
|
|
873
|
+
}
|
|
874
|
+
return {};
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
export default StoneEngine;
|