jslike 1.4.5 → 1.6.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/dist/esm/index.js +51 -25
- package/dist/esm/interpreter/index.js +2 -1
- package/dist/esm/interpreter/interpreter.js +370 -26
- package/dist/esm/parser.js +781 -3
- package/dist/esm/runtime/builtins.js +14 -0
- package/dist/esm/runtime/execution-controller.js +212 -0
- package/dist/index.cjs +1199 -41
- package/dist/index.d.cts +1394 -63
- package/dist/index.d.ts +1394 -63
- package/dist/index.js +1198 -41
- package/dist/validator/index.cjs +669 -5
- package/dist/validator/index.js +669 -5
- package/package.json +5 -2
|
@@ -114,6 +114,20 @@ export function createGlobalEnvironment(env) {
|
|
|
114
114
|
env.define('SyntaxError', SyntaxError);
|
|
115
115
|
env.define('RangeError', RangeError);
|
|
116
116
|
|
|
117
|
+
// JSX Runtime Support
|
|
118
|
+
env.define('createElement', (type, props, ...children) => ({
|
|
119
|
+
$$typeof: Symbol.for('react.element'),
|
|
120
|
+
type,
|
|
121
|
+
props: {
|
|
122
|
+
...props,
|
|
123
|
+
children: children.length === 0 ? undefined : children.length === 1 ? children[0] : children
|
|
124
|
+
},
|
|
125
|
+
key: props?.key ?? null,
|
|
126
|
+
ref: props?.ref ?? null
|
|
127
|
+
}));
|
|
128
|
+
|
|
129
|
+
env.define('Fragment', Symbol.for('react.fragment'));
|
|
130
|
+
|
|
117
131
|
// Global console functions (shortcuts for console.log/warn/error)
|
|
118
132
|
env.define('log', (...args) => {
|
|
119
133
|
console.log(...args);
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExecutionController - Controls and monitors interpreter execution
|
|
3
|
+
*
|
|
4
|
+
* Provides pause/resume/abort capabilities and execution status introspection.
|
|
5
|
+
* Works with async evaluation only - sync evaluation can only abort.
|
|
6
|
+
*/
|
|
7
|
+
export class ExecutionController {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.state = 'idle'; // 'idle' | 'running' | 'paused' | 'aborted' | 'completed'
|
|
10
|
+
this.pauseRequested = false;
|
|
11
|
+
this.abortRequested = false;
|
|
12
|
+
|
|
13
|
+
// Debug info
|
|
14
|
+
this.stepCount = 0;
|
|
15
|
+
this.currentNode = null;
|
|
16
|
+
this.callStack = [];
|
|
17
|
+
this.currentEnv = null;
|
|
18
|
+
|
|
19
|
+
this._resolveResume = null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// --- Control methods (called by user) ---
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Request pause at next checkpoint. Only works during async evaluation.
|
|
26
|
+
*/
|
|
27
|
+
pause() {
|
|
28
|
+
if (this.state === 'running') {
|
|
29
|
+
this.pauseRequested = true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Resume execution after pause.
|
|
35
|
+
*/
|
|
36
|
+
resume() {
|
|
37
|
+
if (this.state === 'paused' && this._resolveResume) {
|
|
38
|
+
this.pauseRequested = false;
|
|
39
|
+
this._resolveResume();
|
|
40
|
+
this._resolveResume = null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Abort execution. Works for both sync and async evaluation.
|
|
46
|
+
* If paused, will resume and then abort.
|
|
47
|
+
*/
|
|
48
|
+
abort() {
|
|
49
|
+
this.abortRequested = true;
|
|
50
|
+
// Also resume if paused to allow abort to take effect
|
|
51
|
+
if (this._resolveResume) {
|
|
52
|
+
this._resolveResume();
|
|
53
|
+
this._resolveResume = null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// --- Status (called by user) ---
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get current execution status with full debug information.
|
|
61
|
+
* @returns {Object} Status object with state, stepCount, currentNode, callStack, and variables
|
|
62
|
+
*/
|
|
63
|
+
getStatus() {
|
|
64
|
+
return {
|
|
65
|
+
state: this.state,
|
|
66
|
+
stepCount: this.stepCount,
|
|
67
|
+
currentNode: this.currentNode?.type || null,
|
|
68
|
+
callStack: [...this.callStack],
|
|
69
|
+
variables: this._getEnvironmentVariables()
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Check if abort has been requested.
|
|
75
|
+
* Compatible with AbortSignal interface.
|
|
76
|
+
*/
|
|
77
|
+
get aborted() {
|
|
78
|
+
return this.abortRequested;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// --- Internal methods (called by interpreter) ---
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Mark execution as started. Called by execute().
|
|
85
|
+
* @internal
|
|
86
|
+
*/
|
|
87
|
+
_start() {
|
|
88
|
+
this.state = 'running';
|
|
89
|
+
this.stepCount = 0;
|
|
90
|
+
this.currentNode = null;
|
|
91
|
+
this.callStack = [];
|
|
92
|
+
this.pauseRequested = false;
|
|
93
|
+
// Note: don't reset abortRequested - allow pre-abort
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Mark execution as completed. Called by execute().
|
|
98
|
+
* @internal
|
|
99
|
+
*/
|
|
100
|
+
_complete() {
|
|
101
|
+
this.state = 'completed';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Push a function call onto the call stack.
|
|
106
|
+
* @internal
|
|
107
|
+
*/
|
|
108
|
+
_pushCall(name) {
|
|
109
|
+
this.callStack.push(name);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Pop a function call from the call stack.
|
|
114
|
+
* @internal
|
|
115
|
+
*/
|
|
116
|
+
_popCall() {
|
|
117
|
+
this.callStack.pop();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Set the current environment for variable introspection.
|
|
122
|
+
* @internal
|
|
123
|
+
*/
|
|
124
|
+
_setEnv(env) {
|
|
125
|
+
this.currentEnv = env;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Async checkpoint - yields if paused, throws if aborted.
|
|
130
|
+
* Called at coarse granularity points (loops, function calls).
|
|
131
|
+
* @internal
|
|
132
|
+
*/
|
|
133
|
+
async _checkpoint(node) {
|
|
134
|
+
this.stepCount++;
|
|
135
|
+
this.currentNode = node;
|
|
136
|
+
|
|
137
|
+
if (this.abortRequested) {
|
|
138
|
+
this.state = 'aborted';
|
|
139
|
+
const error = new Error('The operation was aborted');
|
|
140
|
+
error.name = 'AbortError';
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (this.pauseRequested && this.state === 'running') {
|
|
145
|
+
this.state = 'paused';
|
|
146
|
+
await new Promise(resolve => { this._resolveResume = resolve; });
|
|
147
|
+
this.state = 'running';
|
|
148
|
+
|
|
149
|
+
// Check abort after resume (user may have called abort while paused)
|
|
150
|
+
if (this.abortRequested) {
|
|
151
|
+
this.state = 'aborted';
|
|
152
|
+
const error = new Error('The operation was aborted');
|
|
153
|
+
error.name = 'AbortError';
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Sync abort check - only throws if aborted, cannot pause.
|
|
161
|
+
* Used in sync evaluate() path.
|
|
162
|
+
* @internal
|
|
163
|
+
*/
|
|
164
|
+
_checkAbortSync() {
|
|
165
|
+
if (this.abortRequested) {
|
|
166
|
+
this.state = 'aborted';
|
|
167
|
+
const error = new Error('The operation was aborted');
|
|
168
|
+
error.name = 'AbortError';
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Get all variables from current environment chain.
|
|
175
|
+
* @internal
|
|
176
|
+
*/
|
|
177
|
+
_getEnvironmentVariables() {
|
|
178
|
+
if (!this.currentEnv) return {};
|
|
179
|
+
const vars = {};
|
|
180
|
+
let env = this.currentEnv;
|
|
181
|
+
while (env) {
|
|
182
|
+
if (env.vars) {
|
|
183
|
+
for (const [key, value] of env.vars) {
|
|
184
|
+
if (!(key in vars)) {
|
|
185
|
+
vars[key] = this._serializeValue(value);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
env = env.parent;
|
|
190
|
+
}
|
|
191
|
+
return vars;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Serialize a value for status reporting.
|
|
196
|
+
* @internal
|
|
197
|
+
*/
|
|
198
|
+
_serializeValue(value) {
|
|
199
|
+
if (value === undefined) return { type: 'undefined' };
|
|
200
|
+
if (value === null) return { type: 'null' };
|
|
201
|
+
if (typeof value === 'function' || (value && value.__isFunction)) {
|
|
202
|
+
return { type: 'function', name: value.name || 'anonymous' };
|
|
203
|
+
}
|
|
204
|
+
if (Array.isArray(value)) {
|
|
205
|
+
return { type: 'array', length: value.length };
|
|
206
|
+
}
|
|
207
|
+
if (typeof value === 'object') {
|
|
208
|
+
return { type: 'object', preview: Object.keys(value).slice(0, 5) };
|
|
209
|
+
}
|
|
210
|
+
return { type: typeof value, value };
|
|
211
|
+
}
|
|
212
|
+
}
|