jslike 1.4.5 → 1.5.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 +85 -26
- package/dist/esm/runtime/execution-controller.js +212 -0
- package/dist/index.cjs +287 -35
- package/dist/index.d.cts +350 -52
- package/dist/index.d.ts +350 -52
- package/dist/index.js +286 -35
- package/package.json +1 -1
package/dist/esm/index.js
CHANGED
|
@@ -98,34 +98,59 @@ function containsTopLevelAwait(node) {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
export async function execute(code, env = null, options = {}) {
|
|
101
|
-
//
|
|
102
|
-
const
|
|
101
|
+
// Get execution controller if provided
|
|
102
|
+
const controller = options.executionController;
|
|
103
103
|
|
|
104
|
-
//
|
|
105
|
-
if (
|
|
106
|
-
|
|
104
|
+
// Mark execution as starting
|
|
105
|
+
if (controller) {
|
|
106
|
+
controller._start();
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
109
|
+
try {
|
|
110
|
+
// Parse the code
|
|
111
|
+
const ast = parse(code, options);
|
|
112
|
+
|
|
113
|
+
// Create global environment if not provided
|
|
114
|
+
if (!env) {
|
|
115
|
+
env = createGlobalEnvironment(new Environment());
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Create interpreter with module resolver, abort signal, and execution controller
|
|
119
|
+
const interpreter = new Interpreter(env, {
|
|
120
|
+
moduleResolver: options.moduleResolver,
|
|
121
|
+
abortSignal: options.abortSignal,
|
|
122
|
+
executionController: controller
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Use async evaluation if:
|
|
126
|
+
// 1. Explicitly requested module mode
|
|
127
|
+
// 2. AST contains import/export declarations
|
|
128
|
+
// 3. Code contains top-level await
|
|
129
|
+
// 4. Execution controller provided (needs async for pause/resume)
|
|
130
|
+
const needsAsync = options.sourceType === 'module' ||
|
|
131
|
+
containsModuleDeclarations(ast) ||
|
|
132
|
+
containsTopLevelAwait(ast) ||
|
|
133
|
+
controller != null;
|
|
134
|
+
|
|
135
|
+
if (needsAsync) {
|
|
136
|
+
const result = await interpreter.evaluateAsync(ast, env);
|
|
137
|
+
if (controller) {
|
|
138
|
+
controller._complete();
|
|
139
|
+
}
|
|
140
|
+
return result instanceof ReturnValue ? result.value : result;
|
|
141
|
+
} else {
|
|
142
|
+
const result = interpreter.evaluate(ast, env);
|
|
143
|
+
if (controller) {
|
|
144
|
+
controller._complete();
|
|
145
|
+
}
|
|
146
|
+
return result instanceof ReturnValue ? result.value : result;
|
|
147
|
+
}
|
|
148
|
+
} catch (e) {
|
|
149
|
+
// Mark as aborted if that's the error type
|
|
150
|
+
if (controller && e.name === 'AbortError') {
|
|
151
|
+
controller.state = 'aborted';
|
|
152
|
+
}
|
|
153
|
+
throw e;
|
|
129
154
|
}
|
|
130
155
|
}
|
|
131
156
|
|
|
@@ -139,6 +164,7 @@ export const isTopLevelAwait = containsModuleSyntax;
|
|
|
139
164
|
export { Interpreter } from './interpreter/interpreter.js';
|
|
140
165
|
export { Environment } from './runtime/environment.js';
|
|
141
166
|
export { WangInterpreter, InMemoryModuleResolver } from './interpreter/index.js';
|
|
167
|
+
export { ExecutionController } from './runtime/execution-controller.js';
|
|
142
168
|
|
|
143
169
|
/**
|
|
144
170
|
* Abstract base class for module resolution
|
|
@@ -101,7 +101,8 @@ export class WangInterpreter {
|
|
|
101
101
|
|
|
102
102
|
// Prepare execution options
|
|
103
103
|
const options = {
|
|
104
|
-
moduleResolver: this.moduleResolver
|
|
104
|
+
moduleResolver: this.moduleResolver,
|
|
105
|
+
executionController: userOptions.executionController
|
|
105
106
|
// sourceType will be auto-detected from code
|
|
106
107
|
};
|
|
107
108
|
|
|
@@ -9,10 +9,17 @@ export class Interpreter {
|
|
|
9
9
|
this.moduleCache = new Map(); // Cache loaded modules
|
|
10
10
|
this.moduleExports = {}; // Track exports in current module
|
|
11
11
|
this.abortSignal = options.abortSignal;
|
|
12
|
+
this.executionController = options.executionController;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
// Check if execution should be aborted
|
|
15
|
+
// Check if execution should be aborted (sync version)
|
|
15
16
|
checkAbortSignal() {
|
|
17
|
+
// Check controller first if available
|
|
18
|
+
if (this.executionController) {
|
|
19
|
+
this.executionController._checkAbortSync();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
// Fall back to legacy abortSignal
|
|
16
23
|
if (this.abortSignal && this.abortSignal.aborted) {
|
|
17
24
|
const error = new Error('The operation was aborted');
|
|
18
25
|
error.name = 'AbortError';
|
|
@@ -20,12 +27,26 @@ export class Interpreter {
|
|
|
20
27
|
}
|
|
21
28
|
}
|
|
22
29
|
|
|
30
|
+
// Checkpoint that returns a promise only when controller is present
|
|
31
|
+
// When no controller, returns null to signal no await needed
|
|
32
|
+
_getCheckpointPromise(node, env) {
|
|
33
|
+
if (this.executionController) {
|
|
34
|
+
this.executionController._setEnv(env);
|
|
35
|
+
return this.executionController._checkpoint(node);
|
|
36
|
+
} else {
|
|
37
|
+
this.checkAbortSignal();
|
|
38
|
+
return null; // Signal that no await is needed
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
23
42
|
// Async evaluation for async functions - handles await expressions
|
|
24
43
|
async evaluateAsync(node, env) {
|
|
25
44
|
if (!node) return undefined;
|
|
26
45
|
|
|
27
|
-
//
|
|
28
|
-
|
|
46
|
+
// Checkpoint - yields if paused, throws if aborted
|
|
47
|
+
// Only await when there's actually a promise (controller present)
|
|
48
|
+
const checkpointPromise = this._getCheckpointPromise(node, env);
|
|
49
|
+
if (checkpointPromise) await checkpointPromise;
|
|
29
50
|
|
|
30
51
|
// Handle await expressions by actually awaiting the promise
|
|
31
52
|
if (node.type === 'AwaitExpression') {
|
|
@@ -262,6 +283,9 @@ export class Interpreter {
|
|
|
262
283
|
await this.evaluateAsync(node.init, forEnv);
|
|
263
284
|
}
|
|
264
285
|
while (!node.test || await this.evaluateAsync(node.test, forEnv)) {
|
|
286
|
+
// Checkpoint at each loop iteration (only await if controller present)
|
|
287
|
+
const cp1 = this._getCheckpointPromise(node, forEnv);
|
|
288
|
+
if (cp1) await cp1;
|
|
265
289
|
const result = await this.evaluateAsync(node.body, forEnv);
|
|
266
290
|
if (result instanceof BreakSignal) {
|
|
267
291
|
break;
|
|
@@ -290,6 +314,9 @@ export class Interpreter {
|
|
|
290
314
|
const isConst = node.left.kind === 'const';
|
|
291
315
|
|
|
292
316
|
for (const value of iterable) {
|
|
317
|
+
// Checkpoint at each loop iteration (only await if controller present)
|
|
318
|
+
const cp2 = this._getCheckpointPromise(node, forEnv);
|
|
319
|
+
if (cp2) await cp2;
|
|
293
320
|
const iterEnv = forEnv.extend();
|
|
294
321
|
if (declarator.id.type === 'Identifier') {
|
|
295
322
|
iterEnv.define(declarator.id.name, value, isConst);
|
|
@@ -323,6 +350,9 @@ export class Interpreter {
|
|
|
323
350
|
forEnv.define(varName, undefined);
|
|
324
351
|
|
|
325
352
|
for (const key in obj) {
|
|
353
|
+
// Checkpoint at each loop iteration (only await if controller present)
|
|
354
|
+
const cp3 = this._getCheckpointPromise(node, forEnv);
|
|
355
|
+
if (cp3) await cp3;
|
|
326
356
|
forEnv.set(varName, key);
|
|
327
357
|
const result = await this.evaluateAsync(node.body, forEnv);
|
|
328
358
|
if (result instanceof BreakSignal) {
|
|
@@ -341,6 +371,9 @@ export class Interpreter {
|
|
|
341
371
|
// For WhileStatement with async body
|
|
342
372
|
if (node.type === 'WhileStatement') {
|
|
343
373
|
while (await this.evaluateAsync(node.test, env)) {
|
|
374
|
+
// Checkpoint at each loop iteration (only await if controller present)
|
|
375
|
+
const cp4 = this._getCheckpointPromise(node, env);
|
|
376
|
+
if (cp4) await cp4;
|
|
344
377
|
const result = await this.evaluateAsync(node.body, env);
|
|
345
378
|
if (result instanceof BreakSignal) {
|
|
346
379
|
break;
|
|
@@ -358,6 +391,9 @@ export class Interpreter {
|
|
|
358
391
|
// For DoWhileStatement with async body
|
|
359
392
|
if (node.type === 'DoWhileStatement') {
|
|
360
393
|
do {
|
|
394
|
+
// Checkpoint at each loop iteration (only await if controller present)
|
|
395
|
+
const cp5 = this._getCheckpointPromise(node, env);
|
|
396
|
+
if (cp5) await cp5;
|
|
361
397
|
const result = await this.evaluateAsync(node.body, env);
|
|
362
398
|
if (result instanceof BreakSignal) {
|
|
363
399
|
break;
|
|
@@ -1161,6 +1197,9 @@ export class Interpreter {
|
|
|
1161
1197
|
const metadata = func.__metadata || func;
|
|
1162
1198
|
const funcEnv = new Environment(metadata.closure);
|
|
1163
1199
|
|
|
1200
|
+
// Get function name for call stack tracking
|
|
1201
|
+
const funcName = metadata.name || func.name || 'anonymous';
|
|
1202
|
+
|
|
1164
1203
|
// Bind 'this' if provided (for method calls)
|
|
1165
1204
|
if (thisContext !== undefined) {
|
|
1166
1205
|
funcEnv.define('this', thisContext);
|
|
@@ -1196,18 +1235,54 @@ export class Interpreter {
|
|
|
1196
1235
|
// Execute function body
|
|
1197
1236
|
// If async, use async evaluation and return a promise
|
|
1198
1237
|
if (metadata.async) {
|
|
1238
|
+
// Track call stack for async functions
|
|
1239
|
+
if (this.executionController) {
|
|
1240
|
+
this.executionController._pushCall(funcName);
|
|
1241
|
+
}
|
|
1199
1242
|
return (async () => {
|
|
1243
|
+
try {
|
|
1244
|
+
if (metadata.expression) {
|
|
1245
|
+
// Arrow function with expression body
|
|
1246
|
+
const result = await this.evaluateAsync(metadata.body, funcEnv);
|
|
1247
|
+
// If the result is a ThrowSignal, throw the error
|
|
1248
|
+
if (result instanceof ThrowSignal) {
|
|
1249
|
+
throw result.value;
|
|
1250
|
+
}
|
|
1251
|
+
return result;
|
|
1252
|
+
} else {
|
|
1253
|
+
// Block statement body
|
|
1254
|
+
const result = await this.evaluateAsync(metadata.body, funcEnv);
|
|
1255
|
+
if (result instanceof ReturnValue) {
|
|
1256
|
+
return result.value;
|
|
1257
|
+
}
|
|
1258
|
+
// If the result is a ThrowSignal, throw the error
|
|
1259
|
+
if (result instanceof ThrowSignal) {
|
|
1260
|
+
throw result.value;
|
|
1261
|
+
}
|
|
1262
|
+
return undefined;
|
|
1263
|
+
}
|
|
1264
|
+
} finally {
|
|
1265
|
+
if (this.executionController) {
|
|
1266
|
+
this.executionController._popCall();
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
})();
|
|
1270
|
+
} else {
|
|
1271
|
+
// Synchronous evaluation for non-async functions
|
|
1272
|
+
// Track call stack for sync functions
|
|
1273
|
+
if (this.executionController) {
|
|
1274
|
+
this.executionController._pushCall(funcName);
|
|
1275
|
+
}
|
|
1276
|
+
try {
|
|
1200
1277
|
if (metadata.expression) {
|
|
1201
|
-
|
|
1202
|
-
const result = await this.evaluateAsync(metadata.body, funcEnv);
|
|
1278
|
+
const result = this.evaluate(metadata.body, funcEnv);
|
|
1203
1279
|
// If the result is a ThrowSignal, throw the error
|
|
1204
1280
|
if (result instanceof ThrowSignal) {
|
|
1205
1281
|
throw result.value;
|
|
1206
1282
|
}
|
|
1207
1283
|
return result;
|
|
1208
1284
|
} else {
|
|
1209
|
-
|
|
1210
|
-
const result = await this.evaluateAsync(metadata.body, funcEnv);
|
|
1285
|
+
const result = this.evaluate(metadata.body, funcEnv);
|
|
1211
1286
|
if (result instanceof ReturnValue) {
|
|
1212
1287
|
return result.value;
|
|
1213
1288
|
}
|
|
@@ -1217,26 +1292,10 @@ export class Interpreter {
|
|
|
1217
1292
|
}
|
|
1218
1293
|
return undefined;
|
|
1219
1294
|
}
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
if (metadata.expression) {
|
|
1224
|
-
const result = this.evaluate(metadata.body, funcEnv);
|
|
1225
|
-
// If the result is a ThrowSignal, throw the error
|
|
1226
|
-
if (result instanceof ThrowSignal) {
|
|
1227
|
-
throw result.value;
|
|
1228
|
-
}
|
|
1229
|
-
return result;
|
|
1230
|
-
} else {
|
|
1231
|
-
const result = this.evaluate(metadata.body, funcEnv);
|
|
1232
|
-
if (result instanceof ReturnValue) {
|
|
1233
|
-
return result.value;
|
|
1234
|
-
}
|
|
1235
|
-
// If the result is a ThrowSignal, throw the error
|
|
1236
|
-
if (result instanceof ThrowSignal) {
|
|
1237
|
-
throw result.value;
|
|
1295
|
+
} finally {
|
|
1296
|
+
if (this.executionController) {
|
|
1297
|
+
this.executionController._popCall();
|
|
1238
1298
|
}
|
|
1239
|
-
return undefined;
|
|
1240
1299
|
}
|
|
1241
1300
|
}
|
|
1242
1301
|
}
|
|
@@ -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
|
+
}
|