jslike 1.6.1 → 1.6.3

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 CHANGED
@@ -1,77 +1,246 @@
1
1
  # JSLike
2
2
 
3
- **Production-ready JavaScript interpreter** with full ES6+ support using Acorn parser. JSLike executes real JavaScript code with a custom runtime environment, supporting modern ES6+ features including classes, destructuring, template literals, and more.
3
+ **Production-ready JavaScript interpreter** with full ES6+ support, native JSX parsing, and React integration. JSLike executes real JavaScript code with a custom runtime environment, supporting modern ES6+ features including classes, destructuring, template literals, JSX, and more.
4
4
 
5
5
  ## Features
6
6
 
7
- - **✅ Production-Ready** - Handles files of any size, tested on 100+ line programs
7
+ - **Production-Ready** - Handles files of any size, tested with 1000+ tests
8
8
  - **Full ES6+ JavaScript Support** - Classes, destructuring, template literals, spread operator, arrow functions
9
+ - **Native JSX Support** - Parse and execute JSX without pre-transformation
10
+ - **React Integration** - Import React hooks and components via moduleResolver
11
+ - **CSP-Safe** - Tree-walking interpreter, no eval() or new Function()
9
12
  - **ASI (Automatic Semicolon Insertion)** - Write JavaScript naturally without mandatory semicolons
10
13
  - **Acorn Parser** - Battle-tested parser used by webpack, ESLint, and major tools
11
- - **Custom Interpreter** - ~2000 LOC tree-walking interpreter with proper scoping
12
- - **Zero Runtime Dependencies** - Only requires Node.js to run
13
- - **REPL** - Interactive development environment
14
- - **CLI** - Run `.js` files directly
14
+ - **Zero Runtime Dependencies** - Parser bundled, no npm install needed after build
15
+ - **REPL & CLI** - Interactive development and direct file execution
15
16
 
16
17
  ## Installation
17
18
 
18
19
  ```bash
19
- # For development (includes acorn for building)
20
+ npm install jslike
21
+ ```
22
+
23
+ Or for development:
24
+
25
+ ```bash
26
+ git clone https://github.com/artpar/jslike.git
27
+ cd jslike
20
28
  npm install
21
29
  npm run build
30
+ ```
22
31
 
23
- # For end users - zero runtime dependencies!
24
- # Just copy the src/ and bin/ directories and run:
25
- node bin/jslike.js examples/hello.js
32
+ ## Quick Start
33
+
34
+ ### Programmatic Usage
35
+
36
+ ```javascript
37
+ import { execute, createEnvironment } from 'jslike';
38
+
39
+ // Simple execution
40
+ const result = await execute(`
41
+ const greeting = "Hello";
42
+ const name = "World";
43
+ greeting + ", " + name + "!"
44
+ `);
45
+ console.log(result); // "Hello, World!"
26
46
  ```
27
47
 
28
- ### For End Users
48
+ ### JSX Support
49
+
50
+ ```javascript
51
+ import { execute } from 'jslike';
29
52
 
30
- The built project has **zero runtime dependencies**! The Acorn parser (~225KB) is bundled into `src/parser.js`.
53
+ const element = await execute(`
54
+ function Button({ label, onClick }) {
55
+ return <button className="btn" onClick={onClick}>{label}</button>;
56
+ }
31
57
 
32
- ```bash
33
- # No npm install needed after build!
34
- node bin/jslike.js myfile.js
58
+ <div className="container">
59
+ <h1>Welcome</h1>
60
+ <Button label="Click me" onClick={() => console.log('clicked')} />
61
+ </div>
62
+ `);
63
+
64
+ // element is a React-compatible element object:
65
+ // { $$typeof: Symbol(react.element), type: 'div', props: {...}, ... }
35
66
  ```
36
67
 
37
- Or install globally:
68
+ ### React Integration
69
+
70
+ ```javascript
71
+ import { execute, createEnvironment } from 'jslike';
72
+ import * as React from 'react';
73
+
74
+ // Create module resolver for React imports
75
+ const moduleResolver = {
76
+ async resolve(modulePath) {
77
+ if (modulePath === 'react') {
78
+ return { exports: React }; // Return native module exports
79
+ }
80
+ return null;
81
+ }
82
+ };
83
+
84
+ // Create environment with React for JSX
85
+ const env = createEnvironment();
86
+ env.define('React', React);
87
+
88
+ // Execute with React hooks
89
+ const component = await execute(`
90
+ import { useState, useEffect } from 'react';
91
+
92
+ function Counter() {
93
+ const [count, setCount] = useState(0);
94
+
95
+ useEffect(() => {
96
+ document.title = \`Count: \${count}\`;
97
+ }, [count]);
98
+
99
+ return (
100
+ <div>
101
+ <p>Count: {count}</p>
102
+ <button onClick={() => setCount(count + 1)}>+</button>
103
+ </div>
104
+ );
105
+ }
106
+
107
+ <Counter />
108
+ `, env, { moduleResolver });
109
+ ```
110
+
111
+ ### CLI Usage
112
+
38
113
  ```bash
39
- npm install -g jslike
40
- jslike myfile.js
114
+ # Run a file
115
+ npx jslike myfile.js
116
+
117
+ # Interactive REPL
118
+ npx jslike --repl
41
119
  ```
42
120
 
43
- ## Usage
121
+ ## Module System
44
122
 
45
- ### Running a file
123
+ JSLike supports ES6 imports with a flexible module resolver:
46
124
 
47
- ```bash
48
- npm start examples/hello.js
49
- # or
50
- ./bin/jslike.js examples/hello.js
125
+ ### Native Module Exports (React, lodash, etc.)
126
+
127
+ ```javascript
128
+ const moduleResolver = {
129
+ async resolve(modulePath) {
130
+ // Return native JavaScript objects directly
131
+ if (modulePath === 'react') {
132
+ return { exports: React };
133
+ }
134
+ if (modulePath === 'lodash') {
135
+ return { exports: _ };
136
+ }
137
+ return null;
138
+ }
139
+ };
51
140
  ```
