@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.
- package/ACKNOWLEDGEMENTS.md +10 -0
- package/LICENSE.md +10 -0
- package/README.md +201 -0
- package/dist/custom-types.d.ts +239 -0
- package/dist/custom-types.js +341 -0
- package/dist/evaluate/declaration.d.ts +27 -0
- package/dist/evaluate/declaration.js +289 -0
- package/dist/evaluate/expression.d.ts +40 -0
- package/dist/evaluate/expression.js +604 -0
- package/dist/evaluate/helper.d.ts +18 -0
- package/dist/evaluate/helper.js +270 -0
- package/dist/evaluate/identifier.d.ts +7 -0
- package/dist/evaluate/identifier.js +27 -0
- package/dist/evaluate/index.d.ts +3 -0
- package/dist/evaluate/index.js +112 -0
- package/dist/evaluate/literal.d.ts +3 -0
- package/dist/evaluate/literal.js +8 -0
- package/dist/evaluate/pattern.d.ts +13 -0
- package/dist/evaluate/pattern.js +118 -0
- package/dist/evaluate/program.d.ts +3 -0
- package/dist/evaluate/program.js +20 -0
- package/dist/evaluate/statement.d.ts +35 -0
- package/dist/evaluate/statement.js +323 -0
- package/dist/evaluate_n/declaration.d.ts +27 -0
- package/dist/evaluate_n/declaration.js +289 -0
- package/dist/evaluate_n/expression.d.ts +38 -0
- package/dist/evaluate_n/expression.js +596 -0
- package/dist/evaluate_n/helper.d.ts +18 -0
- package/dist/evaluate_n/helper.js +238 -0
- package/dist/evaluate_n/identifier.d.ts +7 -0
- package/dist/evaluate_n/identifier.js +27 -0
- package/dist/evaluate_n/index.d.ts +3 -0
- package/dist/evaluate_n/index.js +76 -0
- package/dist/evaluate_n/literal.d.ts +3 -0
- package/dist/evaluate_n/literal.js +8 -0
- package/dist/evaluate_n/pattern.d.ts +13 -0
- package/dist/evaluate_n/pattern.js +118 -0
- package/dist/evaluate_n/program.d.ts +3 -0
- package/dist/evaluate_n/program.js +20 -0
- package/dist/evaluate_n/statement.d.ts +35 -0
- package/dist/evaluate_n/statement.js +301 -0
- package/dist/examples/example-2.d.ts +1 -0
- package/dist/examples/example.d.ts +1 -0
- package/dist/helper-functions.d.ts +15 -0
- package/dist/helper-functions.js +104 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +312 -0
- package/dist/q-list.d.ts +39 -0
- package/dist/q-list.js +146 -0
- package/dist/scope/index.d.ts +81 -0
- package/dist/scope/index.js +168 -0
- package/dist/scope/variable.d.ts +20 -0
- package/dist/scope/variable.js +38 -0
- package/dist/share/async.d.ts +7 -0
- package/dist/share/async.js +43 -0
- package/dist/share/const.d.ts +25 -0
- package/dist/share/const.js +21 -0
- package/dist/share/util.d.ts +33 -0
- package/dist/share/util.js +379 -0
- package/dist/sval.d.ts +15 -0
- package/dist/sval.js +104 -0
- 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 };
|
package/dist/q-list.d.ts
ADDED
|
@@ -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
|
+
}
|