littlewing 0.5.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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);
@@ -422,8 +410,10 @@ var defaultContext = {
422
410
  class Lexer {
423
411
  source;
424
412
  position = 0;
413
+ length;
425
414
  constructor(source) {
426
415
  this.source = source;
416
+ this.length = source.length;
427
417
  }
428
418
  tokenize() {
429
419
  const tokens = [];
@@ -438,16 +428,22 @@ class Lexer {
438
428
  }
439
429
  nextToken() {
440
430
  this.skipWhitespaceAndComments();
441
- if (this.position >= this.source.length) {
431
+ if (this.position >= this.length) {
432
+ return { type: "EOF" /* EOF */, value: "", position: this.position };
433
+ }
434
+ const char = this.source[this.position];
435
+ if (char === undefined) {
442
436
  return { type: "EOF" /* EOF */, value: "", position: this.position };
443
437
  }
444
- const char = this.getCharAt(this.position);
445
438
  const start = this.position;
446
439
  if (this.isDigit(char)) {
447
440
  return this.readNumber();
448
441
  }
449
- if (char === "." && this.isDigit(this.peek())) {
450
- return this.readNumber();
442
+ if (char === ".") {
443
+ const nextChar = this.source[this.position + 1];
444
+ if (nextChar !== undefined && this.isDigit(nextChar)) {
445
+ return this.readNumber();
446
+ }
451
447
  }
452
448
  if (this.isLetter(char) || char === "_") {
453
449
  return this.readIdentifier();
@@ -478,41 +474,33 @@ class Lexer {
478
474
  this.position++;
479
475
  return { type: "RPAREN" /* RPAREN */, value: ")", position: start };
480
476
  case "=":
481
- if (this.peek() === "=") {
477
+ if (this.source[this.position + 1] === "=") {
482
478
  this.position += 2;
483
479
  return { type: "DOUBLE_EQUALS" /* DOUBLE_EQUALS */, value: "==", position: start };
484
480
  }
485
481
  this.position++;
486
482
  return { type: "EQUALS" /* EQUALS */, value: "=", position: start };
487
483
  case "!":
488
- if (this.peek() === "=") {
484
+ if (this.source[this.position + 1] === "=") {
489
485
  this.position += 2;
490
486
  return { type: "NOT_EQUALS" /* NOT_EQUALS */, value: "!=", position: start };
491
487
  }
492
488
  throw new Error(`Unexpected character '${char}' at position ${start}`);
493
489
  case "<":
494
- if (this.peek() === "=") {
490
+ if (this.source[this.position + 1] === "=") {
495
491
  this.position += 2;
496
492
  return { type: "LESS_EQUAL" /* LESS_EQUAL */, value: "<=", position: start };
497
493
  }
498
494
  this.position++;
499
495
  return { type: "LESS_THAN" /* LESS_THAN */, value: "<", position: start };
500
496
  case ">":
501
- if (this.peek() === "=") {
497
+ if (this.source[this.position + 1] === "=") {
502
498
  this.position += 2;
503
499
  return { type: "GREATER_EQUAL" /* GREATER_EQUAL */, value: ">=", position: start };
504
500
  }
505
501
  this.position++;
506
502
  return { type: "GREATER_THAN" /* GREATER_THAN */, value: ">", position: start };
507
503
  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
504
  this.position++;
517
505
  return { type: "QUESTION" /* QUESTION */, value: "?", position: start };
518
506
  case ":":
@@ -525,13 +513,13 @@ class Lexer {
525
513
  this.position++;
526
514
  return this.nextToken();
527
515
  case "&":
528
- if (this.peek() === "&") {
516
+ if (this.source[this.position + 1] === "&") {
529
517
  this.position += 2;
530
518
  return { type: "LOGICAL_AND" /* LOGICAL_AND */, value: "&&", position: start };
531
519
  }
532
520
  throw new Error(`Unexpected character '${char}' at position ${start}`);
533
521
  case "|":
534
- if (this.peek() === "|") {
522
+ if (this.source[this.position + 1] === "|") {
535
523
  this.position += 2;
536
524
  return { type: "LOGICAL_OR" /* LOGICAL_OR */, value: "||", position: start };
537
525
  }
@@ -541,14 +529,15 @@ class Lexer {
541
529
  }
542
530
  }
543
531
  skipWhitespaceAndComments() {
544
- while (this.position < this.source.length) {
545
- const char = this.getCharAt(this.position);
532
+ while (this.position < this.length) {
533
+ const char = this.source[this.position];
546
534
  if (this.isWhitespace(char)) {
547
535
  this.position++;
548
536
  continue;
549
537
  }
550
- if (char === "/" && this.peek() === "/") {
551
- while (this.position < this.source.length && this.getCharAt(this.position) !== `
538
+ if (char === "/" && this.source[this.position + 1] === "/") {
539
+ this.position += 2;
540
+ while (this.position < this.length && this.source[this.position] !== `
552
541
  `) {
553
542
  this.position++;
554
543
  }
@@ -561,12 +550,12 @@ class Lexer {
561
550
  const start = this.position;
562
551
  let hasDecimal = false;
563
552
  let hasExponent = false;
564
- if (this.getCharAt(this.position) === ".") {
553
+ if (this.source[this.position] === ".") {
565
554
  hasDecimal = true;
566
555
  this.position++;
567
556
  }
568
- while (this.position < this.source.length) {
569
- const char = this.getCharAt(this.position);
557
+ while (this.position < this.length) {
558
+ const char = this.source[this.position];
570
559
  if (this.isDigit(char)) {
571
560
  this.position++;
572
561
  } else if (char === "." && !hasDecimal && !hasExponent) {
@@ -575,14 +564,15 @@ class Lexer {
575
564
  } else if ((char === "e" || char === "E") && !hasExponent) {
576
565
  hasExponent = true;
577
566
  this.position++;
578
- const nextChar = this.getCharAt(this.position);
567
+ const nextChar = this.source[this.position];
579
568
  if (nextChar === "+" || nextChar === "-") {
580
569
  this.position++;
581
570
  }
582
- if (!this.isDigit(this.getCharAt(this.position))) {
571
+ const digitChar = this.source[this.position];
572
+ if (digitChar === undefined || !this.isDigit(digitChar)) {
583
573
  throw new Error(`Invalid number: expected digit after exponent at position ${this.position}`);
584
574
  }
585
- while (this.position < this.source.length && this.isDigit(this.getCharAt(this.position))) {
575
+ while (this.position < this.length && this.isDigit(this.source[this.position])) {
586
576
  this.position++;
587
577
  }
588
578
  break;
@@ -595,8 +585,8 @@ class Lexer {
595
585
  }
596
586
  readIdentifier() {
597
587
  const start = this.position;
598
- while (this.position < this.source.length) {
599
- const char = this.getCharAt(this.position);
588
+ while (this.position < this.length) {
589
+ const char = this.source[this.position];
600
590
  if (this.isLetter(char) || this.isDigit(char) || char === "_") {
601
591
  this.position++;
602
592
  } else {
@@ -606,24 +596,15 @@ class Lexer {
606
596
  const name = this.source.slice(start, this.position);
607
597
  return { type: "IDENTIFIER" /* IDENTIFIER */, value: name, position: start };
608
598
  }
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
599
  isDigit(char) {
619
- return char >= "0" && char <= "9";
600
+ return char !== undefined && char >= "0" && char <= "9";
620
601
  }
621
602
  isLetter(char) {
622
- return char >= "a" && char <= "z" || char >= "A" && char <= "Z";
603
+ return char !== undefined && (char >= "a" && char <= "z" || char >= "A" && char <= "Z");
623
604
  }
624
605
  isWhitespace(char) {
625
- return char === " " || char === "\t" || char === `
626
- ` || char === "\r";
606
+ return char !== undefined && (char === " " || char === "\t" || char === `
607
+ ` || char === "\r");
627
608
  }
628
609
  }
629
610
 
@@ -660,16 +641,13 @@ class Parser {
660
641
  throw new Error("Empty program");
661
642
  }
662
643
  if (statements.length === 1) {
663
- const stmt = statements[0];
664
- if (stmt === undefined) {
665
- throw new Error("Unexpected undefined statement");
644
+ const singleStatement = statements[0];
645
+ if (singleStatement === undefined) {
646
+ throw new Error("Unexpected empty statements array");
666
647
  }
667
- return stmt;
648
+ return singleStatement;
668
649
  }
669
- return {
670
- type: "Program",
671
- statements
672
- };
650
+ return program(statements);
673
651
  }
674
652
  parseExpression(minPrecedence) {
675
653
  let left = this.parsePrefix();
@@ -686,23 +664,7 @@ class Parser {
686
664
  const identName = left.name;
687
665
  this.advance();
688
666
  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
- };
667
+ left = assign(identName, value);
706
668
  } else if (token.type === "QUESTION" /* QUESTION */) {
707
669
  this.advance();
708
670
  const consequent = this.parseExpression(0);
@@ -711,22 +673,12 @@ class Parser {
711
673
  }
712
674
  this.advance();
713
675
  const alternate = this.parseExpression(precedence);
714
- left = {
715
- type: "ConditionalExpression",
716
- condition: left,
717
- consequent,
718
- alternate
719
- };
676
+ left = conditional(left, consequent, alternate);
720
677
  } else if (this.isBinaryOperator(token.type)) {
721
678
  const operator = token.value;
722
679
  this.advance();
723
680
  const right = this.parseExpression(precedence + 1);
724
- left = {
725
- type: "BinaryOp",
726
- left,
727
- operator,
728
- right
729
- };
681
+ left = binaryOp(left, operator, right);
730
682
  } else {
731
683
  break;
732
684
  }
@@ -738,11 +690,7 @@ class Parser {
738
690
  if (token.type === "MINUS" /* MINUS */) {
739
691
  this.advance();
740
692
  const argument = this.parseExpression(this.getUnaryPrecedence());
741
- return {
742
- type: "UnaryOp",
743
- operator: "-",
744
- argument
745
- };
693
+ return unaryOp(argument);
746
694
  }
747
695
  if (token.type === "LPAREN" /* LPAREN */) {
748
696
  this.advance();
@@ -755,10 +703,7 @@ class Parser {
755
703
  }
756
704
  if (token.type === "NUMBER" /* NUMBER */) {
757
705
  this.advance();
758
- return {
759
- type: "NumberLiteral",
760
- value: token.value
761
- };
706
+ return number(token.value);
762
707
  }
763
708
  if (token.type === "IDENTIFIER" /* IDENTIFIER */) {
764
709
  const name = token.value;
@@ -770,16 +715,9 @@ class Parser {
770
715
  throw new Error("Expected closing parenthesis");
771
716
  }
772
717
  this.advance();
773
- return {
774
- type: "FunctionCall",
775
- name,
776
- arguments: args
777
- };
718
+ return functionCall(name, args);
778
719
  }
779
- return {
780
- type: "Identifier",
781
- name
782
- };
720
+ return identifier(name);
783
721
  }
784
722
  throw new Error(`Unexpected token: ${token.value}`);
785
723
  }
@@ -826,9 +764,11 @@ function parseSource(source) {
826
764
  class Executor {
827
765
  context;
828
766
  variables;
767
+ externalVariables;
829
768
  constructor(context = {}) {
830
769
  this.context = context;
831
770
  this.variables = new Map(Object.entries(context.variables || {}));
771
+ this.externalVariables = new Set(Object.keys(context.variables || {}));
832
772
  }
833
773
  execute(node) {
834
774
  if (isProgram(node))
@@ -845,8 +785,6 @@ class Executor {
845
785
  return this.executeFunctionCall(node);
846
786
  if (isAssignment(node))
847
787
  return this.executeAssignment(node);
848
- if (isNullishAssignment(node))
849
- return this.executeNullishAssignment(node);
850
788
  if (isConditionalExpression(node))
851
789
  return this.executeConditionalExpression(node);
852
790
  throw new Error(`Unknown node type`);
@@ -892,13 +830,11 @@ class Executor {
892
830
  return fn(...args);
893
831
  }
894
832
  executeAssignment(node) {
895
- const value = this.execute(node.value);
896
- this.variables.set(node.name, value);
897
- return value;
898
- }
899
- executeNullishAssignment(node) {
900
- if (this.variables.has(node.name)) {
901
- return this.variables.get(node.name);
833
+ if (this.externalVariables.has(node.name)) {
834
+ const externalValue = this.variables.get(node.name);
835
+ if (externalValue !== undefined) {
836
+ return externalValue;
837
+ }
902
838
  }
903
839
  const value = this.execute(node.value);
904
840
  this.variables.set(node.name, value);
@@ -916,420 +852,47 @@ function execute(source, context) {
916
852
  }
917
853
  // src/optimizer.ts
918
854
  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
855
  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
856
  return node;
1147
857
  }
1148
- if (isNumberLiteral(node)) {
858
+ if (node.type === "Identifier") {
1149
859
  return node;
1150
860
  }
1151
861
  if (isBinaryOp(node)) {
1152
- const left = evaluateWithConstants(node.left, constants);
1153
- const right = evaluateWithConstants(node.right, constants);
862
+ const left = optimize(node.left);
863
+ const right = optimize(node.right);
1154
864
  if (isNumberLiteral(left) && isNumberLiteral(right)) {
1155
865
  const result = evaluateBinaryOperation(node.operator, left.value, right.value);
1156
866
  return number(result);
1157
867
  }
1158
- return {
1159
- ...node,
1160
- left,
1161
- right
1162
- };
868
+ return binaryOp(left, node.operator, right);
1163
869
  }
1164
870
  if (isUnaryOp(node)) {
1165
- const argument = evaluateWithConstants(node.argument, constants);
871
+ const argument = optimize(node.argument);
1166
872
  if (isNumberLiteral(argument)) {
1167
873
  return number(-argument.value);
1168
874
  }
1169
- return {
1170
- ...node,
1171
- argument
1172
- };
875
+ return unaryOp(argument);
1173
876
  }
1174
877
  if (isFunctionCall(node)) {
1175
- return {
1176
- ...node,
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
- };
1193
- }
1194
- 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
- }
878
+ const optimizedArgs = node.arguments.map((arg) => optimize(arg));
879
+ return functionCall(node.name, optimizedArgs);
1259
880
  }
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
881
  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
- };
882
+ return assign(node.name, optimize(node.value));
1313
883
  }
1314
884
  if (isConditionalExpression(node)) {
1315
- const condition = basicOptimize(node.condition);
1316
- const consequent = basicOptimize(node.consequent);
1317
- const alternate = basicOptimize(node.alternate);
885
+ const condition = optimize(node.condition);
1318
886
  if (isNumberLiteral(condition)) {
1319
- return condition.value !== 0 ? consequent : alternate;
887
+ return condition.value !== 0 ? optimize(node.consequent) : optimize(node.alternate);
1320
888
  }
1321
- return {
1322
- ...node,
1323
- condition,
1324
- consequent,
1325
- alternate
1326
- };
889
+ const consequent = optimize(node.consequent);
890
+ const alternate = optimize(node.alternate);
891
+ return conditional(condition, consequent, alternate);
1327
892
  }
1328
893
  if (isProgram(node)) {
1329
- return {
1330
- ...node,
1331
- statements: node.statements.map((stmt) => basicOptimize(stmt))
1332
- };
894
+ const optimizedStatements = node.statements.map((stmt) => optimize(stmt));
895
+ return program(optimizedStatements);
1333
896
  }
1334
897
  return node;
1335
898
  }
@@ -1339,7 +902,6 @@ export {
1339
902
  isUnaryOp,
1340
903
  isProgram,
1341
904
  isNumberLiteral,
1342
- isNullishAssignment,
1343
905
  isIdentifier,
1344
906
  isFunctionCall,
1345
907
  isConditionalExpression,