52
141
 
53
- ### Interactive REPL
142
+ ### Code Modules (parsed and executed)
54
143
 
55
- ```bash
56
- npm run repl
57
- # or
58
- ./bin/repl.js
144
+ ```javascript
145
+ const moduleResolver = {
146
+ async resolve(modulePath) {
147
+ if (modulePath === './utils') {
148
+ return {
149
+ code: `
150
+ export function double(x) { return x * 2; }
151
+ export const PI = 3.14159;
152
+ `
153
+ };
154
+ }
155
+ return null;
156
+ }
157
+ };
59
158
  ```
60
159
 
61
- ### Example REPL session
160
+ ### Import Styles Supported
62
161
 
162
+ ```javascript
163
+ import { useState, useEffect } from 'react'; // Named imports
164
+ import React from 'react'; // Default import
165
+ import * as Utils from './utils'; // Namespace import
63
166
  ```
64
- JSLike REPL v1.0.0
65
- Type JavaScript-like code and press Enter to execute
66
- Press Ctrl+C or Ctrl+D to exit
67
-
68
- > const x = 10;
69
- > const y = 20;
70
- > x + y
71
- 30
72
- > function greet(name) { return "Hello, " + name; }
73
- > greet("World")
74
- Hello, World
167
+
168
+ ## JSX Features
169
+
170
+ ### Basic Elements
171
+
172
+ ```javascript
173
+ <div className="container">Hello World</div>
174
+ <input type="text" disabled />
175
+ <br />
176
+ ```
177
+
178
+ ### Expressions
179
+
180
+ ```javascript
181
+ const name = "World";
182
+ <div>Hello {name}</div>
183
+ <div>{1 + 2 + 3}</div>
184
+ <div>{items.map(item => <span key={item.id}>{item.name}</span>)}</div>
185
+ ```
186
+
187
+ ### Attributes
188
+
189
+ ```javascript
190
+ // String attributes
191
+ <div className="container" id="main">
192
+
193
+ // Expression attributes
194
+ <div className={isActive ? 'active' : 'inactive'}>
195
+
196
+ // Spread attributes
197
+ const props = { className: 'btn', disabled: true };
198
+ <button {...props}>Click</button>
199
+
200
+ // Boolean attributes
201
+ <input disabled /> // Same as disabled={true}
202
+ ```
203
+
204
+ ### Fragments
205
+
206
+ ```javascript
207
+ <>
208
+ <div>First</div>
209
+ <div>Second</div>
210
+ </>
211
+ ```
212
+
213
+ ### Components
214
+
215
+ ```javascript
216
+ // Function components
217
+ function Card({ title, children }) {
218
+ return (
219
+ <div className="card">
220
+ <h2>{title}</h2>
221
+ <div className="card-body">{children}</div>
222
+ </div>
223
+ );
224
+ }
225
+
226
+ // Usage - components are stored as type (React behavior)
227
+ <Card title="Welcome">
228
+ <p>Card content here</p>
229
+ </Card>
230
+
231
+ // To render, call component manually or use React renderer
232
+ Card({ title: "Welcome", children: <p>Content</p> })
233
+ ```
234
+
235
+ ### Member Expression Components
236
+
237
+ ```javascript
238
+ const UI = {
239
+ Button: ({ children }) => <button className="ui-btn">{children}</button>,
240
+ Card: ({ children }) => <div className="ui-card">{children}</div>
241
+ };
242
+
243
+ <UI.Button>Click me</UI.Button>
75
244
  ```
76
245
 
77
246
  ## Language Features
@@ -94,30 +263,80 @@ function add(a, b) {
94
263
 
95
264
  // Arrow functions
96
265
  const multiply = (x, y) => x * y;
97
- const greet = (name) => {
98
- return "Hello, " + name;
99
- };
266
+ const greet = name => `Hello, ${name}`;
267
+
268
+ // Default parameters
269
+ function greet(name = "World") {
270
+ return `Hello, ${name}`;
271
+ }
272
+
273
+ // Rest parameters
274
+ function sum(...numbers) {
275
+ return numbers.reduce((a, b) => a + b, 0);
276
+ }
100
277
  ```
101
278
 
102
- ### Arrays
279
+ ### Classes
103
280
 
104
281
  ```javascript
105
- const numbers = [1, 2, 3, 4, 5];
106
- console.log(numbers[0]); // 1
107
- numbers[5] = 6;
282
+ class Animal {
283
+ constructor(name) {
284
+ this.name = name;
285
+ }
286
+
287
+ speak() {
288
+ return `${this.name} makes a sound`;
289
+ }
290
+ }
291
+
292
+ class Dog extends Animal {
293
+ speak() {
294
+ return `${this.name} barks`;
295
+ }
296
+ }
297
+
298
+ const dog = new Dog("Rex");
299
+ dog.speak(); // "Rex barks"
108
300
  ```
109
301
 
110
- ### Objects
302
+ ### Destructuring
111
303
 
112
304
  ```javascript
113
- const person = {
114
- name: "Bob",
115
- age: 30,
116
- greet: () => "Hello!"
117
- };
305
+ // Object destructuring
306
+ const { name, age } = person;
307
+ const { name: userName, age: userAge } = person;
308
+
309
+ // Array destructuring
310
+ const [first, second, ...rest] = array;
311
+
312
+ // Parameter destructuring
313
+ function greet({ name, age }) {
314
+ return `${name} is ${age}`;
315
+ }
316
+ ```
317
+
318
+ ### Template Literals
319
+
320
+ ```javascript
321
+ const name = "World";
322
+ const greeting = `Hello, ${name}!`;
323
+ const multiline = `
324
+ Line 1
325
+ Line 2
326
+ `;
327
+ ```
328
+
329
+ ### Async/Await
330
+
331
+ ```javascript
332
+ async function fetchData() {
333
+ const response = await fetch(url);
334
+ const data = await response.json();
335
+ return data;
336
+ }
118
337
 
119
- console.log(person.name); // Bob
120
- console.log(person["age"]); // 30
338
+ // Top-level await supported
339
+ const result = await fetchData();
121
340
  ```
