jslike 1.3.0 → 1.4.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/src/index.js DELETED
@@ -1,186 +0,0 @@
1
- // Use bundled Acorn parser for zero runtime dependencies
2
- import { parse as acornParse } from './parser.js';
3
- import { Interpreter } from './interpreter/interpreter.js';
4
- import { Environment, ReturnValue } from './runtime/environment.js';
5
- import { createGlobalEnvironment } from './runtime/builtins.js';
6
-
7
- // Helper to detect if code contains module syntax or top-level await
8
- function containsModuleSyntax(code) {
9
- // Trigger module mode for:
10
- // 1. import/export statements
11
- // 2. Top-level await (await not inside a function)
12
- if (/^\s*(import|export)\s+/m.test(code)) {
13
- return true;
14
- }
15
-
16
- // Check for top-level await (simple heuristic: await at start of line or after statement)
17
- // This isn't perfect but handles common cases, including await in template literals
18
- // Also matches await after operators like || or && or = or inside parentheses
19
- if (/^await\s+/m.test(code) || /[;\n{(|&=]\s*await\s+/.test(code)) {
20
- return true;
21
- }
22
-
23
- return false;
24
- }
25
-
26
-
27
- export function parse(code, options = {}) {
28
- // Determine sourceType: use 'module' ONLY if explicitly requested or if code has imports/exports
29
- // Default to 'script' for better compatibility with labeled statements
30
- let sourceType = options.sourceType || 'script';
31
- if (!options.sourceType && containsModuleSyntax(code)) {
32
- sourceType = 'module';
33
- }
34
-
35
- // Parse with Acorn
36
- try {
37
- return acornParse(code, {
38
- ecmaVersion: 2022, // Support ES2022 features (including top-level await)
39
- sourceType: sourceType,
40
- locations: true, // Track source locations for better error messages
41
- allowReturnOutsideFunction: true // Allow top-level return statements
42
- });
43
- } catch (error) {
44
- // Reformat error message for consistency
45
- throw new SyntaxError(
46
- `Parse error at line ${error.loc?.line || '?'}: ${error.message}`
47
- );
48
- }
49
- }
50
-
51
- // Helper to detect if AST contains import/export declarations
52
- function containsModuleDeclarations(node) {
53
- if (!node || typeof node !== 'object') return false;
54
-
55
- if (node.type === 'ImportDeclaration' ||
56
- node.type === 'ExportNamedDeclaration' ||
57
- node.type === 'ExportDefaultDeclaration' ||
58
- node.type === 'ExportAllDeclaration') {
59
- return true;
60
- }
61
-
62
- // Check Program body for module declarations
63
- if (node.type === 'Program' && node.body) {
64
- for (const statement of node.body) {
65
- if (containsModuleDeclarations(statement)) return true;
66
- }
67
- }
68
-
69
- return false;
70
- }
71
-
72
- // Helper to detect if AST contains top-level await expressions
73
- function containsTopLevelAwait(node) {
74
- if (!node || typeof node !== 'object') return false;
75
-
76
- if (node.type === 'AwaitExpression') return true;
77
-
78
- // Don't recurse into function bodies (they handle their own await)
79
- if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {
80
- return false;
81
- }
82
-
83
- // Check all properties
84
- for (const key in node) {
85
- if (key === 'loc' || key === 'range') continue;
86
- const value = node[key];
87
- if (Array.isArray(value)) {
88
- for (const item of value) {
89
- if (containsTopLevelAwait(item)) return true;
90
- }
91
- } else if (typeof value === 'object') {
92
- if (containsTopLevelAwait(value)) return true;
93
- }
94
- }
95
-
96
- return false;
97
- }
98
-
99
- export async function execute(code, env = null, options = {}) {
100
- // Parse the code
101
- const ast = parse(code, options);
102
-
103
- // Create global environment if not provided
104
- if (!env) {
105
- env = createGlobalEnvironment(new Environment());
106
- }
107
-
108
- // Create a child environment for user code to allow shadowing of built-ins
109
- // This prevents conflicts when user code declares variables with same names as stdlib functions
110
- const userEnv = env.extend();
111
-
112
- // Create interpreter with module resolver and abort signal if provided
113
- const interpreter = new Interpreter(env, {
114
- moduleResolver: options.moduleResolver,
115
- abortSignal: options.abortSignal
116
- });
117
-
118
- // Use async evaluation if:
119
- // 1. Explicitly requested module mode
120
- // 2. AST contains import/export declarations
121
- // 3. Code contains top-level await
122
- const needsAsync = options.sourceType === 'module' ||
123
- containsModuleDeclarations(ast) ||
124
- containsTopLevelAwait(ast);
125
-
126
- if (needsAsync) {
127
- const result = await interpreter.evaluateAsync(ast, userEnv);
128
- return result instanceof ReturnValue ? result.value : result;
129
- } else {
130
- const result = interpreter.evaluate(ast, userEnv);
131
- return result instanceof ReturnValue ? result.value : result;
132
- }
133
- }
134
-
135
- export function createEnvironment() {
136
- return createGlobalEnvironment(new Environment());
137
- }
138
-
139
- // Export utility functions for CLI tools
140
- export const isTopLevelAwait = containsModuleSyntax;
141
-
142
- export { Interpreter } from './interpreter/interpreter.js';
143
- export { Environment } from './runtime/environment.js';
144
- export { WangInterpreter, InMemoryModuleResolver } from './interpreter/index.js';
145
-
146
- /**
147
- * Abstract base class for module resolution
148
- * Extend this class to implement custom module loading strategies
149
- */
150
- export class ModuleResolver {
151
- /**
152
- * Resolve a module and return its code
153
- * @param {string} modulePath - The module path to resolve
154
- * @param {string} [fromPath] - The path of the importing module
155
- * @returns {Promise<ModuleResolution>} The resolved module
156
- */
157
- async resolve(modulePath, fromPath) {
158
- throw new Error('ModuleResolver.resolve() must be implemented by subclass');
159
- }
160
-
161
- /**
162
- * Check if a module exists
163
- * @param {string} modulePath - The module path to check
164
- * @param {string} [fromPath] - The path of the importing module
165
- * @returns {Promise<boolean>} Whether the module exists
166
- */
167
- async exists(modulePath, fromPath) {
168
- throw new Error('ModuleResolver.exists() must be implemented by subclass');
169
- }
170
-
171
- /**
172
- * List available modules
173
- * @param {string} [prefix] - Optional prefix to filter modules
174
- * @returns {Promise<string[]>} List of module paths
175
- */
176
- async list(prefix) {
177
- throw new Error('ModuleResolver.list() must be implemented by subclass');
178
- }
179
- }
180
-
181
- /**
182
- * @typedef {Object} ModuleResolution
183
- * @property {string} code - The module source code
184
- * @property {string} path - The resolved module path
185
- * @property {any} [metadata] - Optional metadata about the module
186
- */
@@ -1,201 +0,0 @@
1
- /**
2
- * WangInterpreter compatibility adapter for JSLike
3
- * Maps Wang's interpreter API to JSLike's API
4
- */
5
-
6
- import { execute, createEnvironment } from '../index.js';
7
-
8
- export class WangInterpreter {
9
- constructor(options = {}) {
10
- this.options = options;
11
- this.moduleResolver = options.moduleResolver;
12
- this.functions = options.functions || {};
13
- // Create a persistent environment for setVariable support
14
- this.persistentEnv = null;
15
- }
16
-
17
- setVariable(name, value) {
18
- // If persistent environment doesn't exist, create it
19
- if (!this.persistentEnv) {
20
- this.persistentEnv = createEnvironment();
21
-
22
- // Add custom functions
23
- for (const [funcName, func] of Object.entries(this.functions)) {
24
- if (this.persistentEnv.has(funcName)) {
25
- this.persistentEnv.set(funcName, func);
26
- } else {
27
- this.persistentEnv.define(funcName, func);
28
- }
29
- }
30
- }
31
-
32
- // Set the variable in persistent environment
33
- if (this.persistentEnv.has(name)) {
34
- this.persistentEnv.set(name, value);
35
- } else {
36
- this.persistentEnv.define(name, value);
37
- }
38
- }
39
-
40
- // Alias for setVariable - specifically for binding functions
41
- bindFunction(name, func) {
42
- return this.setVariable(name, func);
43
- }
44
-
45
- // Expose global context for accessing variables after execution
46
- get globalContext() {
47
- return {
48
- variables: this.persistentEnv?.vars || new Map()
49
- };
50
- }
51
-
52
- createExecutionEnvironment() {
53
- // Create fresh environment for each execution
54
- const env = createEnvironment();
55
-
56
- // Add custom functions to environment, overriding built-ins if needed
57
- for (const [name, func] of Object.entries(this.functions)) {
58
- // Use set() to override existing variables instead of define()
59
- if (env.has(name)) {
60
- env.set(name, func);
61
- } else {
62
- env.define(name, func);
63
- }
64
- }
65
-
66
- return env;
67
- }
68
-
69
- async execute(code, initialEnv = undefined, userOptions = {}) {
70
- // Use provided environment, persistent environment, or create fresh one
71
- const env = initialEnv || this.persistentEnv || this.createExecutionEnvironment();
72
-
73
- // Setup console capture if withMetadata option is enabled
74
- const withMetadata = userOptions.withMetadata || false;
75
- const capturedLogs = [];
76
-
77
- if (withMetadata) {
78
- // Override log/warn/error functions to capture calls
79
- const createCapture = (type) => {
80
- return (...args) => {
81
- capturedLogs.push({
82
- type,
83
- args,
84
- timestamp: Date.now()
85
- });
86
- // Still output to console
87
- console[type](...args);
88
- return undefined;
89
- };
90
- };
91
-
92
- env.set('log', createCapture('log'));
93
- env.set('warn', createCapture('warn'));
94
- env.set('error', createCapture('error'));
95
- }
96
-
97
- // Check if code contains top-level return statements
98
- // Need to detect `return` at start of line, but NOT inside function bodies
99
- // Simple heuristic: check if braces are balanced before the return statement
100
- const hasTopLevelReturn = this.hasTopLevelReturn(code);
101
-
102
- // Prepare execution options
103
- const options = {
104
- moduleResolver: this.moduleResolver
105
- // sourceType will be auto-detected from code
106
- };
107
-
108
- let result;
109
- if (hasTopLevelReturn) {
110
- // Wrap code in async IIFE to support top-level return statements
111
- const wrappedCode = `(async function() { ${code} })()`;
112
- try {
113
- result = await execute(wrappedCode, env, options);
114
- } catch (error) {
115
- throw error;
116
- }
117
- } else {
118
- // Execute directly - JSLike returns last evaluated expression
119
- try {
120
- result = await execute(code, env, options);
121
- } catch (error) {
122
- throw error;
123
- }
124
- }
125
-
126
- // Return result with metadata if requested
127
- if (withMetadata) {
128
- return {
129
- result,
130
- metadata: {
131
- logs: capturedLogs
132
- }
133
- };
134
- }
135
-
136
- return result;
137
- }
138
-
139
- hasTopLevelReturn(code) {
140
- const lines = code.split('\n');
141
- let braceDepth = 0;
142
-
143
- for (const line of lines) {
144
- // Check if this line has a return statement at the start (ignoring whitespace)
145
- const trimmed = line.trim();
146
- if (trimmed.startsWith('return ') || trimmed === 'return') {
147
- // If we're at brace depth 0, it's a top-level return
148
- if (braceDepth === 0) {
149
- return true;
150
- }
151
- }
152
-
153
- // Count braces on this line (after checking for return)
154
- for (const char of line) {
155
- if (char === '{') braceDepth++;
156
- if (char === '}') braceDepth--;
157
- }
158
- }
159
-
160
- return false;
161
- }
162
-
163
- async executeModule(code) {
164
- // Same as execute for now - modules are handled by execute()
165
- return await this.execute(code);
166
- }
167
- }
168
-
169
- export class InMemoryModuleResolver {
170
- constructor() {
171
- this.modules = new Map();
172
- }
173
-
174
- addModule(name, code, metadata) {
175
- this.modules.set(name, { code, metadata });
176
- }
177
-
178
- async resolve(modulePath, fromPath) {
179
- const module = this.modules.get(modulePath);
180
- if (!module) {
181
- return null;
182
- }
183
- return {
184
- code: typeof module === 'string' ? module : module.code,
185
- path: modulePath,
186
- metadata: module.metadata
187
- };
188
- }
189
-
190
- async exists(modulePath, fromPath) {
191
- return this.modules.has(modulePath);
192
- }
193
-
194
- async list(prefix) {
195
- const paths = Array.from(this.modules.keys());
196
- if (prefix) {
197
- return paths.filter(p => p.startsWith(prefix));
198
- }
199
- return paths;
200
- }
201
- }