littlewing 0.5.3 → 0.7.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/LICENSE +1 -1
- package/README.md +161 -674
- package/dist/index.d.ts +228 -65
- package/dist/index.js +257 -544
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -15,8 +15,8 @@ var exports_ast = {};
|
|
|
15
15
|
__export(exports_ast, {
|
|
16
16
|
unaryOp: () => unaryOp,
|
|
17
17
|
subtract: () => subtract,
|
|
18
|
+
program: () => program,
|
|
18
19
|
number: () => number,
|
|
19
|
-
nullishAssign: () => nullishAssign,
|
|
20
20
|
notEquals: () => notEquals,
|
|
21
21
|
negate: () => negate,
|
|
22
22
|
multiply: () => multiply,
|
|
@@ -37,6 +37,12 @@ __export(exports_ast, {
|
|
|
37
37
|
assign: () => assign,
|
|
38
38
|
add: () => add
|
|
39
39
|
});
|
|
40
|
+
function program(statements) {
|
|
41
|
+
return {
|
|
42
|
+
type: "Program",
|
|
43
|
+
statements
|
|
44
|
+
};
|
|
45
|
+
}
|
|
40
46
|
function number(value) {
|
|
41
47
|
return {
|
|
42
48
|
type: "NumberLiteral",
|
|
@@ -78,13 +84,6 @@ function assign(name, value) {
|
|
|
78
84
|
value
|
|
79
85
|
};
|
|
80
86
|
}
|
|
81
|
-
function nullishAssign(name, value) {
|
|
82
|
-
return {
|
|
83
|
-
type: "NullishAssignment",
|
|
84
|
-
name,
|
|
85
|
-
value
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
87
|
function conditional(condition, consequent, alternate) {
|
|
89
88
|
return {
|
|
90
89
|
type: "ConditionalExpression",
|
|
@@ -163,7 +162,6 @@ var TokenType;
|
|
|
163
162
|
TokenType2["COMMA"] = "COMMA";
|
|
164
163
|
TokenType2["QUESTION"] = "QUESTION";
|
|
165
164
|
TokenType2["COLON"] = "COLON";
|
|
166
|
-
TokenType2["NULLISH_ASSIGN"] = "NULLISH_ASSIGN";
|
|
167
165
|
TokenType2["EOF"] = "EOF";
|
|
168
166
|
})(TokenType ||= {});
|
|
169
167
|
function isNumberLiteral(node) {
|
|
@@ -190,9 +188,6 @@ function isProgram(node) {
|
|
|
190
188
|
function isConditionalExpression(node) {
|
|
191
189
|
return node.type === "ConditionalExpression";
|
|
192
190
|
}
|
|
193
|
-
function isNullishAssignment(node) {
|
|
194
|
-
return node.type === "NullishAssignment";
|
|
195
|
-
}
|
|
196
191
|
|
|
197
192
|
// src/utils.ts
|
|
198
193
|
function evaluateBinaryOperation(operator, left, right) {
|
|
@@ -264,7 +259,6 @@ function getOperatorPrecedence(operator) {
|
|
|
264
259
|
function getTokenPrecedence(type) {
|
|
265
260
|
switch (type) {
|
|
266
261
|
case "EQUALS" /* EQUALS */:
|
|
267
|
-
case "NULLISH_ASSIGN" /* NULLISH_ASSIGN */:
|
|
268
262
|
return 1;
|
|
269
263
|
case "QUESTION" /* QUESTION */:
|
|
270
264
|
return 2;
|
|
@@ -310,8 +304,6 @@ class CodeGenerator {
|
|
|
310
304
|
return this.generateFunctionCall(node);
|
|
311
305
|
if (isAssignment(node))
|
|
312
306
|
return this.generateAssignment(node);
|
|
313
|
-
if (isNullishAssignment(node))
|
|
314
|
-
return this.generateNullishAssignment(node);
|
|
315
307
|
if (isConditionalExpression(node))
|
|
316
308
|
return this.generateConditionalExpression(node);
|
|
317
309
|
throw new Error(`Unknown node type`);
|
|
@@ -348,10 +340,6 @@ class CodeGenerator {
|
|
|
348
340
|
const value = this.generate(node.value);
|
|
349
341
|
return `${node.name} = ${value}`;
|
|
350
342
|
}
|
|
351
|
-
generateNullishAssignment(node) {
|
|
352
|
-
const value = this.generate(node.value);
|
|
353
|
-
return `${node.name} ??= ${value}`;
|
|
354
|
-
}
|
|
355
343
|
generateConditionalExpression(node) {
|
|
356
344
|
const condition = this.generate(node.condition);
|
|
357
345
|
const consequent = this.generate(node.consequent);
|
|
@@ -385,45 +373,197 @@ function generate(node) {
|
|
|
385
373
|
const generator = new CodeGenerator;
|
|
386
374
|
return generator.generate(node);
|
|
387
375
|
}
|
|
376
|
+
// src/date-utils.ts
|
|
377
|
+
var exports_date_utils = {};
|
|
378
|
+
__export(exports_date_utils, {
|
|
379
|
+
TO_UNIX_SECONDS: () => TO_UNIX_SECONDS,
|
|
380
|
+
START_OF_YEAR: () => START_OF_YEAR,
|
|
381
|
+
START_OF_WEEK: () => START_OF_WEEK,
|
|
382
|
+
START_OF_QUARTER: () => START_OF_QUARTER,
|
|
383
|
+
START_OF_MONTH: () => START_OF_MONTH,
|
|
384
|
+
START_OF_DAY: () => START_OF_DAY,
|
|
385
|
+
NOW: () => NOW,
|
|
386
|
+
IS_WEEKEND: () => IS_WEEKEND,
|
|
387
|
+
IS_SAME_DAY: () => IS_SAME_DAY,
|
|
388
|
+
IS_LEAP_YEAR: () => IS_LEAP_YEAR,
|
|
389
|
+
IS_BEFORE: () => IS_BEFORE,
|
|
390
|
+
IS_AFTER: () => IS_AFTER,
|
|
391
|
+
GET_YEAR: () => GET_YEAR,
|
|
392
|
+
GET_WEEKDAY: () => GET_WEEKDAY,
|
|
393
|
+
GET_SECOND: () => GET_SECOND,
|
|
394
|
+
GET_QUARTER: () => GET_QUARTER,
|
|
395
|
+
GET_MONTH: () => GET_MONTH,
|
|
396
|
+
GET_MINUTE: () => GET_MINUTE,
|
|
397
|
+
GET_MILLISECOND: () => GET_MILLISECOND,
|
|
398
|
+
GET_HOUR: () => GET_HOUR,
|
|
399
|
+
GET_DAY_OF_YEAR: () => GET_DAY_OF_YEAR,
|
|
400
|
+
GET_DAY: () => GET_DAY,
|
|
401
|
+
FROM_YEARS: () => FROM_YEARS,
|
|
402
|
+
FROM_WEEKS: () => FROM_WEEKS,
|
|
403
|
+
FROM_UNIX_SECONDS: () => FROM_UNIX_SECONDS,
|
|
404
|
+
FROM_SECONDS: () => FROM_SECONDS,
|
|
405
|
+
FROM_MONTHS: () => FROM_MONTHS,
|
|
406
|
+
FROM_MINUTES: () => FROM_MINUTES,
|
|
407
|
+
FROM_HOURS: () => FROM_HOURS,
|
|
408
|
+
FROM_DAYS: () => FROM_DAYS,
|
|
409
|
+
END_OF_YEAR: () => END_OF_YEAR,
|
|
410
|
+
END_OF_MONTH: () => END_OF_MONTH,
|
|
411
|
+
END_OF_DAY: () => END_OF_DAY,
|
|
412
|
+
DIFFERENCE_IN_WEEKS: () => DIFFERENCE_IN_WEEKS,
|
|
413
|
+
DIFFERENCE_IN_SECONDS: () => DIFFERENCE_IN_SECONDS,
|
|
414
|
+
DIFFERENCE_IN_MINUTES: () => DIFFERENCE_IN_MINUTES,
|
|
415
|
+
DIFFERENCE_IN_HOURS: () => DIFFERENCE_IN_HOURS,
|
|
416
|
+
DIFFERENCE_IN_DAYS: () => DIFFERENCE_IN_DAYS,
|
|
417
|
+
DATE: () => DATE,
|
|
418
|
+
ADD_YEARS: () => ADD_YEARS,
|
|
419
|
+
ADD_MONTHS: () => ADD_MONTHS,
|
|
420
|
+
ADD_DAYS: () => ADD_DAYS
|
|
421
|
+
});
|
|
422
|
+
var NOW = () => Date.now();
|
|
423
|
+
var DATE = (year, month = 1, day = 1, hour = 0, minute = 0, second = 0) => new Date(year, month - 1, day, hour, minute, second).getTime();
|
|
424
|
+
var FROM_SECONDS = (s) => s * 1000;
|
|
425
|
+
var FROM_MINUTES = (m) => m * 60 * 1000;
|
|
426
|
+
var FROM_HOURS = (h) => h * 60 * 60 * 1000;
|
|
427
|
+
var FROM_DAYS = (d) => d * 24 * 60 * 60 * 1000;
|
|
428
|
+
var FROM_WEEKS = (w) => w * 7 * 24 * 60 * 60 * 1000;
|
|
429
|
+
var FROM_MONTHS = (months) => months * 30 * 24 * 60 * 60 * 1000;
|
|
430
|
+
var FROM_YEARS = (years) => years * 365 * 24 * 60 * 60 * 1000;
|
|
431
|
+
var GET_YEAR = (timestamp) => new Date(timestamp).getFullYear();
|
|
432
|
+
var GET_MONTH = (timestamp) => new Date(timestamp).getMonth() + 1;
|
|
433
|
+
var GET_DAY = (timestamp) => new Date(timestamp).getDate();
|
|
434
|
+
var GET_HOUR = (timestamp) => new Date(timestamp).getHours();
|
|
435
|
+
var GET_MINUTE = (timestamp) => new Date(timestamp).getMinutes();
|
|
436
|
+
var GET_SECOND = (timestamp) => new Date(timestamp).getSeconds();
|
|
437
|
+
var GET_MILLISECOND = (timestamp) => new Date(timestamp).getMilliseconds();
|
|
438
|
+
var GET_WEEKDAY = (timestamp) => new Date(timestamp).getDay();
|
|
439
|
+
var GET_DAY_OF_YEAR = (timestamp) => {
|
|
440
|
+
const date = new Date(timestamp);
|
|
441
|
+
const start = new Date(date.getFullYear(), 0, 0);
|
|
442
|
+
const diff = date.getTime() - start.getTime();
|
|
443
|
+
const oneDay = 1000 * 60 * 60 * 24;
|
|
444
|
+
return Math.floor(diff / oneDay);
|
|
445
|
+
};
|
|
446
|
+
var GET_QUARTER = (timestamp) => {
|
|
447
|
+
const month = new Date(timestamp).getMonth();
|
|
448
|
+
return Math.floor(month / 3) + 1;
|
|
449
|
+
};
|
|
450
|
+
var DIFFERENCE_IN_SECONDS = (ts1, ts2) => Math.abs(ts1 - ts2) / 1000;
|
|
451
|
+
var DIFFERENCE_IN_MINUTES = (ts1, ts2) => Math.abs(ts1 - ts2) / (60 * 1000);
|
|
452
|
+
var DIFFERENCE_IN_HOURS = (ts1, ts2) => Math.abs(ts1 - ts2) / (60 * 60 * 1000);
|
|
453
|
+
var DIFFERENCE_IN_DAYS = (ts1, ts2) => Math.abs(ts1 - ts2) / (24 * 60 * 60 * 1000);
|
|
454
|
+
var DIFFERENCE_IN_WEEKS = (ts1, ts2) => Math.abs(ts1 - ts2) / (7 * 24 * 60 * 60 * 1000);
|
|
455
|
+
var START_OF_DAY = (timestamp) => {
|
|
456
|
+
const date = new Date(timestamp);
|
|
457
|
+
date.setHours(0, 0, 0, 0);
|
|
458
|
+
return date.getTime();
|
|
459
|
+
};
|
|
460
|
+
var END_OF_DAY = (timestamp) => {
|
|
461
|
+
const date = new Date(timestamp);
|
|
462
|
+
date.setHours(23, 59, 59, 999);
|
|
463
|
+
return date.getTime();
|
|
464
|
+
};
|
|
465
|
+
var START_OF_WEEK = (timestamp) => {
|
|
466
|
+
const date = new Date(timestamp);
|
|
467
|
+
const day = date.getDay();
|
|
468
|
+
const diff = date.getDate() - day;
|
|
469
|
+
date.setDate(diff);
|
|
470
|
+
date.setHours(0, 0, 0, 0);
|
|
471
|
+
return date.getTime();
|
|
472
|
+
};
|
|
473
|
+
var START_OF_MONTH = (timestamp) => {
|
|
474
|
+
const date = new Date(timestamp);
|
|
475
|
+
date.setDate(1);
|
|
476
|
+
date.setHours(0, 0, 0, 0);
|
|
477
|
+
return date.getTime();
|
|
478
|
+
};
|
|
479
|
+
var END_OF_MONTH = (timestamp) => {
|
|
480
|
+
const date = new Date(timestamp);
|
|
481
|
+
date.setMonth(date.getMonth() + 1, 0);
|
|
482
|
+
date.setHours(23, 59, 59, 999);
|
|
483
|
+
return date.getTime();
|
|
484
|
+
};
|
|
485
|
+
var START_OF_YEAR = (timestamp) => {
|
|
486
|
+
const date = new Date(timestamp);
|
|
487
|
+
date.setMonth(0, 1);
|
|
488
|
+
date.setHours(0, 0, 0, 0);
|
|
489
|
+
return date.getTime();
|
|
490
|
+
};
|
|
491
|
+
var END_OF_YEAR = (timestamp) => {
|
|
492
|
+
const date = new Date(timestamp);
|
|
493
|
+
date.setMonth(11, 31);
|
|
494
|
+
date.setHours(23, 59, 59, 999);
|
|
495
|
+
return date.getTime();
|
|
496
|
+
};
|
|
497
|
+
var ADD_DAYS = (timestamp, days) => {
|
|
498
|
+
const date = new Date(timestamp);
|
|
499
|
+
date.setDate(date.getDate() + days);
|
|
500
|
+
return date.getTime();
|
|
501
|
+
};
|
|
502
|
+
var ADD_MONTHS = (timestamp, months) => {
|
|
503
|
+
const date = new Date(timestamp);
|
|
504
|
+
date.setMonth(date.getMonth() + months);
|
|
505
|
+
return date.getTime();
|
|
506
|
+
};
|
|
507
|
+
var ADD_YEARS = (timestamp, years) => {
|
|
508
|
+
const date = new Date(timestamp);
|
|
509
|
+
date.setFullYear(date.getFullYear() + years);
|
|
510
|
+
return date.getTime();
|
|
511
|
+
};
|
|
512
|
+
var IS_BEFORE = (ts1, ts2) => {
|
|
513
|
+
return ts1 < ts2 ? 1 : 0;
|
|
514
|
+
};
|
|
515
|
+
var IS_AFTER = (ts1, ts2) => {
|
|
516
|
+
return ts1 > ts2 ? 1 : 0;
|
|
517
|
+
};
|
|
518
|
+
var IS_SAME_DAY = (ts1, ts2) => {
|
|
519
|
+
const date1 = new Date(ts1);
|
|
520
|
+
const date2 = new Date(ts2);
|
|
521
|
+
return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate() ? 1 : 0;
|
|
522
|
+
};
|
|
523
|
+
var IS_WEEKEND = (timestamp) => {
|
|
524
|
+
const day = new Date(timestamp).getDay();
|
|
525
|
+
return day === 0 || day === 6 ? 1 : 0;
|
|
526
|
+
};
|
|
527
|
+
var IS_LEAP_YEAR = (timestamp) => {
|
|
528
|
+
const year = new Date(timestamp).getFullYear();
|
|
529
|
+
return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0 ? 1 : 0;
|
|
530
|
+
};
|
|
531
|
+
var START_OF_QUARTER = (timestamp) => {
|
|
532
|
+
const date = new Date(timestamp);
|
|
533
|
+
const quarter = Math.floor(date.getMonth() / 3);
|
|
534
|
+
date.setMonth(quarter * 3, 1);
|
|
535
|
+
date.setHours(0, 0, 0, 0);
|
|
536
|
+
return date.getTime();
|
|
537
|
+
};
|
|
538
|
+
var TO_UNIX_SECONDS = (timestamp) => Math.floor(timestamp / 1000);
|
|
539
|
+
var FROM_UNIX_SECONDS = (seconds) => seconds * 1000;
|
|
388
540
|
// src/defaults.ts
|
|
389
541
|
var defaultContext = {
|
|
390
542
|
functions: {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
timestamp: (year, month, day, hour = 0, minute = 0, second = 0) => new Date(year, month - 1, day, hour, minute, second).getTime(),
|
|
406
|
-
milliseconds: (ms) => ms,
|
|
407
|
-
seconds: (s) => s * 1000,
|
|
408
|
-
minutes: (m) => m * 60 * 1000,
|
|
409
|
-
hours: (h) => h * 60 * 60 * 1000,
|
|
410
|
-
days: (d) => d * 24 * 60 * 60 * 1000,
|
|
411
|
-
weeks: (w) => w * 7 * 24 * 60 * 60 * 1000,
|
|
412
|
-
year: (timestamp) => new Date(timestamp).getFullYear(),
|
|
413
|
-
month: (timestamp) => new Date(timestamp).getMonth() + 1,
|
|
414
|
-
day: (timestamp) => new Date(timestamp).getDate(),
|
|
415
|
-
hour: (timestamp) => new Date(timestamp).getHours(),
|
|
416
|
-
minute: (timestamp) => new Date(timestamp).getMinutes(),
|
|
417
|
-
second: (timestamp) => new Date(timestamp).getSeconds(),
|
|
418
|
-
weekday: (timestamp) => new Date(timestamp).getDay()
|
|
543
|
+
ABS: Math.abs,
|
|
544
|
+
CEIL: Math.ceil,
|
|
545
|
+
FLOOR: Math.floor,
|
|
546
|
+
ROUND: Math.round,
|
|
547
|
+
SQRT: Math.sqrt,
|
|
548
|
+
MIN: Math.min,
|
|
549
|
+
MAX: Math.max,
|
|
550
|
+
SIN: Math.sin,
|
|
551
|
+
COS: Math.cos,
|
|
552
|
+
TAN: Math.tan,
|
|
553
|
+
LOG: Math.log,
|
|
554
|
+
LOG10: Math.log10,
|
|
555
|
+
EXP: Math.exp,
|
|
556
|
+
...exports_date_utils
|
|
419
557
|
}
|
|
420
558
|
};
|
|
421
559
|
// src/lexer.ts
|
|
422
560
|
class Lexer {
|
|
423
561
|
source;
|
|
424
562
|
position = 0;
|
|
563
|
+
length;
|
|
425
564
|
constructor(source) {
|
|
426
565
|
this.source = source;
|
|
566
|
+
this.length = source.length;
|
|
427
567
|
}
|
|
428
568
|
tokenize() {
|
|
429
569
|
const tokens = [];
|
|
@@ -438,16 +578,22 @@ class Lexer {
|
|
|
438
578
|
}
|
|
439
579
|
nextToken() {
|
|
440
580
|
this.skipWhitespaceAndComments();
|
|
441
|
-
if (this.position >= this.
|
|
581
|
+
if (this.position >= this.length) {
|
|
582
|
+
return { type: "EOF" /* EOF */, value: "", position: this.position };
|
|
583
|
+
}
|
|
584
|
+
const char = this.source[this.position];
|
|
585
|
+
if (char === undefined) {
|
|
442
586
|
return { type: "EOF" /* EOF */, value: "", position: this.position };
|
|
443
587
|
}
|
|
444
|
-
const char = this.getCharAt(this.position);
|
|
445
588
|
const start = this.position;
|
|
446
589
|
if (this.isDigit(char)) {
|
|
447
590
|
return this.readNumber();
|
|
448
591
|
}
|
|
449
|
-
if (char === "."
|
|
450
|
-
|
|
592
|
+
if (char === ".") {
|
|
593
|
+
const nextChar = this.source[this.position + 1];
|
|
594
|
+
if (nextChar !== undefined && this.isDigit(nextChar)) {
|
|
595
|
+
return this.readNumber();
|
|
596
|
+
}
|
|
451
597
|
}
|
|
452
598
|
if (this.isLetter(char) || char === "_") {
|
|
453
599
|
return this.readIdentifier();
|
|
@@ -478,41 +624,33 @@ class Lexer {
|
|
|
478
624
|
this.position++;
|
|
479
625
|
return { type: "RPAREN" /* RPAREN */, value: ")", position: start };
|
|
480
626
|
case "=":
|
|
481
|
-
if (this.
|
|
627
|
+
if (this.source[this.position + 1] === "=") {
|
|
482
628
|
this.position += 2;
|
|
483
629
|
return { type: "DOUBLE_EQUALS" /* DOUBLE_EQUALS */, value: "==", position: start };
|
|
484
630
|
}
|
|
485
631
|
this.position++;
|
|
486
632
|
return { type: "EQUALS" /* EQUALS */, value: "=", position: start };
|
|
487
633
|
case "!":
|
|
488
|
-
if (this.
|
|
634
|
+
if (this.source[this.position + 1] === "=") {
|
|
489
635
|
this.position += 2;
|
|
490
636
|
return { type: "NOT_EQUALS" /* NOT_EQUALS */, value: "!=", position: start };
|
|
491
637
|
}
|
|
492
638
|
throw new Error(`Unexpected character '${char}' at position ${start}`);
|
|
493
639
|
case "<":
|
|
494
|
-
if (this.
|
|
640
|
+
if (this.source[this.position + 1] === "=") {
|
|
495
641
|
this.position += 2;
|
|
496
642
|
return { type: "LESS_EQUAL" /* LESS_EQUAL */, value: "<=", position: start };
|
|
497
643
|
}
|
|
498
644
|
this.position++;
|
|
499
645
|
return { type: "LESS_THAN" /* LESS_THAN */, value: "<", position: start };
|
|
500
646
|
case ">":
|
|
501
|
-
if (this.
|
|
647
|
+
if (this.source[this.position + 1] === "=") {
|
|
502
648
|
this.position += 2;
|
|
503
649
|
return { type: "GREATER_EQUAL" /* GREATER_EQUAL */, value: ">=", position: start };
|
|
504
650
|
}
|
|
505
651
|
this.position++;
|
|
506
652
|
return { type: "GREATER_THAN" /* GREATER_THAN */, value: ">", position: start };
|
|
507
653
|
case "?":
|
|
508
|
-
if (this.peek() === "?" && this.peekAhead(2) === "=") {
|
|
509
|
-
this.position += 3;
|
|
510
|
-
return {
|
|
511
|
-
type: "NULLISH_ASSIGN" /* NULLISH_ASSIGN */,
|
|
512
|
-
value: "??=",
|
|
513
|
-
position: start
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
654
|
this.position++;
|
|
517
655
|
return { type: "QUESTION" /* QUESTION */, value: "?", position: start };
|
|
518
656
|
case ":":
|
|
@@ -525,13 +663,13 @@ class Lexer {
|
|
|
525
663
|
this.position++;
|
|
526
664
|
return this.nextToken();
|
|
527
665
|
case "&":
|
|
528
|
-
if (this.
|
|
666
|
+
if (this.source[this.position + 1] === "&") {
|
|
529
667
|
this.position += 2;
|
|
530
668
|
return { type: "LOGICAL_AND" /* LOGICAL_AND */, value: "&&", position: start };
|
|
531
669
|
}
|
|
532
670
|
throw new Error(`Unexpected character '${char}' at position ${start}`);
|
|
533
671
|
case "|":
|
|
534
|
-
if (this.
|
|
672
|
+
if (this.source[this.position + 1] === "|") {
|
|
535
673
|
this.position += 2;
|
|
536
674
|
return { type: "LOGICAL_OR" /* LOGICAL_OR */, value: "||", position: start };
|
|
537
675
|
}
|
|
@@ -541,14 +679,15 @@ class Lexer {
|
|
|
541
679
|
}
|
|
542
680
|
}
|
|
543
681
|
skipWhitespaceAndComments() {
|
|
544
|
-
while (this.position < this.
|
|
545
|
-
const char = this.
|
|
682
|
+
while (this.position < this.length) {
|
|
683
|
+
const char = this.source[this.position];
|
|
546
684
|
if (this.isWhitespace(char)) {
|
|
547
685
|
this.position++;
|
|
548
686
|
continue;
|
|
549
687
|
}
|
|
550
|
-
if (char === "/" && this.
|
|
551
|
-
|
|
688
|
+
if (char === "/" && this.source[this.position + 1] === "/") {
|
|
689
|
+
this.position += 2;
|
|
690
|
+
while (this.position < this.length && this.source[this.position] !== `
|
|
552
691
|
`) {
|
|
553
692
|
this.position++;
|
|
554
693
|
}
|
|
@@ -561,12 +700,12 @@ class Lexer {
|
|
|
561
700
|
const start = this.position;
|
|
562
701
|
let hasDecimal = false;
|
|
563
702
|
let hasExponent = false;
|
|
564
|
-
if (this.
|
|
703
|
+
if (this.source[this.position] === ".") {
|
|
565
704
|
hasDecimal = true;
|
|
566
705
|
this.position++;
|
|
567
706
|
}
|
|
568
|
-
while (this.position < this.
|
|
569
|
-
const char = this.
|
|
707
|
+
while (this.position < this.length) {
|
|
708
|
+
const char = this.source[this.position];
|
|
570
709
|
if (this.isDigit(char)) {
|
|
571
710
|
this.position++;
|
|
572
711
|
} else if (char === "." && !hasDecimal && !hasExponent) {
|
|
@@ -575,14 +714,15 @@ class Lexer {
|
|
|
575
714
|
} else if ((char === "e" || char === "E") && !hasExponent) {
|
|
576
715
|
hasExponent = true;
|
|
577
716
|
this.position++;
|
|
578
|
-
const nextChar = this.
|
|
717
|
+
const nextChar = this.source[this.position];
|
|
579
718
|
if (nextChar === "+" || nextChar === "-") {
|
|
580
719
|
this.position++;
|
|
581
720
|
}
|
|
582
|
-
|
|
721
|
+
const digitChar = this.source[this.position];
|
|
722
|
+
if (digitChar === undefined || !this.isDigit(digitChar)) {
|
|
583
723
|
throw new Error(`Invalid number: expected digit after exponent at position ${this.position}`);
|
|
584
724
|
}
|
|
585
|
-
while (this.position < this.
|
|
725
|
+
while (this.position < this.length && this.isDigit(this.source[this.position])) {
|
|
586
726
|
this.position++;
|
|
587
727
|
}
|
|
588
728
|
break;
|
|
@@ -595,8 +735,8 @@ class Lexer {
|
|
|
595
735
|
}
|
|
596
736
|
readIdentifier() {
|
|
597
737
|
const start = this.position;
|
|
598
|
-
while (this.position < this.
|
|
599
|
-
const char = this.
|
|
738
|
+
while (this.position < this.length) {
|
|
739
|
+
const char = this.source[this.position];
|
|
600
740
|
if (this.isLetter(char) || this.isDigit(char) || char === "_") {
|
|
601
741
|
this.position++;
|
|
602
742
|
} else {
|
|
@@ -606,24 +746,15 @@ class Lexer {
|
|
|
606
746
|
const name = this.source.slice(start, this.position);
|
|
607
747
|
return { type: "IDENTIFIER" /* IDENTIFIER */, value: name, position: start };
|
|
608
748
|
}
|
|
609
|
-
getCharAt(pos) {
|
|
610
|
-
return pos < this.source.length ? this.source[pos] || "" : "";
|
|
611
|
-
}
|
|
612
|
-
peek() {
|
|
613
|
-
return this.getCharAt(this.position + 1);
|
|
614
|
-
}
|
|
615
|
-
peekAhead(n) {
|
|
616
|
-
return this.getCharAt(this.position + n);
|
|
617
|
-
}
|
|
618
749
|
isDigit(char) {
|
|
619
|
-
return char >= "0" && char <= "9";
|
|
750
|
+
return char !== undefined && char >= "0" && char <= "9";
|
|
620
751
|
}
|
|
621
752
|
isLetter(char) {
|
|
622
|
-
return char >= "a" && char <= "z" || char >= "A" && char <= "Z";
|
|
753
|
+
return char !== undefined && (char >= "a" && char <= "z" || char >= "A" && char <= "Z");
|
|
623
754
|
}
|
|
624
755
|
isWhitespace(char) {
|
|
625
|
-
return char === " " || char === "\t" || char === `
|
|
626
|
-
` || char === "\r";
|
|
756
|
+
return char !== undefined && (char === " " || char === "\t" || char === `
|
|
757
|
+
` || char === "\r");
|
|
627
758
|
}
|
|
628
759
|
}
|
|
629
760
|
|
|
@@ -660,16 +791,13 @@ class Parser {
|
|
|
660
791
|
throw new Error("Empty program");
|
|
661
792
|
}
|
|
662
793
|
if (statements.length === 1) {
|
|
663
|
-
const
|
|
664
|
-
if (
|
|
665
|
-
throw new Error("Unexpected
|
|
794
|
+
const singleStatement = statements[0];
|
|
795
|
+
if (singleStatement === undefined) {
|
|
796
|
+
throw new Error("Unexpected empty statements array");
|
|
666
797
|
}
|
|
667
|
-
return
|
|
798
|
+
return singleStatement;
|
|
668
799
|
}
|
|
669
|
-
return
|
|
670
|
-
type: "Program",
|
|
671
|
-
statements
|
|
672
|
-
};
|
|
800
|
+
return program(statements);
|
|
673
801
|
}
|
|
674
802
|
parseExpression(minPrecedence) {
|
|
675
803
|
let left = this.parsePrefix();
|
|
@@ -686,23 +814,7 @@ class Parser {
|
|
|
686
814
|
const identName = left.name;
|
|
687
815
|
this.advance();
|
|
688
816
|
const value = this.parseExpression(precedence);
|
|
689
|
-
left =
|
|
690
|
-
type: "Assignment",
|
|
691
|
-
name: identName,
|
|
692
|
-
value
|
|
693
|
-
};
|
|
694
|
-
} else if (token.type === "NULLISH_ASSIGN" /* NULLISH_ASSIGN */) {
|
|
695
|
-
if (left.type !== "Identifier") {
|
|
696
|
-
throw new Error("Invalid assignment target");
|
|
697
|
-
}
|
|
698
|
-
const identName = left.name;
|
|
699
|
-
this.advance();
|
|
700
|
-
const value = this.parseExpression(precedence);
|
|
701
|
-
left = {
|
|
702
|
-
type: "NullishAssignment",
|
|
703
|
-
name: identName,
|
|
704
|
-
value
|
|
705
|
-
};
|
|
817
|
+
left = assign(identName, value);
|
|
706
818
|
} else if (token.type === "QUESTION" /* QUESTION */) {
|
|
707
819
|
this.advance();
|
|
708
820
|
const consequent = this.parseExpression(0);
|
|
@@ -711,22 +823,12 @@ class Parser {
|
|
|
711
823
|
}
|
|
712
824
|
this.advance();
|
|
713
825
|
const alternate = this.parseExpression(precedence);
|
|
714
|
-
left =
|
|
715
|
-
type: "ConditionalExpression",
|
|
716
|
-
condition: left,
|
|
717
|
-
consequent,
|
|
718
|
-
alternate
|
|
719
|
-
};
|
|
826
|
+
left = conditional(left, consequent, alternate);
|
|
720
827
|
} else if (this.isBinaryOperator(token.type)) {
|
|
721
828
|
const operator = token.value;
|
|
722
829
|
this.advance();
|
|
723
830
|
const right = this.parseExpression(precedence + 1);
|
|
724
|
-
left =
|
|
725
|
-
type: "BinaryOp",
|
|
726
|
-
left,
|
|
727
|
-
operator,
|
|
728
|
-
right
|
|
729
|
-
};
|
|
831
|
+
left = binaryOp(left, operator, right);
|
|
730
832
|
} else {
|
|
731
833
|
break;
|
|
732
834
|
}
|
|
@@ -738,11 +840,7 @@ class Parser {
|
|
|
738
840
|
if (token.type === "MINUS" /* MINUS */) {
|
|
739
841
|
this.advance();
|
|
740
842
|
const argument = this.parseExpression(this.getUnaryPrecedence());
|
|
741
|
-
return
|
|
742
|
-
type: "UnaryOp",
|
|
743
|
-
operator: "-",
|
|
744
|
-
argument
|
|
745
|
-
};
|
|
843
|
+
return unaryOp(argument);
|
|
746
844
|
}
|
|
747
845
|
if (token.type === "LPAREN" /* LPAREN */) {
|
|
748
846
|
this.advance();
|
|
@@ -755,10 +853,7 @@ class Parser {
|
|
|
755
853
|
}
|
|
756
854
|
if (token.type === "NUMBER" /* NUMBER */) {
|
|
757
855
|
this.advance();
|
|
758
|
-
return
|
|
759
|
-
type: "NumberLiteral",
|
|
760
|
-
value: token.value
|
|
761
|
-
};
|
|
856
|
+
return number(token.value);
|
|
762
857
|
}
|
|
763
858
|
if (token.type === "IDENTIFIER" /* IDENTIFIER */) {
|
|
764
859
|
const name = token.value;
|
|
@@ -770,16 +865,9 @@ class Parser {
|
|
|
770
865
|
throw new Error("Expected closing parenthesis");
|
|
771
866
|
}
|
|
772
867
|
this.advance();
|
|
773
|
-
return
|
|
774
|
-
type: "FunctionCall",
|
|
775
|
-
name,
|
|
776
|
-
arguments: args
|
|
777
|
-
};
|
|
868
|
+
return functionCall(name, args);
|
|
778
869
|
}
|
|
779
|
-
return
|
|
780
|
-
type: "Identifier",
|
|
781
|
-
name
|
|
782
|
-
};
|
|
870
|
+
return identifier(name);
|
|
783
871
|
}
|
|
784
872
|
throw new Error(`Unexpected token: ${token.value}`);
|
|
785
873
|
}
|
|
@@ -826,9 +914,11 @@ function parseSource(source) {
|
|
|
826
914
|
class Executor {
|
|
827
915
|
context;
|
|
828
916
|
variables;
|
|
917
|
+
externalVariables;
|
|
829
918
|
constructor(context = {}) {
|
|
830
919
|
this.context = context;
|
|
831
920
|
this.variables = new Map(Object.entries(context.variables || {}));
|
|
921
|
+
this.externalVariables = new Set(Object.keys(context.variables || {}));
|
|
832
922
|
}
|
|
833
923
|
execute(node) {
|
|
834
924
|
if (isProgram(node))
|
|
@@ -845,8 +935,6 @@ class Executor {
|
|
|
845
935
|
return this.executeFunctionCall(node);
|
|
846
936
|
if (isAssignment(node))
|
|
847
937
|
return this.executeAssignment(node);
|
|
848
|
-
if (isNullishAssignment(node))
|
|
849
|
-
return this.executeNullishAssignment(node);
|
|
850
938
|
if (isConditionalExpression(node))
|
|
851
939
|
return this.executeConditionalExpression(node);
|
|
852
940
|
throw new Error(`Unknown node type`);
|
|
@@ -892,13 +980,11 @@ class Executor {
|
|
|
892
980
|
return fn(...args);
|
|
893
981
|
}
|
|
894
982
|
executeAssignment(node) {
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
if (this.variables.has(node.name)) {
|
|
901
|
-
return this.variables.get(node.name);
|
|
983
|
+
if (this.externalVariables.has(node.name)) {
|
|
984
|
+
const externalValue = this.variables.get(node.name);
|
|
985
|
+
if (externalValue !== undefined) {
|
|
986
|
+
return externalValue;
|
|
987
|
+
}
|
|
902
988
|
}
|
|
903
989
|
const value = this.execute(node.value);
|
|
904
990
|
this.variables.set(node.name, value);
|
|
@@ -916,420 +1002,47 @@ function execute(source, context) {
|
|
|
916
1002
|
}
|
|
917
1003
|
// src/optimizer.ts
|
|
918
1004
|
function optimize(node) {
|
|
919
|
-
if (!isProgram(node)) {
|
|
920
|
-
return basicOptimize(node);
|
|
921
|
-
}
|
|
922
|
-
const analysis = analyzeProgram(node);
|
|
923
|
-
const { propagated, allConstants } = propagateConstantsOptimal(node, analysis);
|
|
924
|
-
const optimized = eliminateDeadCodeOptimal(propagated, analysis, allConstants);
|
|
925
|
-
return optimized;
|
|
926
|
-
}
|
|
927
|
-
function analyzeProgram(node) {
|
|
928
|
-
if (!isProgram(node)) {
|
|
929
|
-
return {
|
|
930
|
-
constants: new Map,
|
|
931
|
-
tainted: new Set,
|
|
932
|
-
dependencies: new Map,
|
|
933
|
-
liveVariables: new Set,
|
|
934
|
-
assignmentIndices: new Map,
|
|
935
|
-
evaluationOrder: []
|
|
936
|
-
};
|
|
937
|
-
}
|
|
938
|
-
const constants = new Map;
|
|
939
|
-
const tainted = new Set;
|
|
940
|
-
const dependencies = new Map;
|
|
941
|
-
const assignmentIndices = new Map;
|
|
942
|
-
const assignmentCounts = new Map;
|
|
943
|
-
for (let i = 0;i < node.statements.length; i++) {
|
|
944
|
-
const stmt = node.statements[i];
|
|
945
|
-
if (!stmt)
|
|
946
|
-
continue;
|
|
947
|
-
if (isAssignment(stmt) || isNullishAssignment(stmt)) {
|
|
948
|
-
const varName = stmt.name;
|
|
949
|
-
const count = assignmentCounts.get(varName) || 0;
|
|
950
|
-
assignmentCounts.set(varName, count + 1);
|
|
951
|
-
assignmentIndices.set(varName, i);
|
|
952
|
-
const deps = new Set;
|
|
953
|
-
const hasFunctionCall = collectDependencies(stmt.value, deps);
|
|
954
|
-
dependencies.set(varName, deps);
|
|
955
|
-
if (isNullishAssignment(stmt)) {
|
|
956
|
-
tainted.add(varName);
|
|
957
|
-
} else {
|
|
958
|
-
if (count === 0 && isNumberLiteral(stmt.value)) {
|
|
959
|
-
constants.set(varName, stmt.value.value);
|
|
960
|
-
}
|
|
961
|
-
if (hasFunctionCall) {
|
|
962
|
-
tainted.add(varName);
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
for (const [varName, count] of assignmentCounts) {
|
|
968
|
-
if (count > 1) {
|
|
969
|
-
constants.delete(varName);
|
|
970
|
-
tainted.add(varName);
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
let taintChanged = true;
|
|
974
|
-
while (taintChanged) {
|
|
975
|
-
taintChanged = false;
|
|
976
|
-
for (const [varName, deps] of dependencies) {
|
|
977
|
-
if (tainted.has(varName))
|
|
978
|
-
continue;
|
|
979
|
-
for (const dep of deps) {
|
|
980
|
-
if (tainted.has(dep)) {
|
|
981
|
-
tainted.add(varName);
|
|
982
|
-
constants.delete(varName);
|
|
983
|
-
taintChanged = true;
|
|
984
|
-
break;
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
const liveVariables = new Set;
|
|
990
|
-
const lastStmt = node.statements[node.statements.length - 1];
|
|
991
|
-
if (lastStmt) {
|
|
992
|
-
if (isAssignment(lastStmt)) {
|
|
993
|
-
const deps = new Set;
|
|
994
|
-
collectDependencies(lastStmt.value, deps);
|
|
995
|
-
for (const dep of deps) {
|
|
996
|
-
liveVariables.add(dep);
|
|
997
|
-
}
|
|
998
|
-
} else {
|
|
999
|
-
const deps = new Set;
|
|
1000
|
-
collectDependencies(lastStmt, deps);
|
|
1001
|
-
for (const dep of deps) {
|
|
1002
|
-
liveVariables.add(dep);
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
let liveChanged = true;
|
|
1007
|
-
while (liveChanged) {
|
|
1008
|
-
liveChanged = false;
|
|
1009
|
-
for (const [varName, deps] of dependencies) {
|
|
1010
|
-
if (liveVariables.has(varName)) {
|
|
1011
|
-
for (const dep of deps) {
|
|
1012
|
-
if (!liveVariables.has(dep)) {
|
|
1013
|
-
liveVariables.add(dep);
|
|
1014
|
-
liveChanged = true;
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
const evaluationOrder = topologicalSort(dependencies, liveVariables);
|
|
1021
|
-
return {
|
|
1022
|
-
constants,
|
|
1023
|
-
tainted,
|
|
1024
|
-
dependencies,
|
|
1025
|
-
liveVariables,
|
|
1026
|
-
assignmentIndices,
|
|
1027
|
-
evaluationOrder
|
|
1028
|
-
};
|
|
1029
|
-
}
|
|
1030
|
-
function collectDependencies(node, deps) {
|
|
1031
|
-
if (isIdentifier(node)) {
|
|
1032
|
-
deps.add(node.name);
|
|
1033
|
-
return false;
|
|
1034
|
-
}
|
|
1035
1005
|
if (isNumberLiteral(node)) {
|
|
1036
|
-
return false;
|
|
1037
|
-
}
|
|
1038
|
-
if (isAssignment(node) || isNullishAssignment(node)) {
|
|
1039
|
-
return collectDependencies(node.value, deps);
|
|
1040
|
-
}
|
|
1041
|
-
if (isBinaryOp(node)) {
|
|
1042
|
-
const leftHasCall = collectDependencies(node.left, deps);
|
|
1043
|
-
const rightHasCall = collectDependencies(node.right, deps);
|
|
1044
|
-
return leftHasCall || rightHasCall;
|
|
1045
|
-
}
|
|
1046
|
-
if (isUnaryOp(node)) {
|
|
1047
|
-
return collectDependencies(node.argument, deps);
|
|
1048
|
-
}
|
|
1049
|
-
if (isFunctionCall(node)) {
|
|
1050
|
-
for (const arg of node.arguments) {
|
|
1051
|
-
collectDependencies(arg, deps);
|
|
1052
|
-
}
|
|
1053
|
-
return true;
|
|
1054
|
-
}
|
|
1055
|
-
if (isConditionalExpression(node)) {
|
|
1056
|
-
const condHasCall = collectDependencies(node.condition, deps);
|
|
1057
|
-
const consHasCall = collectDependencies(node.consequent, deps);
|
|
1058
|
-
const altHasCall = collectDependencies(node.alternate, deps);
|
|
1059
|
-
return condHasCall || consHasCall || altHasCall;
|
|
1060
|
-
}
|
|
1061
|
-
if (isProgram(node)) {
|
|
1062
|
-
let hasCall = false;
|
|
1063
|
-
for (const stmt of node.statements) {
|
|
1064
|
-
hasCall = collectDependencies(stmt, deps) || hasCall;
|
|
1065
|
-
}
|
|
1066
|
-
return hasCall;
|
|
1067
|
-
}
|
|
1068
|
-
return false;
|
|
1069
|
-
}
|
|
1070
|
-
function topologicalSort(dependencies, liveVariables) {
|
|
1071
|
-
const result = [];
|
|
1072
|
-
const visited = new Set;
|
|
1073
|
-
const visiting = new Set;
|
|
1074
|
-
function visit(varName) {
|
|
1075
|
-
if (visited.has(varName))
|
|
1076
|
-
return;
|
|
1077
|
-
if (visiting.has(varName)) {
|
|
1078
|
-
return;
|
|
1079
|
-
}
|
|
1080
|
-
visiting.add(varName);
|
|
1081
|
-
const deps = dependencies.get(varName);
|
|
1082
|
-
if (deps) {
|
|
1083
|
-
for (const dep of deps) {
|
|
1084
|
-
if (liveVariables.has(dep)) {
|
|
1085
|
-
visit(dep);
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
visiting.delete(varName);
|
|
1090
|
-
visited.add(varName);
|
|
1091
|
-
result.push(varName);
|
|
1092
|
-
}
|
|
1093
|
-
for (const varName of liveVariables) {
|
|
1094
|
-
visit(varName);
|
|
1095
|
-
}
|
|
1096
|
-
return result;
|
|
1097
|
-
}
|
|
1098
|
-
function propagateConstantsOptimal(node, analysis) {
|
|
1099
|
-
if (!isProgram(node)) {
|
|
1100
|
-
return { propagated: node, allConstants: new Map };
|
|
1101
|
-
}
|
|
1102
|
-
const allConstants = new Map(analysis.constants);
|
|
1103
|
-
for (const varName of analysis.evaluationOrder) {
|
|
1104
|
-
if (allConstants.has(varName))
|
|
1105
|
-
continue;
|
|
1106
|
-
if (analysis.tainted.has(varName))
|
|
1107
|
-
continue;
|
|
1108
|
-
const deps = analysis.dependencies.get(varName);
|
|
1109
|
-
if (!deps)
|
|
1110
|
-
continue;
|
|
1111
|
-
let allDepsConstant = true;
|
|
1112
|
-
for (const dep of deps) {
|
|
1113
|
-
if (!allConstants.has(dep)) {
|
|
1114
|
-
allDepsConstant = false;
|
|
1115
|
-
break;
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
if (allDepsConstant) {
|
|
1119
|
-
const assignmentIdx = analysis.assignmentIndices.get(varName);
|
|
1120
|
-
if (assignmentIdx !== undefined) {
|
|
1121
|
-
const stmt = node.statements[assignmentIdx];
|
|
1122
|
-
if (stmt && isAssignment(stmt)) {
|
|
1123
|
-
const evaluated = evaluateWithConstants(stmt.value, allConstants);
|
|
1124
|
-
if (isNumberLiteral(evaluated)) {
|
|
1125
|
-
allConstants.set(varName, evaluated.value);
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
const statements = node.statements.map((stmt) => replaceWithConstants(stmt, allConstants));
|
|
1132
|
-
return {
|
|
1133
|
-
propagated: {
|
|
1134
|
-
type: "Program",
|
|
1135
|
-
statements
|
|
1136
|
-
},
|
|
1137
|
-
allConstants
|
|
1138
|
-
};
|
|
1139
|
-
}
|
|
1140
|
-
function evaluateWithConstants(node, constants) {
|
|
1141
|
-
if (isIdentifier(node)) {
|
|
1142
|
-
const value = constants.get(node.name);
|
|
1143
|
-
if (value !== undefined) {
|
|
1144
|
-
return number(value);
|
|
1145
|
-
}
|
|
1146
1006
|
return node;
|
|
1147
1007
|
}
|
|
1148
|
-
if (
|
|
1008
|
+
if (node.type === "Identifier") {
|
|
1149
1009
|
return node;
|
|
1150
1010
|
}
|
|
1151
1011
|
if (isBinaryOp(node)) {
|
|
1152
|
-
const left =
|
|
1153
|
-
const right =
|
|
1012
|
+
const left = optimize(node.left);
|
|
1013
|
+
const right = optimize(node.right);
|
|
1154
1014
|
if (isNumberLiteral(left) && isNumberLiteral(right)) {
|
|
1155
1015
|
const result = evaluateBinaryOperation(node.operator, left.value, right.value);
|
|
1156
1016
|
return number(result);
|
|
1157
1017
|
}
|
|
1158
|
-
return
|
|
1159
|
-
...node,
|
|
1160
|
-
left,
|
|
1161
|
-
right
|
|
1162
|
-
};
|
|
1018
|
+
return binaryOp(left, node.operator, right);
|
|
1163
1019
|
}
|
|
1164
1020
|
if (isUnaryOp(node)) {
|
|
1165
|
-
const argument =
|
|
1021
|
+
const argument = optimize(node.argument);
|
|
1166
1022
|
if (isNumberLiteral(argument)) {
|
|
1167
1023
|
return number(-argument.value);
|
|
1168
1024
|
}
|
|
1169
|
-
return
|
|
1170
|
-
...node,
|
|
1171
|
-
argument
|
|
1172
|
-
};
|
|
1025
|
+
return unaryOp(argument);
|
|
1173
1026
|
}
|
|
1174
1027
|
if (isFunctionCall(node)) {
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
arguments: node.arguments.map((arg) => evaluateWithConstants(arg, constants))
|
|
1178
|
-
};
|
|
1179
|
-
}
|
|
1180
|
-
if (isConditionalExpression(node)) {
|
|
1181
|
-
const condition = evaluateWithConstants(node.condition, constants);
|
|
1182
|
-
const consequent = evaluateWithConstants(node.consequent, constants);
|
|
1183
|
-
const alternate = evaluateWithConstants(node.alternate, constants);
|
|
1184
|
-
if (isNumberLiteral(condition)) {
|
|
1185
|
-
return condition.value !== 0 ? consequent : alternate;
|
|
1186
|
-
}
|
|
1187
|
-
return {
|
|
1188
|
-
...node,
|
|
1189
|
-
condition,
|
|
1190
|
-
consequent,
|
|
1191
|
-
alternate
|
|
1192
|
-
};
|
|
1028
|
+
const optimizedArgs = node.arguments.map((arg) => optimize(arg));
|
|
1029
|
+
return functionCall(node.name, optimizedArgs);
|
|
1193
1030
|
}
|
|
1194
1031
|
if (isAssignment(node)) {
|
|
1195
|
-
return
|
|
1196
|
-
...node,
|
|
1197
|
-
value: evaluateWithConstants(node.value, constants)
|
|
1198
|
-
};
|
|
1199
|
-
}
|
|
1200
|
-
if (isNullishAssignment(node)) {
|
|
1201
|
-
return {
|
|
1202
|
-
...node,
|
|
1203
|
-
value: evaluateWithConstants(node.value, constants)
|
|
1204
|
-
};
|
|
1205
|
-
}
|
|
1206
|
-
return node;
|
|
1207
|
-
}
|
|
1208
|
-
function replaceWithConstants(node, constants) {
|
|
1209
|
-
return evaluateWithConstants(node, constants);
|
|
1210
|
-
}
|
|
1211
|
-
function eliminateDeadCodeOptimal(node, analysis, allConstants) {
|
|
1212
|
-
if (!isProgram(node))
|
|
1213
|
-
return node;
|
|
1214
|
-
const lastStmt = node.statements[node.statements.length - 1];
|
|
1215
|
-
if (lastStmt) {
|
|
1216
|
-
const evaluated = evaluateWithConstants(lastStmt, allConstants);
|
|
1217
|
-
if (isNumberLiteral(evaluated)) {
|
|
1218
|
-
return evaluated;
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
const filteredStatements = [];
|
|
1222
|
-
const variablesInUse = new Set;
|
|
1223
|
-
for (const stmt of node.statements) {
|
|
1224
|
-
collectDependencies(stmt, variablesInUse);
|
|
1225
|
-
}
|
|
1226
|
-
for (let i = 0;i < node.statements.length; i++) {
|
|
1227
|
-
const stmt = node.statements[i];
|
|
1228
|
-
if (!stmt)
|
|
1229
|
-
continue;
|
|
1230
|
-
if (i === node.statements.length - 1) {
|
|
1231
|
-
filteredStatements.push(stmt);
|
|
1232
|
-
continue;
|
|
1233
|
-
}
|
|
1234
|
-
if (isAssignment(stmt) || isNullishAssignment(stmt)) {
|
|
1235
|
-
if (variablesInUse.has(stmt.name)) {
|
|
1236
|
-
filteredStatements.push(stmt);
|
|
1237
|
-
}
|
|
1238
|
-
} else {
|
|
1239
|
-
filteredStatements.push(stmt);
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
if (filteredStatements.length === 1) {
|
|
1243
|
-
const singleStmt = filteredStatements[0];
|
|
1244
|
-
if (!singleStmt) {
|
|
1245
|
-
return node;
|
|
1246
|
-
}
|
|
1247
|
-
if (isNumberLiteral(singleStmt)) {
|
|
1248
|
-
return singleStmt;
|
|
1249
|
-
}
|
|
1250
|
-
if (isAssignment(singleStmt) && isNumberLiteral(singleStmt.value)) {
|
|
1251
|
-
if (!analysis.liveVariables.has(singleStmt.name)) {
|
|
1252
|
-
return singleStmt.value;
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
const evaluated = evaluateWithConstants(singleStmt, allConstants);
|
|
1256
|
-
if (isNumberLiteral(evaluated)) {
|
|
1257
|
-
return evaluated;
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
if (filteredStatements.length === 0) {
|
|
1261
|
-
const lastStmt2 = node.statements[node.statements.length - 1];
|
|
1262
|
-
if (lastStmt2 && isAssignment(lastStmt2) && isNumberLiteral(lastStmt2.value)) {
|
|
1263
|
-
return lastStmt2.value;
|
|
1264
|
-
}
|
|
1265
|
-
return node;
|
|
1266
|
-
}
|
|
1267
|
-
return {
|
|
1268
|
-
type: "Program",
|
|
1269
|
-
statements: filteredStatements
|
|
1270
|
-
};
|
|
1271
|
-
}
|
|
1272
|
-
function basicOptimize(node) {
|
|
1273
|
-
if (isAssignment(node)) {
|
|
1274
|
-
return {
|
|
1275
|
-
...node,
|
|
1276
|
-
value: basicOptimize(node.value)
|
|
1277
|
-
};
|
|
1278
|
-
}
|
|
1279
|
-
if (isNullishAssignment(node)) {
|
|
1280
|
-
return {
|
|
1281
|
-
...node,
|
|
1282
|
-
value: basicOptimize(node.value)
|
|
1283
|
-
};
|
|
1284
|
-
}
|
|
1285
|
-
if (isBinaryOp(node)) {
|
|
1286
|
-
const left = basicOptimize(node.left);
|
|
1287
|
-
const right = basicOptimize(node.right);
|
|
1288
|
-
if (isNumberLiteral(left) && isNumberLiteral(right)) {
|
|
1289
|
-
const result = evaluateBinaryOperation(node.operator, left.value, right.value);
|
|
1290
|
-
return number(result);
|
|
1291
|
-
}
|
|
1292
|
-
return {
|
|
1293
|
-
...node,
|
|
1294
|
-
left,
|
|
1295
|
-
right
|
|
1296
|
-
};
|
|
1297
|
-
}
|
|
1298
|
-
if (isUnaryOp(node)) {
|
|
1299
|
-
const argument = basicOptimize(node.argument);
|
|
1300
|
-
if (isNumberLiteral(argument)) {
|
|
1301
|
-
return number(-argument.value);
|
|
1302
|
-
}
|
|
1303
|
-
return {
|
|
1304
|
-
...node,
|
|
1305
|
-
argument
|
|
1306
|
-
};
|
|
1307
|
-
}
|
|
1308
|
-
if (isFunctionCall(node)) {
|
|
1309
|
-
return {
|
|
1310
|
-
...node,
|
|
1311
|
-
arguments: node.arguments.map((arg) => basicOptimize(arg))
|
|
1312
|
-
};
|
|
1032
|
+
return assign(node.name, optimize(node.value));
|
|
1313
1033
|
}
|
|
1314
1034
|
if (isConditionalExpression(node)) {
|
|
1315
|
-
const condition =
|
|
1316
|
-
const consequent = basicOptimize(node.consequent);
|
|
1317
|
-
const alternate = basicOptimize(node.alternate);
|
|
1035
|
+
const condition = optimize(node.condition);
|
|
1318
1036
|
if (isNumberLiteral(condition)) {
|
|
1319
|
-
return condition.value !== 0 ? consequent : alternate;
|
|
1037
|
+
return condition.value !== 0 ? optimize(node.consequent) : optimize(node.alternate);
|
|
1320
1038
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
consequent,
|
|
1325
|
-
alternate
|
|
1326
|
-
};
|
|
1039
|
+
const consequent = optimize(node.consequent);
|
|
1040
|
+
const alternate = optimize(node.alternate);
|
|
1041
|
+
return conditional(condition, consequent, alternate);
|
|
1327
1042
|
}
|
|
1328
1043
|
if (isProgram(node)) {
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
statements: node.statements.map((stmt) => basicOptimize(stmt))
|
|
1332
|
-
};
|
|
1044
|
+
const optimizedStatements = node.statements.map((stmt) => optimize(stmt));
|
|
1045
|
+
return program(optimizedStatements);
|
|
1333
1046
|
}
|
|
1334
1047
|
return node;
|
|
1335
1048
|
}
|
|
@@ -1339,7 +1052,6 @@ export {
|
|
|
1339
1052
|
isUnaryOp,
|
|
1340
1053
|
isProgram,
|
|
1341
1054
|
isNumberLiteral,
|
|
1342
|
-
isNullishAssignment,
|
|
1343
1055
|
isIdentifier,
|
|
1344
1056
|
isFunctionCall,
|
|
1345
1057
|
isConditionalExpression,
|
|
@@ -1348,6 +1060,7 @@ export {
|
|
|
1348
1060
|
generate,
|
|
1349
1061
|
execute,
|
|
1350
1062
|
defaultContext,
|
|
1063
|
+
exports_date_utils as dateUtils,
|
|
1351
1064
|
exports_ast as ast,
|
|
1352
1065
|
TokenType,
|
|
1353
1066
|
Parser,
|