@typescript-guy/fn-monitor 1.0.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.
Files changed (62) hide show
  1. package/ACKNOWLEDGEMENTS.md +10 -0
  2. package/LICENSE.md +10 -0
  3. package/README.md +201 -0
  4. package/dist/custom-types.d.ts +239 -0
  5. package/dist/custom-types.js +341 -0
  6. package/dist/evaluate/declaration.d.ts +27 -0
  7. package/dist/evaluate/declaration.js +289 -0
  8. package/dist/evaluate/expression.d.ts +40 -0
  9. package/dist/evaluate/expression.js +604 -0
  10. package/dist/evaluate/helper.d.ts +18 -0
  11. package/dist/evaluate/helper.js +270 -0
  12. package/dist/evaluate/identifier.d.ts +7 -0
  13. package/dist/evaluate/identifier.js +27 -0
  14. package/dist/evaluate/index.d.ts +3 -0
  15. package/dist/evaluate/index.js +112 -0
  16. package/dist/evaluate/literal.d.ts +3 -0
  17. package/dist/evaluate/literal.js +8 -0
  18. package/dist/evaluate/pattern.d.ts +13 -0
  19. package/dist/evaluate/pattern.js +118 -0
  20. package/dist/evaluate/program.d.ts +3 -0
  21. package/dist/evaluate/program.js +20 -0
  22. package/dist/evaluate/statement.d.ts +35 -0
  23. package/dist/evaluate/statement.js +323 -0
  24. package/dist/evaluate_n/declaration.d.ts +27 -0
  25. package/dist/evaluate_n/declaration.js +289 -0
  26. package/dist/evaluate_n/expression.d.ts +38 -0
  27. package/dist/evaluate_n/expression.js +596 -0
  28. package/dist/evaluate_n/helper.d.ts +18 -0
  29. package/dist/evaluate_n/helper.js +238 -0
  30. package/dist/evaluate_n/identifier.d.ts +7 -0
  31. package/dist/evaluate_n/identifier.js +27 -0
  32. package/dist/evaluate_n/index.d.ts +3 -0
  33. package/dist/evaluate_n/index.js +76 -0
  34. package/dist/evaluate_n/literal.d.ts +3 -0
  35. package/dist/evaluate_n/literal.js +8 -0
  36. package/dist/evaluate_n/pattern.d.ts +13 -0
  37. package/dist/evaluate_n/pattern.js +118 -0
  38. package/dist/evaluate_n/program.d.ts +3 -0
  39. package/dist/evaluate_n/program.js +20 -0
  40. package/dist/evaluate_n/statement.d.ts +35 -0
  41. package/dist/evaluate_n/statement.js +301 -0
  42. package/dist/examples/example-2.d.ts +1 -0
  43. package/dist/examples/example.d.ts +1 -0
  44. package/dist/helper-functions.d.ts +15 -0
  45. package/dist/helper-functions.js +104 -0
  46. package/dist/index.d.ts +55 -0
  47. package/dist/index.js +312 -0
  48. package/dist/q-list.d.ts +39 -0
  49. package/dist/q-list.js +146 -0
  50. package/dist/scope/index.d.ts +81 -0
  51. package/dist/scope/index.js +168 -0
  52. package/dist/scope/variable.d.ts +20 -0
  53. package/dist/scope/variable.js +38 -0
  54. package/dist/share/async.d.ts +7 -0
  55. package/dist/share/async.js +43 -0
  56. package/dist/share/const.d.ts +25 -0
  57. package/dist/share/const.js +21 -0
  58. package/dist/share/util.d.ts +33 -0
  59. package/dist/share/util.js +379 -0
  60. package/dist/sval.d.ts +15 -0
  61. package/dist/sval.js +104 -0
  62. package/package.json +54 -0