122
341
 
123
342
  ### Control Flow
@@ -126,20 +345,26 @@ console.log(person["age"]); // 30
126
345
  // If-else
127
346
  if (x > 10) {
128
347
  console.log("Greater");
348
+ } else if (x === 10) {
349
+ console.log("Equal");
129
350
  } else {
130
- console.log("Smaller or equal");
351
+ console.log("Less");
131
352
  }
132
353
 
133
354
  // Ternary
134
355
  const result = x > 5 ? "yes" : "no";
135
356
 
357
+ // Nullish coalescing
358
+ const value = input ?? "default";
359
+
360
+ // Optional chaining
361
+ const name = user?.profile?.name;
362
+
136
363
  // Switch
137
364
  switch (day) {
138
- case 1:
139
- console.log("Monday");
140
- break;
141
- default:
142
- console.log("Other day");
365
+ case 1: return "Monday";
366
+ case 2: return "Tuesday";
367
+ default: return "Other";
143
368
  }
144
369
  ```
145
370
 
@@ -147,53 +372,30 @@ switch (day) {
147
372
 
148
373
  ```javascript
149
374
  // For loop
150
- for (let i = 0; i < 10; i = i + 1) {
375
+ for (let i = 0; i < 10; i++) {
151
376
  console.log(i);
152
377
  }
153
378
 
154
- // While loop
155
- while (condition) {
156
- // code
379
+ // For-of
380
+ for (const item of array) {
381
+ console.log(item);
157
382
  }
158
383
 
159
- // Do-while
160
- do {
161
- // code
162
- } while (condition);
163
-
164
384
  // For-in
165
- for (let key in object) {
166
- console.log(key);
385
+ for (const key in object) {
386
+ console.log(key, object[key]);
167
387
  }
168
388
 
169
- // For-of
170
- for (let value of array) {
171
- console.log(value);
172
- }
173
- ```
389
+ // While
390
+ while (condition) { /* ... */ }
174
391
 
175
- ### Error Handling
176
-
177
- ```javascript
178
- try {
179
- throw "Error message";
180
- } catch (error) {
181
- console.log("Caught:", error);
182
- } finally {
183
- console.log("Cleanup");
184
- }
392
+ // Do-while
393
+ do { /* ... */ } while (condition);
185
394
  ```
186
395
 
187
- ### Operators
396
+ ## Built-in Objects & Functions
188
397
 
189
- - **Arithmetic**: `+`, `-`, `*`, `/`, `%`, `**`
190
- - **Comparison**: `<`, `>`, `<=`, `>=`, `==`, `!=`, `===`, `!==`
191
- - **Logical**: `&&`, `||`, `!`, `??` (nullish coalescing)
192
- - **Bitwise**: `&`, `|`, `^`, `~`, `<<`, `>>`, `>>>`
193
- - **Assignment**: `=`, `+=`, `-=`, `*=`, `/=`, `%=`
194
- - **Update**: `++`, `--`
195
-
196
- ### Built-in Objects
398
+ ### Standard JavaScript
197
399
 
198
400
  - `console.log()`, `console.error()`, `console.warn()`
199
401
  - `Math.PI`, `Math.sqrt()`, `Math.random()`, etc.
@@ -201,148 +403,174 @@ try {
201
403
  - `Map`, `Set`, `WeakMap`, `WeakSet`
202
404
  - `RegExp`, `Symbol`
203
405
  - `JSON.parse()`, `JSON.stringify()`
406
+ - `Promise`, `Date`, `Error`
204
407
  - `setTimeout()`, `setInterval()`
205
408
  - `parseInt()`, `parseFloat()`, `isNaN()`, `isFinite()`
206
- - `Promise`, `Date`, `Error`
207
409
 
208
- ## Examples
410
+ ### JSX Runtime
411
+
412
+ - `createElement(type, props, ...children)` - Creates React-compatible elements
413
+ - `Fragment` - Symbol for React fragments
209
414
 
210
- See the `examples/` directory for more code samples:
415
+ ### Wang Standard Library
211
416
 
212
- - `hello.js` - Basic hello world
213
- - `functions.js` - Function examples including closures
214
- - `loops.js` - All loop types
215
- - `arrays-objects.js` - Arrays and objects
216
- - `algorithms.js` - Factorial, Fibonacci, prime numbers
217
- - `control-flow.js` - If-else, switch, try-catch
218
- - `comprehensive.js` - Complete feature demonstration
417
+ Utility functions available globally:
219
418
 
220
- ## Development
419
+ ```javascript
420
+ // Array operations
421
+ sort_by(array, key) // Sort by key or function
422
+ group_by(array, key) // Group into object by key
423
+ unique(array) // Remove duplicates
424
+ chunk(array, size) // Split into chunks
425
+ flatten(array, depth) // Flatten nested arrays
426
+ first(array, n) // Get first n items
427
+ last(array, n) // Get last n items
428
+ range(start, end, step) // Generate number sequence
429
+
430
+ // Object operations
431
+ keys(obj) // Object.keys
432
+ values(obj) // Object.values
433
+ entries(obj) // Object.entries
434
+ pick(obj, keys) // Pick specific keys
435
+ omit(obj, keys) // Omit specific keys
436
+ merge(...objects) // Merge objects
437
+ get(obj, path, default) // Deep get with dot notation
438
+ clone(obj) // Deep clone
439
+
440
+ // String operations
441
+ split(str, sep) // Split string
442
+ join(arr, sep) // Join array
443
+ trim(str) // Trim whitespace
444
+ upper(str) // Uppercase
445
+ lower(str) // Lowercase
446
+ capitalize(str) // Capitalize first letter
447
+ truncate(str, len) // Truncate with ellipsis
448
+
449
+ // Type checking
450
+ is_string(val) // Check if string
451
+ is_number(val) // Check if number
452
+ is_array(val) // Check if array
453
+ is_object(val) // Check if object
454
+ is_function(val) // Check if function
455
+ is_empty(val) // Check if empty
456
+
457
+ // Math operations
458
+ sum(array) // Sum of numbers
459
+ avg(array) // Average
460
+ min(array) // Minimum
461
+ max(array) // Maximum
462
+ clamp(num, min, max) // Clamp to range
463
+ round(num, decimals) // Round to decimals
464
+ ```
221
465
 
222
- ### Build Process
466
+ ## API Reference
223
467
 
224
- The build process bundles the Acorn parser into `src/parser.js`:
468
+ ### execute(code, env?, options?)
225
469
 
226
- ```bash
227
- # Bundle Acorn parser (creates self-contained parser.js)
228
- npm run build
470
+ Execute JavaScript code and return the result.
471
+
472
+ ```javascript
473
+ const result = await execute(code, env, {
474
+ moduleResolver, // For import statements
475
+ executionController, // For pause/resume/abort
476
+ abortSignal // For cancellation
477
+ });
229
478
  ```
230
479
 
231
- This creates `src/parser.js` (~225KB) which includes:
232
- - Complete Acorn parser
233
- - No external dependencies at runtime
234
- - ES6 module format
480
+ ### createEnvironment()
235
481
 
236
- ### Debug mode
482
+ Create a new execution environment with built-ins.
237
483
 
238
- ```bash
239
- DEBUG=1 ./bin/jslike.js examples/hello.js
484
+ ```javascript
485
+ const env = createEnvironment();
486
+ env.define('myVar', 42);
487
+ env.define('myFunc', (x) => x * 2);
240
488
  ```
241
489
 
242
- ### Architecture
490
+ ### ModuleResolver
243
491
 
244
- ```
245
- src/
246
- ├── parser.js - Bundled Acorn parser (~225KB, self-contained)
247
- ├── index.js - Main API (parse/execute functions)
248
- ├── interpreter/
249
- │ └── interpreter.js - Tree-walking interpreter (~2000 LOC)
250
- ├── runtime/
251
- │ ├── environment.js - Lexical scoping and closures
252
- │ └── builtins.js - Built-in objects (console, Math, JSON, etc.)
253
- └── ast/
254
- └── nodes.js - AST node constructors (ESTree format)
492
+ Interface for resolving imports:
493
+
494
+ ```javascript
495
+ const moduleResolver = {
496
+ async resolve(modulePath, fromPath) {
497
+ // Return { exports: object } for native modules
498
+ // Return { code: string } for code modules
499
+ // Return null if not found
500
+ },
501
+ async exists(modulePath, fromPath) {
502
+ // Return boolean
503
+ },
504
+ async list(prefix) {
505
+ // Return string[] of module paths
506
+ }
507
+ };
255
508
  ```
256
509
 
257
- ## How It Works
258
-
259
- 1. **Parsing**: Acorn parser processes JavaScript code and builds an ESTree-compatible AST
260
- 2. **Evaluation**: The interpreter walks the AST and executes each node
261
- 3. **Runtime**: Environment manages variable scopes (lexical scoping with closures)
262
- 4. **Built-ins**: Native JavaScript objects (console, Math, JSON, etc.) integrated into runtime
263
-
264
- ## Current Status
265
-
266
- ### ✅ Production-Ready & Fully Implemented
267
-
268
- **ES6+ Language Features**:
269
- - ✅ Variables (var, let, const)
270
- - ✅ Functions (declarations, expressions, arrow functions)
271
- - ✅ **Classes** with constructors, methods, and inheritance
272
- - ✅ **Destructuring** (objects and arrays)
273
- - ✅ **Template literals** with interpolation
274
- - ✅ **Spread operator** for arrays and objects
275
- - ✅ Rest parameters `(...rest)`
276
- - ✅ Object/array shorthand syntax
277
- - ✅ Method shorthand in objects
278
- - ✅ **ASI (Automatic Semicolon Insertion)** - semicolons optional!
279
- - ✅ All operators (arithmetic, logical, bitwise, comparison)
280
- - ✅ Control flow (if/else, switch, try/catch)
281
- - ✅ Loops (for, while, do-while, for-in, for-of)
282
- - ✅ Closures and higher-order functions
283
- - ✅ Proper `this` binding in classes and methods
284
- - ✅ Comments (single-line //, multi-line /* */)
285
- - ✅ **Async/await** with Promises
286
- - ✅ **Regular expressions** (literals and RegExp constructor)
287
- - ✅ Enhanced error messages with suggestions
288
-
289
- **Infrastructure**:
290
- - ✅ Acorn parser - production-grade ES2020 support
291
- - ✅ Tree-walking interpreter (~2000 LOC)
292
- - ✅ Runtime environment with proper lexical scoping
293
- - ✅ Built-in objects (console, Math, JSON, Array, Object, etc.)
294
- - ✅ CLI for running .js files
295
- - ✅ Interactive REPL
296
- - ✅ Handles files of **any size** (tested on 100+ line programs)
297
- - ✅ **No file size limitations** - O(n) parsing performance
298
-
299
- ### Performance
300
-
301
- - ✅ **Fast parsing**: O(n) linear time complexity
302
- - ✅ **Handles large files**: No OOM errors, no timeouts
303
- - ✅ **Production-tested parser**: Acorn is used by webpack, ESLint, Rollup
304
- - ✅ Tested on complex programs with 100+ lines
305
-
306
- ### Not Yet Implemented
307
-
308
- - Generators (function* and yield)
309
- - Proxies and Reflect API
310
- - Module system (import/export between files)
510
+ ### ExecutionController
311
511
 
312
- ## Testing
512
+ Control execution flow:
313
513
 
314
- All programs work, including complex multi-file applications:
514
+ ```javascript
515
+ import { execute, ExecutionController } from 'jslike';
516
+
517
+ const controller = new ExecutionController();
518
+
519
+ // Start execution
520
+ const promise = execute(code, null, { executionController: controller });
521
+
522
+ // Control execution
523
+ controller.pause();
524
+ controller.resume();
525
+ controller.abort();
526
+
527
+ // Check state
528
+ console.log(controller.state); // 'running' | 'paused' | 'completed' | 'aborted'
529
+ ```
530
+
531
+ ## Testing
315
532
 
316
533
  ```bash
317
- # Run test suite
318
- npm test
534
+ npm test # Run all tests
535
+ npm test -- tests/jsx.test.js # Run specific test file
536
+ ```
319
537
 
320
- # Test simple programs
321
- node bin/jslike.js examples/hello.js
322
- node bin/jslike.js examples/functions.js
538
+ **1009 tests** covering:
539
+ - ES6+ language features
540
+ - JSX parsing and execution
541
+ - React integration
542
+ - Module imports
543
+ - Error handling
544
+ - Edge cases
323
545
 
324
- # Test ES6 features
325
- node bin/jslike.js examples/es6-test.js
326
- node bin/jslike.js examples/class-simple.js
546
+ ## Architecture
327
547
 
328
- # Test complex algorithms (100+ lines)
329
- node bin/jslike.js examples/algorithms.js
330
548
  ```
549
+ src/
550
+ ├── parser.js - Bundled Acorn + acorn-jsx (~245KB)
551
+ ├── index.js - Main API (parse/execute)
552
+ ├── interpreter/
553
+ │ └── interpreter.js - Tree-walking interpreter (~2500 LOC)
554
+ ├── runtime/
555
+ │ ├── environment.js - Lexical scoping and closures
556
+ │ ├── builtins.js - Built-in objects and JSX runtime
557
+ │ └── execution-controller.js - Pause/resume/abort
558
+ └── ast/
559
+ └── nodes.js - AST node types
560
+ ```
561
+
562
+ ## Known Limitations
331
563
 
332
- **All 698 tests pass**, covering:
333
- - Variables (const, let, var)
334
- - Function declarations and arrow functions
335
- - Object and array literals
336
- - Destructuring (objects, arrays, parameters)
337
- - Control flow and loops
338
- - Closures and higher-order functions
339
- - Classes, inheritance, and constructors
340
- - Template literals
341
- - Async/await and Promises
342
- - Regular expressions
343
- - Native method calls
344
- - Error handling and edge cases
564
+ - Generator functions (`function*`, `yield`) not supported
565
+ - Tagged template literals not fully supported
566
+ - Class getters/setters not fully supported
567
+ - Proxies and Reflect API not implemented
345
568
 
346
569
  ## License
347
570
 
348
571
  MIT
572
+
573
+ ## Links
574
+
575
+ - [npm package](https://www.npmjs.com/package/jslike)
576
+ - [GitHub repository](https://github.com/artpar/jslike)
package/dist/esm/index.js CHANGED
@@ -133,17 +133,29 @@ export async function execute(code, env = null, options = {}) {
133
133
  controller != null;
134
134
 
135
135
  if (needsAsync) {
136
- const result = await interpreter.evaluateAsync(ast, env);
136
+ let result = await interpreter.evaluateAsync(ast, env);
137
137
  if (controller) {
138
138
  controller._complete();
139
139
  }
140
- return result instanceof ReturnValue ? result.value : result;
140
+ result = result instanceof ReturnValue ? result.value : result;
141
+
142
+ // If result is undefined and code has module declarations, return default export
143
+ if (result === undefined && interpreter.moduleExports?.default !== undefined) {
144
+ return interpreter.moduleExports.default;
145
+ }
146
+ return result;
141
147
  } else {
142
- const result = interpreter.evaluate(ast, env);
148
+ let result = interpreter.evaluate(ast, env);
143
149
  if (controller) {
144
150
  controller._complete();
145
151
  }
146
- return result instanceof ReturnValue ? result.value : result;
152
+ result = result instanceof ReturnValue ? result.value : result;
153
+
154
+ // If result is undefined and code has module declarations, return default export
155
+ if (result === undefined && interpreter.moduleExports?.default !== undefined) {
156
+ return interpreter.moduleExports.default;
157
+ }
158
+ return result;
147
159
  }
148
160
  } catch (e) {
149
161
  // Mark as aborted if that's the error type
@@ -102,7 +102,8 @@ export class WangInterpreter {
102
102
  // Prepare execution options
103
103
  const options = {
104
104
  moduleResolver: this.moduleResolver,
105
- executionController: userOptions.executionController
105
+ executionController: userOptions.executionController,
106
+ abortSignal: userOptions.abortSignal // Pass abort signal to interpreter for cancellation support
106
107
  // sourceType will be auto-detected from code
107
108
  };
108
109
 
@@ -158,10 +158,11 @@ export class Interpreter {
158
158
  return undefined;
159
159
  }
160
160
 
161
- const args = [];
161
+ const rawArgs = [];
162
162
  for (const arg of node.arguments) {
163
- args.push(await this.evaluateAsync(arg, env));
163
+ rawArgs.push(await this.evaluateAsync(arg, env));
164
164
  }
165
+ const args = this.flattenSpreadArgs(rawArgs);
165
166
 
166
167
  if (typeof callee === 'function') {
167
168
  if (thisContext !== undefined) {
@@ -656,6 +657,24 @@ export class Interpreter {
656
657
  return this.normalizeJSXText(node.value);
657
658
  }
658
659
 
660
+ // For SpreadElement - return spread marker for flattenSpreadArgs
661
+ if (node.type === 'SpreadElement') {
662
+ const arg = await this.evaluateAsync(node.argument, env);
663
+ if (Array.isArray(arg)) {
664
+ return { __spread: true, __values: arg };
665
+ }
666
+ if (typeof arg === 'string') {
667
+ return { __spread: true, __values: [...arg] };
668
+ }
669
+ if (arg !== null && arg !== undefined && typeof arg[Symbol.iterator] === 'function') {
670
+ return { __spread: true, __values: [...arg] };
671
+ }
672
+ if (typeof arg === 'object' && arg !== null) {
673
+ return { __spread: true, __values: Object.entries(arg) };
674
+ }
675
+ throw new TypeError('Spread syntax requires an iterable');
676
+ }
677
+
659
678
  // Only leaf nodes should fall through to sync evaluate
660
679
  // These have no sub-expressions that could contain await
661
680
  if (['Literal', 'Identifier', 'BreakStatement', 'ContinueStatement',
package/dist/index.cjs CHANGED
@@ -6589,10 +6589,11 @@ var Interpreter = class _Interpreter {
6589
6589
  if (node.optional && (callee === null || callee === void 0)) {
6590
6590
  return void 0;
6591
6591
  }
6592
- const args = [];
6592
+ const rawArgs = [];
6593
6593
  for (const arg of node.arguments) {
6594
- args.push(await this.evaluateAsync(arg, env));
6594
+ rawArgs.push(await this.evaluateAsync(arg, env));
6595
6595
  }
6596
+ const args = this.flattenSpreadArgs(rawArgs);
6596
6597
  if (typeof callee === "function") {
6597
6598
  if (thisContext !== void 0) {
6598
6599
  return await callee.call(thisContext, ...args);
@@ -6994,6 +6995,22 @@ var Interpreter = class _Interpreter {
6994
6995
  if (node.type === "JSXText") {
6995
6996
  return this.normalizeJSXText(node.value);
6996
6997
  }
6998
+ if (node.type === "SpreadElement") {
6999
+ const arg = await this.evaluateAsync(node.argument, env);
7000
+ if (Array.isArray(arg)) {
7001
+ return { __spread: true, __values: arg };
7002
+ }
7003
+ if (typeof arg === "string") {
7004
+ return { __spread: true, __values: [...arg] };
7005
+ }
7006
+ if (arg !== null && arg !== void 0 && typeof arg[Symbol.iterator] === "function") {
7007
+ return { __spread: true, __values: [...arg] };
7008
+ }
7009
+ if (typeof arg === "object" && arg !== null) {
7010
+ return { __spread: true, __values: Object.entries(arg) };
7011
+ }
7012
+ throw new TypeError("Spread syntax requires an iterable");
7013
+ }
6997
7014
  if ([
6998
7015
  "Literal",
6999
7016
  "Identifier",
@@ -8885,7 +8902,9 @@ var WangInterpreter = class {
8885
8902
  const hasTopLevelReturn = this.hasTopLevelReturn(code);
8886
8903
  const options = {
8887
8904
  moduleResolver: this.moduleResolver,
8888
- executionController: userOptions.executionController
8905
+ executionController: userOptions.executionController,
8906
+ abortSignal: userOptions.abortSignal
8907
+ // Pass abort signal to interpreter for cancellation support
8889
8908
  // sourceType will be auto-detected from code
8890
8909
  };
8891
8910
  let result;
@@ -9215,6 +9234,7 @@ function containsTopLevelAwait(node) {
9215
9234
  return false;
9216
9235
  }
9217
9236
  async function execute(code, env = null, options = {}) {
9237
+ var _a, _b;
9218
9238
  const controller = options.executionController;
9219
9239
  if (controller) {
9220
9240
  controller._start();
@@ -9231,17 +9251,25 @@ async function execute(code, env = null, options = {}) {
9231
9251
  });
9232
9252
  const needsAsync = options.sourceType === "module" || containsModuleDeclarations(ast) || containsTopLevelAwait(ast) || controller != null;
9233
9253
  if (needsAsync) {
9234
- const result = await interpreter.evaluateAsync(ast, env);
9254
+ let result = await interpreter.evaluateAsync(ast, env);
9235
9255
  if (controller) {
9236
9256
  controller._complete();
9237
9257
  }
9238
- return result instanceof ReturnValue ? result.value : result;
9258
+ result = result instanceof ReturnValue ? result.value : result;
9259
+ if (result === void 0 && ((_a = interpreter.moduleExports) == null ? void 0 : _a.default) !== void 0) {
9260
+ return interpreter.moduleExports.default;
9261
+ }
9262
+ return result;
9239
9263
  } else {
9240
- const result = interpreter.evaluate(ast, env);
9264
+ let result = interpreter.evaluate(ast, env);
9241
9265
  if (controller) {
9242
9266
  controller._complete();
9243
9267
  }
9244
- return result instanceof ReturnValue ? result.value : result;
9268
+ result = result instanceof ReturnValue ? result.value : result;
9269
+ if (result === void 0 && ((_b = interpreter.moduleExports) == null ? void 0 : _b.default) !== void 0) {
9270
+ return interpreter.moduleExports.default;
9271
+ }
9272
+ return result;
9245
9273
  }
9246
9274
  } catch (e) {
9247
9275
  if (controller && e.name === "AbortError") {
package/dist/index.d.cts CHANGED
@@ -7323,10 +7323,11 @@ class Interpreter {
7323
7323
  return undefined;
7324
7324
  }
7325
7325
 
7326
- const args = [];
7326
+ const rawArgs = [];
7327
7327
  for (const arg of node.arguments) {
7328
- args.push(await this.evaluateAsync(arg, env));
7328
+ rawArgs.push(await this.evaluateAsync(arg, env));
7329
7329
  }
7330
+ const args = this.flattenSpreadArgs(rawArgs);
7330
7331
 
7331
7332
  if (typeof callee === 'function') {
7332
7333
  if (thisContext !== undefined) {
@@ -7821,6 +7822,24 @@ class Interpreter {
7821
7822
  return this.normalizeJSXText(node.value);
7822
7823
  }
7823
7824
 
7825
+ // For SpreadElement - return spread marker for flattenSpreadArgs
7826
+ if (node.type === 'SpreadElement') {
7827
+ const arg = await this.evaluateAsync(node.argument, env);
7828
+ if (Array.isArray(arg)) {
7829
+ return { __spread: true, __values: arg };
7830
+ }
7831
+ if (typeof arg === 'string') {
7832
+ return { __spread: true, __values: [...arg] };
7833
+ }
7834
+ if (arg !== null && arg !== undefined && typeof arg[Symbol.iterator] === 'function') {
7835
+ return { __spread: true, __values: [...arg] };
7836
+ }
7837
+ if (typeof arg === 'object' && arg !== null) {
7838
+ return { __spread: true, __values: Object.entries(arg) };
7839
+ }
7840
+ throw new TypeError('Spread syntax requires an iterable');
7841
+ }
7842
+
7824
7843
  // Only leaf nodes should fall through to sync evaluate
7825
7844
  // These have no sub-expressions that could contain await
7826
7845
  if (['Literal', 'Identifier', 'BreakStatement', 'ContinueStatement',
@@ -10328,7 +10347,8 @@ class WangInterpreter {
10328
10347
  // Prepare execution options
10329
10348
  const options = {
10330
10349
  moduleResolver: this.moduleResolver,
10331
- executionController: userOptions.executionController
10350
+ executionController: userOptions.executionController,
10351
+ abortSignal: userOptions.abortSignal // Pass abort signal to interpreter for cancellation support
10332
10352
  // sourceType will be auto-detected from code
10333
10353
  };
10334
10354
 
@@ -10771,17 +10791,29 @@ async function execute(code, env = null, options = {}) {
10771
10791
  controller != null;
10772
10792
 
10773
10793
  if (needsAsync) {
10774
- const result = await interpreter.evaluateAsync(ast, env);
10794
+ let result = await interpreter.evaluateAsync(ast, env);
10775
10795
  if (controller) {
10776
10796
  controller._complete();
10777
10797
  }
10778
- return result instanceof ReturnValue ? result.value : result;
10798
+ result = result instanceof ReturnValue ? result.value : result;
10799
+
10800
+ // If result is undefined and code has module declarations, return default export
10801
+ if (result === undefined && interpreter.moduleExports?.default !== undefined) {
10802
+ return interpreter.moduleExports.default;
10803
+ }
10804
+ return result;
10779
10805
  } else {
10780
- const result = interpreter.evaluate(ast, env);
10806
+ let result = interpreter.evaluate(ast, env);
10781
10807
  if (controller) {
10782
10808
  controller._complete();
10783
10809
  }
10784
- return result instanceof ReturnValue ? result.value : result;
10810
+ result = result instanceof ReturnValue ? result.value : result;
10811
+
10812
+ // If result is undefined and code has module declarations, return default export
10813
+ if (result === undefined && interpreter.moduleExports?.default !== undefined) {
10814
+ return interpreter.moduleExports.default;
10815
+ }
10816
+ return result;
10785
10817
  }
10786
10818
  } catch (e) {
10787
10819
  // Mark as aborted if that's the error type
package/dist/index.d.ts CHANGED
@@ -7323,10 +7323,11 @@ class Interpreter {
7323
7323
  return undefined;
7324
7324
  }
7325
7325
 
7326
- const args = [];
7326
+ const rawArgs = [];
7327
7327
  for (const arg of node.arguments) {
7328
- args.push(await this.evaluateAsync(arg, env));
7328
+ rawArgs.push(await this.evaluateAsync(arg, env));
7329
7329
  }
7330
+ const args = this.flattenSpreadArgs(rawArgs);
7330
7331
 
7331
7332
  if (typeof callee === 'function') {
7332
7333
  if (thisContext !== undefined) {
@@ -7821,6 +7822,24 @@ class Interpreter {
7821
7822
  return this.normalizeJSXText(node.value);
7822
7823
  }
7823
7824
 
7825
+ // For SpreadElement - return spread marker for flattenSpreadArgs
7826
+ if (node.type === 'SpreadElement') {
7827
+ const arg = await this.evaluateAsync(node.argument, env);
7828
+ if (Array.isArray(arg)) {
7829
+ return { __spread: true, __values: arg };
7830
+ }
7831
+ if (typeof arg === 'string') {
7832
+ return { __spread: true, __values: [...arg] };
7833
+ }
7834
+ if (arg !== null && arg !== undefined && typeof arg[Symbol.iterator] === 'function') {
7835
+ return { __spread: true, __values: [...arg] };
7836
+ }
7837
+ if (typeof arg === 'object' && arg !== null) {
7838
+ return { __spread: true, __values: Object.entries(arg) };
7839
+ }
7840
+ throw new TypeError('Spread syntax requires an iterable');
7841
+ }
7842
+
7824
7843
  // Only leaf nodes should fall through to sync evaluate
7825
7844
  // These have no sub-expressions that could contain await
7826
7845
  if (['Literal', 'Identifier', 'BreakStatement', 'ContinueStatement',
@@ -10328,7 +10347,8 @@ class WangInterpreter {
10328
10347
  // Prepare execution options
10329
10348
  const options = {
10330
10349
  moduleResolver: this.moduleResolver,
10331
- executionController: userOptions.executionController
10350
+ executionController: userOptions.executionController,
10351
+ abortSignal: userOptions.abortSignal // Pass abort signal to interpreter for cancellation support
10332
10352
  // sourceType will be auto-detected from code
10333
10353
  };
10334
10354
 
@@ -10771,17 +10791,29 @@ async function execute(code, env = null, options = {}) {
10771
10791
  controller != null;
10772
10792
 
10773
10793
  if (needsAsync) {
10774
- const result = await interpreter.evaluateAsync(ast, env);
10794
+ let result = await interpreter.evaluateAsync(ast, env);
10775
10795
  if (controller) {
10776
10796
  controller._complete();
10777
10797
  }
10778
- return result instanceof ReturnValue ? result.value : result;
10798
+ result = result instanceof ReturnValue ? result.value : result;
10799
+
10800
+ // If result is undefined and code has module declarations, return default export
10801
+ if (result === undefined && interpreter.moduleExports?.default !== undefined) {
10802
+ return interpreter.moduleExports.default;
10803
+ }
10804
+ return result;
10779
10805
  } else {
10780
- const result = interpreter.evaluate(ast, env);
10806
+ let result = interpreter.evaluate(ast, env);
10781
10807
  if (controller) {
10782
10808
  controller._complete();
10783
10809
  }
10784
- return result instanceof ReturnValue ? result.value : result;
10810
+ result = result instanceof ReturnValue ? result.value : result;
10811
+
10812
+ // If result is undefined and code has module declarations, return default export
10813
+ if (result === undefined && interpreter.moduleExports?.default !== undefined) {
10814
+ return interpreter.moduleExports.default;
10815
+ }
10816
+ return result;
10785
10817
  }
10786
10818
  } catch (e) {
10787
10819
  // Mark as aborted if that's the error type
package/dist/index.js CHANGED
@@ -6555,10 +6555,11 @@ var Interpreter = class _Interpreter {
6555
6555
  if (node.optional && (callee === null || callee === void 0)) {
6556
6556
  return void 0;
6557
6557
  }
6558
- const args = [];
6558
+ const rawArgs = [];
6559
6559
  for (const arg of node.arguments) {
6560
- args.push(await this.evaluateAsync(arg, env));
6560
+ rawArgs.push(await this.evaluateAsync(arg, env));
6561
6561
  }
6562
+ const args = this.flattenSpreadArgs(rawArgs);
6562
6563
  if (typeof callee === "function") {
6563
6564
  if (thisContext !== void 0) {
6564
6565
  return await callee.call(thisContext, ...args);
@@ -6960,6 +6961,22 @@ var Interpreter = class _Interpreter {
6960
6961
  if (node.type === "JSXText") {
6961
6962
  return this.normalizeJSXText(node.value);
6962
6963
  }
6964
+ if (node.type === "SpreadElement") {
6965
+ const arg = await this.evaluateAsync(node.argument, env);
6966
+ if (Array.isArray(arg)) {
6967
+ return { __spread: true, __values: arg };
6968
+ }
6969
+ if (typeof arg === "string") {
6970
+ return { __spread: true, __values: [...arg] };
6971
+ }
6972
+ if (arg !== null && arg !== void 0 && typeof arg[Symbol.iterator] === "function") {
6973
+ return { __spread: true, __values: [...arg] };
6974
+ }
6975
+ if (typeof arg === "object" && arg !== null) {
6976
+ return { __spread: true, __values: Object.entries(arg) };
6977
+ }
6978
+ throw new TypeError("Spread syntax requires an iterable");
6979
+ }
6963
6980
  if ([
6964
6981
  "Literal",
6965
6982
  "Identifier",
@@ -8851,7 +8868,9 @@ var WangInterpreter = class {
8851
8868
  const hasTopLevelReturn = this.hasTopLevelReturn(code);
8852
8869
  const options = {
8853
8870
  moduleResolver: this.moduleResolver,
8854
- executionController: userOptions.executionController
8871
+ executionController: userOptions.executionController,
8872
+ abortSignal: userOptions.abortSignal
8873
+ // Pass abort signal to interpreter for cancellation support
8855
8874
  // sourceType will be auto-detected from code
8856
8875
  };
8857
8876
  let result;
@@ -9181,6 +9200,7 @@ function containsTopLevelAwait(node) {
9181
9200
  return false;
9182
9201
  }
9183
9202
  async function execute(code, env = null, options = {}) {
9203
+ var _a, _b;
9184
9204
  const controller = options.executionController;
9185
9205
  if (controller) {
9186
9206
  controller._start();
@@ -9197,17 +9217,25 @@ async function execute(code, env = null, options = {}) {
9197
9217
  });
9198
9218
  const needsAsync = options.sourceType === "module" || containsModuleDeclarations(ast) || containsTopLevelAwait(ast) || controller != null;
9199
9219
  if (needsAsync) {
9200
- const result = await interpreter.evaluateAsync(ast, env);
9220
+ let result = await interpreter.evaluateAsync(ast, env);
9201
9221
  if (controller) {
9202
9222
  controller._complete();
9203
9223
  }
9204
- return result instanceof ReturnValue ? result.value : result;
9224
+ result = result instanceof ReturnValue ? result.value : result;
9225
+ if (result === void 0 && ((_a = interpreter.moduleExports) == null ? void 0 : _a.default) !== void 0) {
9226
+ return interpreter.moduleExports.default;
9227
+ }
9228
+ return result;
9205
9229
  } else {
9206
- const result = interpreter.evaluate(ast, env);
9230
+ let result = interpreter.evaluate(ast, env);
9207
9231
  if (controller) {
9208
9232
  controller._complete();
9209
9233
  }
9210
- return result instanceof ReturnValue ? result.value : result;
9234
+ result = result instanceof ReturnValue ? result.value : result;
9235
+ if (result === void 0 && ((_b = interpreter.moduleExports) == null ? void 0 : _b.default) !== void 0) {
9236
+ return interpreter.moduleExports.default;
9237
+ }
9238
+ return result;
9211
9239
  }
9212
9240
  } catch (e) {
9213
9241
  if (controller && e.name === "AbortError") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jslike",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
4
4
  "description": "Production-ready JavaScript interpreter with full ES6+ support using Acorn parser",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",