pure-dango 1.8.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.
@@ -0,0 +1,534 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import {runtimeErrors, utilsErrors} from "../runtime/errors";
4
+
5
+ import {GF, isGFloat} from "./interpreter";
6
+ import {errorTemplate} from "../runtime/stdlib";
7
+
8
+ type RuntimeState =
9
+ {
10
+ time: number
11
+ }
12
+ type Bytecode = Array<string | number>;
13
+ type CacheFolder = string;
14
+
15
+ export const formatParameter = (parameter : any) : string =>
16
+ {
17
+ let result = parameter.rest ?
18
+ `...${parameter.name}`
19
+ : parameter.name;
20
+
21
+ if (parameter.default)
22
+ result += ` = ${generateOrigin(parameter.default, 0)}`;
23
+
24
+ return result;
25
+ }
26
+
27
+ export const functionOrigin = (node : any, padding : string) : string =>
28
+ {
29
+ const indent = padding.length;
30
+ const parameters = node.parameters.map(formatParameter).join(", ");
31
+ if (node.body.length === 0)
32
+ return `${padding}function ${node.name ? node.name : ""}(${parameters}) {}`;
33
+
34
+ return `${padding}function ${node.name ? node.name : ""}(${parameters}) {\n` +
35
+ `${node.body.map((child : any) => generateOrigin(child, indent + 4)).join("\n")}\n` +
36
+ `${padding}}`;
37
+ }
38
+
39
+ export const binaryOrigin = (node : any, padding : string) : string =>
40
+ {
41
+ return `${padding}${generateOrigin(node.left, 0)} ${node.operator} ${generateOrigin(node.right, 0)}`;
42
+ }
43
+
44
+ const origins : Record<string, Function> = Object.freeze({
45
+ FunctionExpression : (node : any, padding : string) : string => functionOrigin(node, padding),
46
+ FunctionDeclaration : (node : any, padding : string) : string => functionOrigin(node, padding),
47
+
48
+ Literal : (node : any, padding : string) : string => `${padding}${node.value}`,
49
+ StringLiteral: (node: any, padding: string): string =>
50
+ {
51
+ const escaped = node.value
52
+ .replace(/\\/g, "\\\\")
53
+ .replace(/\n/g, "\\n")
54
+ .replace(/\t/g, "\\t")
55
+ .replace(/\r/g, "\\r")
56
+ .replace(/"/g, '\\"');
57
+ return `${padding}"${escaped}"`;
58
+ },
59
+ VariableReference : (node : any, padding : string) : string => `${padding}${node.value}`,
60
+
61
+ ArrayLiteral : (node : any, padding : string) : string => `${padding}[${node.elements.map((element : any) => generateOrigin(element, 0)).join(", ")}]`,
62
+ ArrayAccess : (node : any, padding : string) : string => `${padding}${generateOrigin(node.object, 0)}[${generateOrigin(node.index, 0)}]`,
63
+ ObjectLiteral : (node : any, padding : string) : string =>
64
+ `${padding}{\n` +
65
+ node.properties
66
+ .map((property : {key: string, value : any}) =>
67
+ `${property.key}: ${generateOrigin(property.value, 0)}`
68
+ )
69
+ .join(", ") +
70
+ `}`,
71
+
72
+ ClassDeclaration : (node : any, padding : string) : string =>
73
+ {
74
+ const methods: string = node.methods
75
+ .map((method: any) => {
76
+ const indent = padding.length + 4;
77
+ const pad = " ".repeat(indent);
78
+
79
+ const parameters = method.parameters.map(formatParameter).join(", "); // was node.parameters
80
+
81
+ if (method.body.length === 0)
82
+ return `${pad}${method.name}(${parameters}) {}`;
83
+
84
+ return `${pad}${method.name}(${parameters}) {\n` +
85
+ method.body.map((child: any) => generateOrigin(child, indent + 4)).join("\n") +
86
+ `\n${pad}}`;
87
+ })
88
+ .join("\n\n");
89
+
90
+ const properties: string = (node.properties ?? [])
91
+ .slice(1)
92
+ .map((property: any) => {
93
+ const indent = padding.length + 4;
94
+ const pad = " ".repeat(indent);
95
+
96
+ if (property.value?.type === "FunctionExpression")
97
+ {
98
+ const parameters = property.value.parameters.map(formatParameter).join(", ");
99
+ if (property.value.body.length === 0)
100
+ return `${pad}${property.key}(${parameters}) {}`;
101
+
102
+ return `${pad}${property.key}(${parameters}) {\n` +
103
+ property.value.body.map((child: any) => generateOrigin(child, indent + 4)).join("\n") +
104
+ `\n${pad}}`;
105
+ }
106
+
107
+ return `${pad}${property.key}: ${generateOrigin(property.value, 0)}`;
108
+ })
109
+ .join("\n\n");
110
+
111
+ return `${padding}${node.name} {\n` +
112
+ `${methods ? methods + "\n" : ""}` +
113
+ `${properties ? properties + "\n" : ""}` +
114
+ `${padding}}`;
115
+ },
116
+
117
+ MethodCall : (node : any, padding : string) : string =>
118
+ `${padding}${generateOrigin(node.object, 0)}.${node.property}(${node.args.map((a : any) => generateOrigin(a, 0)).join(", ")})`,
119
+
120
+ ClassInstantiation : (node : any, padding: string) =>
121
+ {
122
+ return `${padding}new ${node.variableName} = inst ${node.className}(${node.args.map((x : any) => generateOrigin(x, 0)).join(", ")})`
123
+ },
124
+
125
+ MemberExpression : (node : any, padding : string) : string => `${padding}${generateOrigin(node.object, 0)}.${node.property}`,
126
+
127
+ Assignment : (node : any, padding : string) : string =>
128
+ {
129
+ const isCompound : boolean =
130
+ (node.value.type === "BinaryExpression" || node.value.type === "LogicalExpression") &&
131
+ node.value.left?.type === "VariableReference" &&
132
+ node.value.left?.value === node.name;
133
+ const operator : string = isCompound ? (node.value.operator + "=") : "=";
134
+ const value : any = isCompound ? generateOrigin(node.value.right, 0) : generateOrigin(node.value, 0);
135
+
136
+ const target : string = typeof node.name === "string" ? node.name : generateOrigin(node.name, 0);
137
+ return `${padding}${target} ${operator} ${value};`;
138
+ },
139
+
140
+ NewAssignment : (node : any, padding : string) : string =>
141
+ {
142
+ const isCompound: boolean =
143
+ (node.value.type === "BinaryExpression" || node.value.type === "LogicalExpression") &&
144
+ node.value.left?.type === "VariableReference" &&
145
+ node.value.left?.value === node.name;
146
+ const operator : string = isCompound ? (node.value.operator + "=") : "=";
147
+ const value : any = isCompound ? generateOrigin(node.value.right, 0) : generateOrigin(node.value, 0);
148
+
149
+ const target : string = typeof node.name === "string" ? node.name : generateOrigin(node.name, 0);
150
+ return `${padding}new ${target} ${operator} ${value};`;
151
+ },
152
+ NewDeclaration : (node : any, padding : string) : string => `${padding}new ${node.name};`,
153
+
154
+ UnaryExpression : (node : any, padding : string) : string => `${padding}${node.value === "-u" || node.value === "+u" ? node.value[0] : node.value}${generateOrigin(node.argument, 0)}`,
155
+ PostfixUnaryExpression : (node : any, padding : string) : string => `${padding}${generateOrigin(node.argument, 0)}${node.operator}`,
156
+ BinaryExpression : (node : any, padding : string) : string => binaryOrigin(node, padding),
157
+ LogicalExpression : (node : any, padding : string) : string => binaryOrigin(node, padding),
158
+
159
+ IfStatement : (node : any, padding : string) : string =>
160
+ {
161
+ const indent : number = padding.length;
162
+ const elseBlock : string = node.else
163
+ ? `\n${padding}else {\n` +
164
+ `${Array.isArray(node.else) ? generateOrigin(node.else[0], indent + 4) : generateOrigin(node.else, indent + 4)}` +
165
+ `\n${padding}}`
166
+ : "";
167
+
168
+ return `${padding}if (${generateOrigin(node.condition, 0)}) {\n` +
169
+ node.body.map((child : any) => generateOrigin(child, indent + 4)).join("\n") +
170
+ `\n${padding}}\n` +
171
+ elseBlock;
172
+ },
173
+
174
+ TernaryExpression : (node : any, padding : string) : string => `${padding}${generateOrigin(node.condition, 0)} ? ${generateOrigin(node.then, 0)} : ${generateOrigin(node.else, 0)}`,
175
+
176
+ ForStatement : (node : any, padding : string) : string =>
177
+ {
178
+ const indent = padding.length;
179
+
180
+ const initial = generateOrigin(node.initial, 0)?.replace(/;$/, "") ?? "";
181
+ const condition = generateOrigin(node.condition, 0) ?? "";
182
+ const update = generateOrigin(node.update, 0)?.replace(/;$/, "") ?? "";
183
+ return `${padding}for (${initial}; ${condition}; ${update}) {\n` +
184
+ node.body.map((child : any) => generateOrigin(child, indent + 4)).join("\n") +
185
+ `\n${padding}}`
186
+ },
187
+
188
+ ForInStatement : (node : any, padding : string) : string =>
189
+ {
190
+ const indent = padding.length;
191
+ return `${padding}for (${node.left} in ${generateOrigin(node.right, 0)}) {\n` +
192
+ node.body.map((child : any) => generateOrigin(child, indent + 4)).join("\n") +
193
+ `\n${padding}}`;
194
+ },
195
+
196
+ ForOfStatement : (node : any, padding : string) : string =>
197
+ {
198
+ const indent = padding.length;
199
+ return `${padding}for (${node.left} of ${generateOrigin(node.right, 0)}) {\n` +
200
+ node.body.map((child : any) => generateOrigin(child, indent + 4)).join("\n") +
201
+ `\n${padding}}`;
202
+ },
203
+
204
+ WhileStatement : (node : any, padding : string) : string =>
205
+ {
206
+ const indent = padding.length;
207
+ return `${padding}while (${generateOrigin(node.condition, 0)}) {\n` +
208
+ node.body.map((child : any) => generateOrigin(child, indent + 4)).join("\n") +
209
+ `\n${padding}}`
210
+ },
211
+
212
+ DoWhileStatement : (node : any, padding : string) : string =>
213
+ {
214
+ const indent = padding.length;
215
+ return `${padding}do {\n` +
216
+ node.body.map((child : any) => generateOrigin(child, indent + 4)).join("\n") +
217
+ `\n${padding}} while (${generateOrigin(node.condition, 0)});`;
218
+ },
219
+
220
+ TryStatement : (node : any, padding : string) : string =>
221
+ {
222
+ const indent = padding.length;
223
+
224
+ const tryBlock = node.tryBlock.map((child : any) => generateOrigin(child, indent + 4)).join("\n");
225
+ const catchBlock = node.catchBlock.map((child : any) => generateOrigin(child, indent + 4)).join("\n");
226
+ const finallyBlock = node.finallyBlock
227
+ ? `\n${padding}finally {\n` +
228
+ node.finallyBlock.map((child : any) => generateOrigin(child, indent + 4)).join("\n") +
229
+ `\n${padding}}`
230
+ : "";
231
+
232
+ return `${padding}try {\n` +
233
+ `${tryBlock}\n` +
234
+ `${padding}} catch (${node.errorVariable}) {` +
235
+ `\n${catchBlock}\n` +
236
+ `${padding}}` +
237
+ `${finallyBlock}`;
238
+ },
239
+
240
+ SwitchStatement : (node : any, padding : string) : string =>
241
+ {
242
+ const indent = padding.length;
243
+ const pad = " ".repeat(indent + 4);
244
+
245
+ const cases = node.cases.map((child : any) =>
246
+ `${pad}case ${generateOrigin(child.test, 0)}:\n` +
247
+ child.consequent.map((statement: any) => generateOrigin(statement, indent + 8)).join("\n")
248
+ ).join("\n");
249
+
250
+ const defaultCase = node.defaultCase
251
+ ? `\n${pad}default:\n` +
252
+ node.defaultCase.consequent.map((statement: any) => generateOrigin(statement, indent + 8)).join("\n")
253
+ : "";
254
+
255
+ return `${padding}switch (${generateOrigin(node.discriminant, 0)}) {` +
256
+ `\n${cases}${defaultCase}\n` +
257
+ `${padding}}`;
258
+ },
259
+
260
+ FunctionCall : (node : any, padding : string) : string => `${padding}${node.name}(${node.args.map((argument : any) => generateOrigin(argument, 0)).join(", ")})`,
261
+ ReturnStatement : (node : any, padding : string) : string => `${padding}return${node.argument ? " " + generateOrigin(node.argument, 0) : ""};`,
262
+
263
+ BreakStatement : (node : any, padding : string) : string => `${padding}break;`,
264
+ ContinueStatement : (node : any, padding : string) : string => `${padding}continue;`,
265
+
266
+ ImportStatement : (node : any, padding : string) : string => `${padding}import "${node.path}";`
267
+ });
268
+
269
+ export const generateOrigin = (node : any, indent = 0) : string | null =>
270
+ {
271
+ if (!node)
272
+ return null;
273
+
274
+ const padding = " ".repeat(indent); // get the padding
275
+ const func = origins[node.type];
276
+
277
+ if (func)
278
+ return func(node, padding);
279
+ throw new runtimeErrors.InternalError(`No origin generator for node type "${node.type}"`);
280
+ }
281
+
282
+ // stdlib
283
+ export const maxArguments = (maxLength: number, list: any[], name: string) : void =>
284
+ {
285
+ let plural = maxLength === 1 ? "argument": "arguments";
286
+ if (list.length > maxLength)
287
+ throw new utilsErrors.FunctionArgumentError(name, maxLength, plural);
288
+ }
289
+
290
+ export const bigIntPow = (base: bigint, exponent: bigint) : bigint | any =>
291
+ {
292
+ if (exponent < 0n)
293
+ return GF(base.toString()).inner.pow(GF(exponent.toString()));
294
+ if (exponent === 0n && base === 0n)
295
+ errorTemplate("bigIntPow", `0n raised to 0n can't be evaluated`);
296
+ if (exponent === 0n)
297
+ return 1n;
298
+
299
+ let result = 1n;
300
+ while (exponent > 0n)
301
+ {
302
+ if ((exponent & 1n) === 1n) result *= base; // use exponentiation by squaring. "(exponent & 1) === 1n" checks if the exponent is an odd number
303
+ base *= base;
304
+ exponent >>= 1n; // integer division operator
305
+ }
306
+
307
+ return result;
308
+ }
309
+
310
+ export const typeHandler = (item: any) : string =>
311
+ {
312
+ if (item.isInstance)
313
+ {
314
+ if (item.properties["0"])
315
+ return format(item.properties["0"])!;
316
+
317
+ const properties = Object.entries(item.properties ?? {})
318
+ .filter(([key]) => key !== "0") // filter out __INIT__
319
+ .map(([key, value]) => ` ${key}: ${format(value)}`)
320
+ .join(",\n ");
321
+
322
+ const methods = Object.values(item.methods ?? {})
323
+ .filter((method : any) => method.name !== "constructor")
324
+ .map((method : any) => ` ${method.name}(${method.parameters.map((parameter: any) => parameter.name).join(", ")})`)
325
+ .join("\n");
326
+
327
+ const hasContent = properties || methods;
328
+ return hasContent
329
+ ? `${item.class} {\n${properties ? properties + "\n" : ""}${methods ? methods + "\n" : ""}}`
330
+ : `${item.class} {}`;
331
+ }
332
+
333
+ switch (item.type)
334
+ {
335
+ case "object":
336
+ {
337
+ const entries = Object.entries(item.value).map(([key, value]) => `${key}: ${format(value)}`).join(", ");
338
+ return `{${entries}}`;
339
+ }
340
+
341
+ case "class":
342
+ {
343
+ const methods = Object.values(item.methods ?? {})
344
+ .filter((method: any) => method.name !== "constructor")
345
+ .map((method: any) => ` ${method.name}(${method.parameters.map((p: any) => p.name).join(", ")})`)
346
+ .join("\n");
347
+
348
+ const properties = Object.entries(item.properties ?? {})
349
+ .filter(([key]) => key !== "0")
350
+ .map(([key, value]: [string, any]) => {
351
+ if (typeof value === "function" || value?.bytecode)
352
+ return ` ${key}(${(value.parameters ?? []).map((p: any) => p.name).join(", ")})`;
353
+ return ` ${key}: ${value}`;
354
+ })
355
+ .join("\n");
356
+
357
+ return `${item.name ?? "unknown"} {\n` +
358
+ `${methods ? methods + "\n" : ""}` +
359
+ `${properties ? properties + "\n" : ""}` +
360
+ `}`;
361
+ }
362
+
363
+ default:
364
+ {
365
+ throw new runtimeErrors.InternalError(`Type of "${item}" is not implemented in typeHandler`);
366
+ }
367
+ }
368
+ }
369
+
370
+ export const format = (item: any) : string | null =>
371
+ {
372
+ if (item === null || item === undefined)
373
+ return item;
374
+
375
+ if (isGFloat(item))
376
+ return item.inner.toString();
377
+
378
+ if (Array.isArray(item))
379
+ return "[" +
380
+ item.map(x =>
381
+ {
382
+ const formatted : any = format(x);
383
+ return formatted;
384
+ }).join(", ") + "]";
385
+
386
+ if (item.type)
387
+ return typeHandler(item);
388
+
389
+ if (typeof item === "object")
390
+ return generateOrigin(item.ast)?.replace(/\\/g, "\\\\")!;
391
+
392
+ return item;
393
+ }
394
+
395
+ // FON (FormatObjectNotation) is a version of format designed for console.dir()
396
+ export const FON = (item: any) : any =>
397
+ {
398
+ if (item === null || item === undefined)
399
+ return item;
400
+
401
+ if (typeof item === "number" || typeof item === "bigint")
402
+ return item;
403
+ if (isGFloat(item))
404
+ return item.toString();
405
+ if (Array.isArray(item))
406
+ return item.map(FON);
407
+
408
+ if (item?.type === "object")
409
+ {
410
+ const result : Record<any, any> = {};
411
+ for (const [key, value] of Object.entries(item.value))
412
+ result[key] = FON(value);
413
+ return result;
414
+ }
415
+
416
+ if (item?.isInstance)
417
+ {
418
+ const inner: Record<string, any> = {};
419
+ for (const [key, value] of Object.entries(item.properties ?? {}))
420
+ {
421
+ if (key.startsWith("__") && key.endsWith("__"))
422
+ continue;
423
+ inner[key] = FON(value);
424
+ }
425
+
426
+ const wrapper: Record<string, any> = {};
427
+ wrapper[item.class] = inner;
428
+ return wrapper;
429
+ }
430
+
431
+ if (item?.type === "class")
432
+ return `[class ${item.name}]`;
433
+
434
+ if (item?.bytecode)
435
+ return `[function ${item.name ?? "<anonymous>"}]`;
436
+
437
+ return item.toString();
438
+ }
439
+
440
+ export const joinStrings = (list: any[]) : string =>
441
+ {
442
+ return list.reduce((joined, item) => joined + format(item), "");
443
+ }
444
+
445
+ // index
446
+ export const run = async (functionToRun: Function, state: RuntimeState, ...args: any[]) : Promise<any> =>
447
+ {
448
+ const start : number = performance.now();
449
+ const output : any = await functionToRun(...args);
450
+ const end : number = performance.now();
451
+ const duration : number = end - start;
452
+
453
+ state.time += duration;
454
+
455
+ return output;
456
+ }
457
+
458
+ const printResult = (name: string, value: any) : void =>
459
+ {
460
+ console.log(`${name}:`);
461
+ console.dir(value, {depth: null, colors: true});
462
+ console.log("\n");
463
+ }
464
+
465
+ export const printResults = (results: object) : void =>
466
+ {
467
+ for (const [name, value] of Object.entries(results)) if (value !== null) printResult(`${name}`, value);
468
+ }
469
+
470
+ export const saveBytecode = (cacheFolder: CacheFolder, bytecode: Bytecode, srcFile: string, srcMTime: number) : void =>
471
+ {
472
+ const outFile = path.join(cacheFolder, path.basename(srcFile, ".pds") + ".pdbc");
473
+ fs.writeFileSync(
474
+ outFile,
475
+ JSON.stringify(
476
+ {
477
+ version: 1,
478
+ source: path.basename(srcFile),
479
+ bytecode,
480
+ mtime: srcMTime
481
+ },
482
+ null,
483
+ 2,
484
+ )
485
+ );
486
+ }
487
+
488
+ export const loadBytecode = (cacheFolder: string, srcFile: string) : Bytecode | null =>
489
+ {
490
+ const inFile = path.join(cacheFolder, path.basename(srcFile, ".pds") + ".pdbc");
491
+
492
+ if (!fs.existsSync(inFile))
493
+ return null;
494
+
495
+ const data = JSON.parse(fs.readFileSync(inFile, "utf-8"));
496
+ const stats = fs.statSync(srcFile);
497
+ const srcMTime = stats.mtimeMs;
498
+
499
+ if (data.version !== 1 || data.mtime !== srcMTime)
500
+ {
501
+ console.log("recompile");
502
+ return null;
503
+ }
504
+
505
+ return data.bytecode;
506
+ }
507
+
508
+ export const interpretEscapeCharacters = (str: string): string =>
509
+ {
510
+ return String(str).replace(
511
+ /\\(x[0-9a-fA-F]{1,4}|u[0-9a-fA-F]{1,4}|[0-7]{1,3}|.)/g,
512
+ (match, character) =>
513
+ {
514
+ if (character.startsWith('x'))
515
+ return String.fromCharCode(parseInt(character.slice(1), 16));
516
+ if (character.startsWith('u'))
517
+ return String.fromCharCode(parseInt(character.slice(1), 16));
518
+ if (/^[0-7]{1,3}$/.test(character))
519
+ return String.fromCharCode(parseInt(character, 8));
520
+ switch (character)
521
+ {
522
+ case "n": return "\n";
523
+ case "t": return "\t";
524
+ case "b": return "\b";
525
+ case "r": return "\r";
526
+ case '0': return "\0";
527
+ case "'": return "'";
528
+ case '"': return '"';
529
+ case "\\": return "\\";
530
+ default: return match;
531
+ }
532
+ }
533
+ );
534
+ };