package/dist/index.js ADDED
@@ -0,0 +1,312 @@
1
+ import Sval from './sval.js';
2
+ import { parse } from 'meriyah';
3
+ import ansis from 'ansis';
4
+ import { LRUCache } from 'lru-cache';
5
+ import { sha256 } from 'js-sha256';
6
+ import { UNASSIGNED, NOT_ALLOCATED, createEvent, LAZY_NODE } from './custom-types.js';
7
+ export { ArrayExprEvent, ArrowFnExprEvent, AssignmentExprEvent, AwaitExprEvent, BinaryExprEvent, BreakStmtEvent, CallExprEvent, CatchClauseEvent, ContinueStmtEvent, DoWhileStmtEvent, ExpressionStmtEvent, ForInStmtEvent, ForOfStmtEvent, ForStmtEvent, FuncDeclEvent, FuncExprEvent, IfStmtEvent, LabeledStmtEvent, LangEvent, LiteralEvent, LogicalExprEvent, MemberExprEvent, NewExprEvent, ObjectExprEvent, ReturnStmtEvent, SequenceExprEvent, SwitchStmtEvent, TemplateLiteralEvent, TernaryExprEvent, ThrowStmtEvent, TryStmtEvent, UnaryExprEvent, UpdateExprEvent, VarDeclEvent, WhileStmtEvent, YieldExprEvent } from './custom-types.js';
8
+ import { isGenerator, pushResult } from './helper-functions.js';
9
+ import { ReadonlyQList, QList } from './q-list.js';
10
+ import jsBeatutify from 'js-beautify';
11
+ export { Var } from './scope/variable.js';
12
+
13
+ class EventScope {
14
+ #scope;
15
+ parent;
16
+ depth;
17
+ variables;
18
+ constructor(interpreter) {
19
+ this.#scope = interpreter.reusables.currentScope;
20
+ this.parent = this.#scope.scopeParent;
21
+ this.depth = this.#scope.scopeDepth;
22
+ this.variables = {
23
+ search: (name) => {
24
+ const variable = this.#scope.find(name);
25
+ if (variable === null) return null;
26
+ const variableForEvent = { value: () => variable.get() };
27
+ return variableForEvent;
28
+ },
29
+ local: this.#scope.scopeContext
30
+ };
31
+ }
32
+ }
33
+ class Visit {
34
+ #interpreter;
35
+ constructor(interpreter) {
36
+ this.#interpreter = interpreter;
37
+ }
38
+ localExeStack = () => {
39
+ return this.#interpreter.reusables.shared.readonlyExeStack;
40
+ };
41
+ is = (query, cb) => {
42
+ const node = this.#interpreter.reusables.node;
43
+ if (query === "Any" || node.type === query) {
44
+ const event = createEvent(query, this.#interpreter);
45
+ cb(event);
46
+ this.#interpreter.reusables.currentEvent = event;
47
+ }
48
+ };
49
+ execute = () => {
50
+ const handler = this.#interpreter.reusables.handler;
51
+ if (handler !== null) {
52
+ if (this.#interpreter.reusables.result !== UNASSIGNED) {
53
+ throw new Error(ansis.red(`A node can only be executed once`));
54
+ }
55
+ this.#interpreter.reusables.result = handler(this.#interpreter.reusables.node, this.#interpreter.reusables.currentScope);
56
+ if (isGenerator(this.#interpreter.reusables.result)) {
57
+ return LAZY_NODE;
58
+ } else {
59
+ pushResult(this.#interpreter, this.#interpreter.reusables.result);
60
+ return this.#interpreter.reusables.result;
61
+ }
62
+ }
63
+ };
64
+ set perExecution(perExe) {
65
+ this.#interpreter.reusables.shared.perExe = {
66
+ fn: perExe,
67
+ owner: this.#interpreter.reusables.node
68
+ };
69
+ }
70
+ }
71
+ class SvalPlus extends Sval {
72
+ inspector = null;
73
+ onStep = null;
74
+ fnBeforeEachCall = void 0;
75
+ fnAfterEachCall = void 0;
76
+ static resultExport = SvalPlus.sha256Key("result");
77
+ static argsVar = SvalPlus.sha256Key("args");
78
+ //this can safely be static because its just used as a common name for the passed arguments.Its used in a per-instance object to ensure isolation
79
+ static capturesVar = SvalPlus.sha256Key("captures");
80
+ static fnAstCache = new LRUCache({ max: 400 });
81
+ astInUse = null;
82
+ reusables;
83
+ visit = new Visit(this);
84
+ //Even if each inspector gets a shared visit object that reflects the latest values for performance,i wont freeze its properties to allow possible external wrappers to customize it
85
+ stage = "IDLE";
86
+ static meriyahParseOptions = {
87
+ module: false,
88
+ //Since im just parsing functions,i dont need the extra overhead of a module parser
89
+ next: true,
90
+ // Modern ES support
91
+ loc: true,
92
+ // Essential for your shop.demand tracking
93
+ ranges: true,
94
+ // Good for error reporting
95
+ lexical: true
96
+ // Helps Sval understand 'let/const' vs 'var'
97
+ };
98
+ static defaultOptions = {
99
+ sourceType: "script",
100
+ //use the normalized and faster evaluator at the cost of not using esm import syntax which i dont even need anyway in a function.And some of the monitor's generated code wont even work with the modules option.This also means that the interpreter cant utilize top level await to handle async functions.It has to be done carefully in the evaluator
101
+ ecmaVer: 2024,
102
+ sandBox: true
103
+ };
104
+ constructor(args) {
105
+ super(args.options);
106
+ this.fnBeforeEachCall = args.fnBeforeEachCall;
107
+ this.fnAfterEachCall = args.fnAfterEachCall;
108
+ this.inspector = args.inspector || null;
109
+ this.onStep = args.onStep || null;
110
+ this.reusables = {
111
+ currentEvent: NOT_ALLOCATED,
112
+ currentScope: null,
113
+ node: null,
114
+ result: UNASSIGNED,
115
+ handler: null,
116
+ shared: {
117
+ evalStack: { value: 0 },
118
+ exeStack: new QList(),
119
+ readonlyExeStack: new ReadonlyQList(),
120
+ perExe: null
121
+ }
122
+ };
123
+ this.reusables.shared.readonlyExeStack.swapSrc(this.reusables.shared.exeStack);
124
+ }
125
+ createEventScope = () => {
126
+ return new EventScope(this);
127
+ };
128
+ getFnSrc(fn, capturesVar) {
129
+ const fnString = fn.toString();
130
+ const hash = SvalPlus.sha256Key(fnString);
131
+ const isDeclaration = /^(async\s+)?function(\s*\*|\s+|$)/.test(fnString);
132
+ const intermediateFn = "intermediateFn_" + hash;
133
+ let intermediateFnCode = "";
134
+ if (isDeclaration) {
135
+ intermediateFnCode = `
136
+ const ${intermediateFn} = (()=>{
137
+ ${fnString};
138
+ return ${fn.name}
139
+ })();`;
140
+ } else {
141
+ intermediateFnCode = `
142
+ const ${intermediateFn} = ${fnString};`;
143
+ }
144
+ const capturedKeys = capturesVar !== null ? Object.keys(this.exports[capturesVar]).sort() : [];
145
+ const storeCaptures = capturedKeys.length > 0 ? `
146
+ const {${capturedKeys.join(",")}} = exports.${capturesVar};` : "";
147
+ const finalFnName = fn.name.length > 0 ? fn.name : "anonymousFn_" + hash;
148
+ const finalFnCode = `
149
+ const ${finalFnName} = (()=>{
150
+ ${storeCaptures}
151
+ ${intermediateFnCode}
152
+ return ${intermediateFn};
153
+ })();`;
154
+ return {
155
+ fnCode: finalFnCode,
156
+ fnName: finalFnName
157
+ };
158
+ }
159
+ getFnSources(functions) {
160
+ let fnCode = "";
161
+ if (functions !== void 0) {
162
+ let declarations = "";
163
+ let assignments = "";
164
+ for (const [index, name] of Object.keys(functions).sort().entries()) {
165
+ const fn = functions[name];
166
+ const capturesVar = SvalPlus.sha256Key(`embeddedCaptures_${index}`);
167
+ this.exports[capturesVar] = fn.captures || /* @__PURE__ */ Object.create(null);
168
+ const fnSrc = this.getFnSrc(fn.ref, capturesVar);
169
+ const scopedFn = `(()=>{
170
+ ${fnSrc.fnCode}
171
+ return ${fnSrc.fnName};
172
+ })();`;
173
+ declarations += `
174
+ var ${name};`;
175
+ assignments += `
176
+ ${name} = ${scopedFn};`;
177
+ }
178
+ fnCode = declarations + assignments;
179
+ }
180
+ return fnCode;
181
+ }
182
+ static sha256Key(str) {
183
+ return "generated_" + sha256.create().update(str).hex();
184
+ }
185
+ static getFnAst(fnSrc) {
186
+ const fnCodeHash = SvalPlus.sha256Key(fnSrc.fnCode);
187
+ const cached = SvalPlus.fnAstCache.get(fnCodeHash);
188
+ if (cached) {
189
+ return cached;
190
+ }
191
+ const fnCallString = `
192
+
193
+ //This is the code that is ran each time the monitored function is called and the result is returned through the exports variable.
194
+
195
+ exports.${SvalPlus.resultExport} = ${fnSrc.fnName}(...${SvalPlus.argsVar});`;
196
+ const fnCodeAst = parse(fnSrc.fnCode, SvalPlus.meriyahParseOptions);
197
+ const fnCallAst = parse(fnCallString, SvalPlus.meriyahParseOptions);
198
+ const ast = {
199
+ fnCode: fnCodeAst,
200
+ fnCall: fnCallAst,
201
+ fnCallString
202
+ };
203
+ SvalPlus.fnAstCache.set(fnCodeHash, ast);
204
+ return ast;
205
+ }
206
+ static refErrMsg(err) {
207
+ return ansis.red.underline(`
208
+ Reference Error`) + Colors.orange(`
209
+ -Monitored functions cannot access data outside the isolated interpreter.
210
+
211
+ -The data must be either be passed as an argument on each call,captured into the monitored fn upon creation or embedded through the embed property when calling monitor.fn(). (inlining only works for functions).
212
+
213
+ -Captured variables are handled outside the interpreter and thus,outside the monitor's tracking system but embedded functions can be monitored.`) + ansis.red.underline(`
214
+
215
+ Trace`) + `
216
+ ${err}`;
217
+ }
218
+ argImports = {
219
+ [SvalPlus.argsVar]: null
220
+ //we firstly set it to null to prevent creating a wasted empty object
221
+ };
222
+ normalizeErr(err) {
223
+ if (err instanceof ReferenceError) {
224
+ err.message = SvalPlus.refErrMsg(err);
225
+ return err;
226
+ } else {
227
+ const error = err instanceof Error ? err : new Error(String(err));
228
+ error.message = ansis.red.underline(`
229
+ Error in Monitored Function:`) + `
230
+ ${error.message}`;
231
+ return error;
232
+ }
233
+ }
234
+ runMonitoredFn = (...args) => {
235
+ this.stage = "MONITORING";
236
+ let result;
237
+ if (this.fnBeforeEachCall !== void 0) {
238
+ this.fnBeforeEachCall(...args);
239
+ }
240
+ this.argImports[SvalPlus.argsVar] = args;
241
+ this.import(this.argImports);
242
+ try {
243
+ this.run(this.astInUse.fnCall);
244
+ result = this.exports[SvalPlus.resultExport];
245
+ } catch (err) {
246
+ result = this.normalizeErr(err);
247
+ }
248
+ if (result instanceof Promise) {
249
+ return result.then((res) => {
250
+ if (this.fnAfterEachCall) this.fnAfterEachCall(res);
251
+ return res;
252
+ }).catch((err) => {
253
+ const error = this.normalizeErr(err);
254
+ if (this.fnAfterEachCall) this.fnAfterEachCall(error);
255
+ throw error;
256
+ }).finally(() => {
257
+ this.stage = "IDLE";
258
+ });
259
+ } else {
260
+ try {
261
+ if (this.fnAfterEachCall !== void 0) this.fnAfterEachCall(result);
262
+ if (result instanceof Error) throw result;
263
+ return result;
264
+ } finally {
265
+ this.stage = "IDLE";
266
+ }
267
+ }
268
+ };
269
+ }
270
+ const Colors = {
271
+ orange: ansis.hex("#f6c098")
272
+ };
273
+ function monitor(setup) {
274
+ const { ref: mainFn, captures } = setup.main;
275
+ if ("alreadyMonitored" in mainFn) {
276
+ throw new Error(ansis.red(`
277
+ A monitored function cannot be monitored.`));
278
+ }
279
+ const {
280
+ embed: functionsToEmbed,
281
+ inspector,
282
+ onStep,
283
+ beforeEachCall,
284
+ afterEachCall,
285
+ sourceOut
286
+ } = setup;
287
+ const interpreter = new SvalPlus({
288
+ inspector,
289
+ onStep,
290
+ fnBeforeEachCall: beforeEachCall,
291
+ fnAfterEachCall: afterEachCall,
292
+ options: SvalPlus.defaultOptions
293
+ });
294
+ interpreter.stage = "PRE-PROCESSING";
295
+ interpreter.exports[SvalPlus.capturesVar] = captures || /* @__PURE__ */ Object.create(null);
296
+ const fnSrc = interpreter.getFnSrc(mainFn, SvalPlus.capturesVar);
297
+ fnSrc.fnCode += interpreter.getFnSources(functionsToEmbed);
298
+ const ast = SvalPlus.getFnAst(fnSrc);
299
+ interpreter.run(ast.fnCode);
300
+ if (sourceOut) {
301
+ sourceOut.value = jsBeatutify(
302
+ fnSrc.fnCode + ast.fnCallString,
303
+ { indent_size: 4 }
304
+ );
305
+ }
306
+ interpreter.astInUse = ast;
307
+ const newFn = interpreter.runMonitoredFn;
308
+ newFn["alreadyMonitored"] = true;
309
+ return newFn;
310
+ }
311
+
312
+ export { LAZY_NODE, NOT_ALLOCATED, QList, ReadonlyQList, monitor };
@@ -0,0 +1,39 @@
1
+ /**A custom optimized dequeue with the random access of an array in one data structure */
2
+ export declare class QList<T> {
3
+ private static readonly EDGE_OF_HEAD;
4
+ private static readonly LEFT_SHIFT;
5
+ private static readonly RIGHT_SHIFT;
6
+ private static readonly LEAST_ARRAY_LENGTH;
7
+ private static readonly MAX_HEAD_TO_TAIL_RATIO;
8
+ private static readonly MEDIUM_ARRAY_SIZE;
9
+ private arr;
10
+ private start;
11
+ constructor(init?: T[]);
12
+ private tailSize;
13
+ private expand;
14
+ push(element: T): void;
15
+ unshift(element: T): void;
16
+ private minimize;
17
+ pop(): T | undefined;
18
+ shift(): T | undefined;
19
+ clear(): void;
20
+ private validateIndex;
21
+ /**Get the element at the specified index.It can accept negative indices like the at method of an array */
22
+ get(i: number): T;
23
+ /**Setthe specified index to an element.It can accept negative indices*/
24
+ set(i: number, element: T): undefined;
25
+ [Symbol.iterator](): Generator<T, void, unknown>;
26
+ entries(): Generator<[number, T], void, unknown>;
27
+ get length(): number;
28
+ }
29
+ /**A sub-version of the QList that removes mutation. */
30
+ export declare class ReadonlyQList<T> {
31
+ private qList;
32
+ constructor(qList?: QList<T>);
33
+ private getQList;
34
+ swapSrc(newQList: QList<T>): void;
35
+ get(i: number): T;
36
+ [Symbol.iterator](): Generator<T, void, unknown>;
37
+ entries(): Generator<[number, T], void, unknown>;
38
+ get length(): number;
39
+ }
package/dist/q-list.js ADDED
@@ -0,0 +1,146 @@
1
+ import ansis from 'ansis';
2
+
3
+ class QList {
4
+ static EDGE_OF_HEAD = 0;
5
+ static LEFT_SHIFT = -1;
6
+ static RIGHT_SHIFT = 1;
7
+ static LEAST_ARRAY_LENGTH = 5;
8
+ static MAX_HEAD_TO_TAIL_RATIO = 2;
9
+ static MEDIUM_ARRAY_SIZE = 50;
10
+ arr = [];
11
+ start = QList.EDGE_OF_HEAD;
12
+ constructor(init) {
13
+ if (init) {
14
+ for (const element of init) {
15
+ this.push(element);
16
+ }
17
+ }
18
+ }
19
+ //HELPERS
20
+ tailSize() {
21
+ return this.arr.length - this.start;
22
+ }
23
+ //ADDING ITEMS
24
+ expand(size) {
25
+ const TAIL_SIZE = this.tailSize();
26
+ const newArrSize = size + TAIL_SIZE;
27
+ const newArr = new Array(newArrSize);
28
+ for (let i = 0; i < TAIL_SIZE; i++) {
29
+ newArr[size + i] = this.arr[this.start + i];
30
+ }
31
+ this.arr = newArr;
32
+ this.start = size;
33
+ }
34
+ push(element) {
35
+ this.arr[this.arr.length] = element;
36
+ }
37
+ unshift(element) {
38
+ if (this.start === QList.EDGE_OF_HEAD) {
39
+ const HALF_THE_TAIL_SIZE = this.tailSize() / 2;
40
+ const EXPANSION_SIZE = Math.max(Math.ceil(HALF_THE_TAIL_SIZE), QList.LEAST_ARRAY_LENGTH);
41
+ this.expand(EXPANSION_SIZE);
42
+ }
43
+ this.start += QList.LEFT_SHIFT;
44
+ this.arr[this.start] = element;
45
+ }
46
+ //REMOVING ITEMS
47
+ minimize() {
48
+ if (this.arr.length > QList.MEDIUM_ARRAY_SIZE) {
49
+ const HEAD_SIZE = this.start;
50
+ const TAIL_SIZE = Math.max(this.tailSize(), 1);
51
+ if (HEAD_SIZE / TAIL_SIZE >= QList.MAX_HEAD_TO_TAIL_RATIO) {
52
+ const NEW_EDGE_OF_HEAD = HEAD_SIZE - TAIL_SIZE;
53
+ this.arr = this.arr.slice(NEW_EDGE_OF_HEAD);
54
+ this.start = HEAD_SIZE - NEW_EDGE_OF_HEAD;
55
+ }
56
+ }
57
+ }
58
+ pop() {
59
+ if (this.tailSize() === 0) return void 0;
60
+ const element = this.arr[this.arr.length - 1];
61
+ this.arr.length = this.arr.length - 1;
62
+ this.minimize();
63
+ return element;
64
+ }
65
+ shift() {
66
+ if (this.tailSize() === 0) return void 0;
67
+ const element = this.arr[this.start];
68
+ this.arr[this.start] = void 0;
69
+ this.start += QList.RIGHT_SHIFT;
70
+ this.minimize();
71
+ return element;
72
+ }
73
+ clear() {
74
+ this.arr = new Array(QList.LEAST_ARRAY_LENGTH);
75
+ this.start = QList.LEAST_ARRAY_LENGTH;
76
+ }
77
+ //RANDOM ACCESS
78
+ validateIndex(i) {
79
+ const TAIL_SIZE = this.tailSize();
80
+ if (i < 0 || i >= TAIL_SIZE || TAIL_SIZE === 0) {
81
+ throw new Error(ansis.red(`
82
+ Invalid random access in QList. Index ${i} not available (length: ${this.tailSize()})`));
83
+ }
84
+ }
85
+ /**Get the element at the specified index.It can accept negative indices like the at method of an array */
86
+ get(i) {
87
+ i = i < 0 ? this.length + i : i;
88
+ this.validateIndex(i);
89
+ const index = this.start + i;
90
+ return this.arr[index];
91
+ }
92
+ /**Setthe specified index to an element.It can accept negative indices*/
93
+ set(i, element) {
94
+ i = i < 0 ? this.length + i : i;
95
+ this.validateIndex(i);
96
+ const index = this.start + i;
97
+ this.arr[index] = element;
98
+ }
99
+ //GETTERS
100
+ *[Symbol.iterator]() {
101
+ for (let i = 0; i < this.tailSize(); i++) {
102
+ yield this.get(i);
103
+ }
104
+ }
105
+ *entries() {
106
+ for (let i = 0; i < this.tailSize(); i++) {
107
+ yield [i, this.get(i)];
108
+ }
109
+ }
110
+ get length() {
111
+ return this.tailSize();
112
+ }
113
+ }
114
+ class ReadonlyQList {
115
+ qList;
116
+ constructor(qList) {
117
+ this.qList = qList;
118
+ }
119
+ getQList() {
120
+ if (this.qList === void 0) {
121
+ throw new Error(ansis.red(`ReadonlyQList Error:Cannot use this method because the object was not given a src qList.`));
122
+ }
123
+ return this.qList;
124
+ }
125
+ swapSrc(newQList) {
126
+ this.qList = newQList;
127
+ }
128
+ get(i) {
129
+ const qList = this.getQList();
130
+ return qList.get(i);
131
+ }
132
+ *[Symbol.iterator]() {
133
+ const qList = this.getQList();
134
+ yield* qList;
135
+ }
136
+ *entries() {
137
+ const qList = this.getQList();
138
+ yield* qList.entries();
139
+ }
140
+ get length() {
141
+ const qList = this.getQList();
142
+ return qList.length;
143
+ }
144
+ }
145
+
146
+ export { QList, ReadonlyQList };
@@ -0,0 +1,81 @@
1
+ import { Variable, Var } from './variable.ts';
2
+ /**
3
+ * Scope simulation class
4
+ */
5
+ export default class Scope<T = any> {
6
+ /**
7
+ * The parent scope along the scope chain
8
+ * @private
9
+ * @readonly
10
+ */
11
+ private readonly parent;
12
+ /**
13
+ * To distinguish function scope and block scope
14
+ * The value is true for function scope or false for block scope
15
+ * @private
16
+ * @readonly
17
+ */
18
+ private readonly isolated;
19
+ /**
20
+ * Context simulation object
21
+ * @private
22
+ * @readonly
23
+ */
24
+ private readonly context;
25
+ /**
26
+ * To memoize the object for with-statement context
27
+ * @private
28
+ */
29
+ private withContext;
30
+ /**
31
+ * Create a simulated scope
32
+ * @param parent the parent scope along the scope chain (default: null)
33
+ * @param isolated true for function scope or false for block scope (default: false)
34
+ */
35
+ interpreter: T | undefined;
36
+ constructor(parent: Scope | null, isolated?: boolean, interpreter?: T);
37
+ hasParent(): boolean;
38
+ get scopeContext(): {
39
+ [key: string]: Var;
40
+ };
41
+ get scopeParent(): Scope<any>;
42
+ get scopeDepth(): number;
43
+ /**
44
+ * Get global scope
45
+ */
46
+ global(): Scope;
47
+ /**
48
+ * Find a variable along scope chain
49
+ * @param name variable identifier name
50
+ */
51
+ find(name: string): Variable | null;
52
+ /**
53
+ * Declare a var variable
54
+ * @param name variable identifier name
55
+ * @param value variable value
56
+ */
57
+ var(name: string, value?: any): void;
58
+ /**
59
+ * Declare a let variable
60
+ * @param name variable identifier name
61
+ * @param value variable value
62
+ */
63
+ let(name: string, value: any): void;
64
+ /**
65
+ * Declare a const variable
66
+ * @param name variable identifier name
67
+ * @param value variable value
68
+ */
69
+ const(name: string, value: any): void;
70
+ /**
71
+ * Declare a function
72
+ * @param name function name
73
+ * @param value function
74
+ */
75
+ func(name: string, value: any): void;
76
+ /**
77
+ * Memoize the object for with-statement context
78
+ * @param value object
79
+ */
80
+ with(value: any): void;
81
+ }