littlewing 0.5.2 → 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,14 +428,23 @@ 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
  }
442
+ if (char === ".") {
443
+ const nextChar = this.source[this.position + 1];
444
+ if (nextChar !== undefined && this.isDigit(nextChar)) {
445
+ return this.readNumber();
446
+ }
447
+ }
449
448
  if (this.isLetter(char) || char === "_") {
450
449
  return this.readIdentifier();
451
450
  }
@@ -475,41 +474,33 @@ class Lexer {
475
474
  this.position++;
476
475
  return { type: "RPAREN" /* RPAREN */, value: ")", position: start };
477
476
  case "=":
478
- if (this.peek() === "=") {
477
+ if (this.source[this.position + 1] === "=") {
479
478
  this.position += 2;
480
479
  return { type: "DOUBLE_EQUALS" /* DOUBLE_EQUALS */, value: "==", position: start };
481
480
  }
482
481
  this.position++;
483
482
  return { type: "EQUALS" /* EQUALS */, value: "=", position: start };
484
483
  case "!":
485
- if (this.peek() === "=") {
484
+ if (this.source[this.position + 1] === "=") {
486
485
  this.position += 2;
487
486
  return { type: "NOT_EQUALS" /* NOT_EQUALS */, value: "!=", position: start };
488
487
  }
489
488
  throw new Error(`Unexpected character '${char}' at position ${start}`);
490
489
  case "<":
491
- if (this.peek() === "=") {
490
+ if (this.source[this.position + 1] === "=") {
492
491
  this.position += 2;
493
492
  return { type: "LESS_EQUAL" /* LESS_EQUAL */, value: "<=", position: start };
494
493
  }
495
494
  this.position++;
496
495
  return { type: "LESS_THAN" /* LESS_THAN */, value: "<", position: start };
497
496
  case ">":
498
- if (this.peek() === "=") {
497
+ if (this.source[this.position + 1] === "=") {
499
498
  this.position += 2;
500
499
  return { type: "GREATER_EQUAL" /* GREATER_EQUAL */, value: ">=", position: start };
501
500
  }
502
501
  this.position++;
503
502
  return { type: "GREATER_THAN" /* GREATER_THAN */, value: ">", position: start };
504
503
  case "?":
505
- if (this.peek() === "?" && this.peekAhead(2) === "=") {
506
- this.position += 3;
507
- return {
508
- type: "NULLISH_ASSIGN" /* NULLISH_ASSIGN */,
509
- value: "??=",
510
- position: start
511
- };
512
- }
513
504
  this.position++;
514
505
  return { type: "QUESTION" /* QUESTION */, value: "?", position: start };
515
506
  case ":":
@@ -522,13 +513,13 @@ class Lexer {
522
513
  this.position++;
523
514
  return this.nextToken();
524
515
  case "&":
525
- if (this.peek() === "&") {
516
+ if (this.source[this.position + 1] === "&") {
526
517
  this.position += 2;
527
518
  return { type: "LOGICAL_AND" /* LOGICAL_AND */, value: "&&", position: start };
528
519
  }
529
520
  throw new Error(`Unexpected character '${char}' at position ${start}`);
530
521
  case "|":
531
- if (this.peek() === "|") {
522
+ if (this.source[this.position + 1] === "|") {
532
523
  this.position += 2;
533
524
  return { type: "LOGICAL_OR" /* LOGICAL_OR */, value: "||", position: start };
534
525
  }
@@ -538,14 +529,15 @@ class Lexer {
538
529
  }
539
530
  }
540
531
  skipWhitespaceAndComments() {
541
- while (this.position < this.source.length) {
542
- const char = this.getCharAt(this.position);
532
+ while (this.position < this.length) {
533
+ const char = this.source[this.position];
543
534
  if (this.isWhitespace(char)) {
544
535
  this.position++;
545
536
  continue;
546
537
  }
547
- if (char === "/" && this.peek() === "/") {
548
- 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] !== `
549
541
  `) {
550
542
  this.position++;
551
543
  }
@@ -558,8 +550,12 @@ class Lexer {
558
550
  const start = this.position;
559
551
  let hasDecimal = false;
560
552
  let hasExponent = false;
561
- while (this.position < this.source.length) {
562
- const char = this.getCharAt(this.position);
553
+ if (this.source[this.position] === ".") {
554
+ hasDecimal = true;
555
+ this.position++;
556
+ }
557
+ while (this.position < this.length) {
558
+ const char = this.source[this.position];
563
559
  if (this.isDigit(char)) {
564
560
  this.position++;
565
561
  } else if (char === "." && !hasDecimal && !hasExponent) {
@@ -568,14 +564,15 @@ class Lexer {
568
564
  } else if ((char === "e" || char === "E") && !hasExponent) {
569
565
  hasExponent = true;
570
566
  this.position++;
571
- const nextChar = this.getCharAt(this.position);
567
+ const nextChar = this.source[this.position];
572
568
  if (nextChar === "+" || nextChar === "-") {
573
569
  this.position++;
574
570
  }
575
- if (!this.isDigit(this.getCharAt(this.position))) {
571
+ const digitChar = this.source[this.position];
572
+ if (digitChar === undefined || !this.isDigit(digitChar)) {
576
573
  throw new Error(`Invalid number: expected digit after exponent at position ${this.position}`);
577
574
  }
578
- while (this.position < this.source.length && this.isDigit(this.getCharAt(this.position))) {
575
+ while (this.position < this.length && this.isDigit(this.source[this.position])) {
579
576
  this.position++;
580
577
  }
581
578
  break;
@@ -588,8 +585,8 @@ class Lexer {
588
585
  }
589
586
  readIdentifier() {
590
587
  const start = this.position;
591
- while (this.position < this.source.length) {
592
- const char = this.getCharAt(this.position);
588
+ while (this.position < this.length) {
589
+ const char = this.source[this.position];
593
590
  if (this.isLetter(char) || this.isDigit(char) || char === "_") {
594
591
  this.position++;
595
592
  } else {
@@ -599,24 +596,15 @@ class Lexer {
599
596
  const name = this.source.slice(start, this.position);
600
597
  return { type: "IDENTIFIER" /* IDENTIFIER */, value: name, position: start };
601
598
  }
602
- getCharAt(pos) {
603
- return pos < this.source.length ? this.source[pos] || "" : "";
604
- }
605
- peek() {
606
- return this.getCharAt(this.position + 1);
607
- }
608
- peekAhead(n) {
609
- return this.getCharAt(this.position + n);
610
- }
611
599
  isDigit(char) {
612
- return char >= "0" && char <= "9";
600
+ return char !== undefined && char >= "0" && char <= "9";
613
601
  }
614
602
  isLetter(char) {
615
- return char >= "a" && char <= "z" || char >= "A" && char <= "Z";
603
+ return char !== undefined && (char >= "a" && char <= "z" || char >= "A" && char <= "Z");
616
604
  }
617
605
  isWhitespace(char) {
618
- return char === " " || char === "\t" || char === `
619
- ` || char === "\r";
606
+ return char !== undefined && (char === " " || char === "\t" || char === `
607
+ ` || char === "\r");
620
608
  }
621
609
  }
622
610
 
@@ -653,16 +641,13 @@ class Parser {
653
641
  throw new Error("Empty program");
654
642
  }
655
643
  if (statements.length === 1) {
656
- const stmt = statements[0];
657
- if (stmt === undefined) {
658
- throw new Error("Unexpected undefined statement");
644
+ const singleStatement = statements[0];
645
+ if (singleStatement === undefined) {
646
+ throw new Error("Unexpected empty statements array");
659
647
  }
660
- return stmt;
648
+ return singleStatement;
661
649
  }
662
- return {
663
- type: "Program",
664
- statements
665
- };
650
+ return program(statements);
666
651
  }
667
652
  parseExpression(minPrecedence) {
668
653
  let left = this.parsePrefix();
@@ -679,23 +664,7 @@ class Parser {
679
664
  const identName = left.name;
680
665
  this.advance();
681
666
  const value = this.parseExpression(precedence);
682
- left = {
683
- type: "Assignment",
684
- name: identName,
685
- value
686
- };
687
- } else if (token.type === "NULLISH_ASSIGN" /* NULLISH_ASSIGN */) {
688
- if (left.type !== "Identifier") {
689
- throw new Error("Invalid assignment target");
690
- }
691
- const identName = left.name;
692
- this.advance();
693
- const value = this.parseExpression(precedence);
694
- left = {
695
- type: "NullishAssignment",
696
- name: identName,
697
- value
698
- };
667
+ left = assign(identName, value);
699
668
  } else if (token.type === "QUESTION" /* QUESTION */) {
700
669
  this.advance();
701
670
  const consequent = this.parseExpression(0);
@@ -704,22 +673,12 @@ class Parser {
704
673
  }
705
674
  this.advance();
706
675
  const alternate = this.parseExpression(precedence);
707
- left = {
708
- type: "ConditionalExpression",
709
- condition: left,
710
- consequent,
711
- alternate
712
- };
676
+ left = conditional(left, consequent, alternate);
713
677
  } else if (this.isBinaryOperator(token.type)) {
714
678
  const operator = token.value;
715
679
  this.advance();
716
680
  const right = this.parseExpression(precedence + 1);
717
- left = {
718
- type: "BinaryOp",
719
- left,
720
- operator,
721
- right
722
- };
681
+ left = binaryOp(left, operator, right);
723
682
  } else {
724
683
  break;
725
684
  }
@@ -731,11 +690,7 @@ class Parser {
731
690
  if (token.type === "MINUS" /* MINUS */) {
732
691
  this.advance();
733
692
  const argument = this.parseExpression(this.getUnaryPrecedence());
734
- return {
735
- type: "UnaryOp",
736
- operator: "-",
737
- argument
738
- };
693
+ return unaryOp(argument);
739
694
  }
740
695
  if (token.type === "LPAREN" /* LPAREN */) {
741
696
  this.advance();
@@ -748,10 +703,7 @@ class Parser {
748
703
  }
749
704
  if (token.type === "NUMBER" /* NUMBER */) {
750
705
  this.advance();
751
- return {
752
- type: "NumberLiteral",
753
- value: token.value
754
- };
706
+ return number(token.value);
755
707
  }
756
708
  if (token.type === "IDENTIFIER" /* IDENTIFIER */) {
757
709
  const name = token.value;
@@ -763,16 +715,9 @@ class Parser {
763
715
  throw new Error("Expected closing parenthesis");
764
716
  }
765
717
  this.advance();
766
- return {
767
- type: "FunctionCall",
768
- name,
769
- arguments: args
770
- };
718
+ return functionCall(name, args);
771
719
  }
772
- return {
773
- type: "Identifier",
774
- name
775
- };
720
+ return identifier(name);
776
721
  }
777
722
  throw new Error(`Unexpected token: ${token.value}`);
778
723
  }
@@ -819,9 +764,11 @@ function parseSource(source) {
819
764
  class Executor {
820
765
  context;
821
766
  variables;
767
+ externalVariables;
822
768
  constructor(context = {}) {
823
769
  this.context = context;
824
770
  this.variables = new Map(Object.entries(context.variables || {}));
771
+ this.externalVariables = new Set(Object.keys(context.variables || {}));
825
772
  }
826
773
  execute(node) {
827
774
  if (isProgram(node))
@@ -838,8 +785,6 @@ class Executor {
838
785
  return this.executeFunctionCall(node);
839
786
  if (isAssignment(node))
840
787
  return this.executeAssignment(node);
841
- if (isNullishAssignment(node))
842
- return this.executeNullishAssignment(node);
843
788
  if (isConditionalExpression(node))
844
789
  return this.executeConditionalExpression(node);
845
790
  throw new Error(`Unknown node type`);
@@ -885,13 +830,11 @@ class Executor {
885
830
  return fn(...args);
886
831
  }
887
832
  executeAssignment(node) {
888
- const value = this.execute(node.value);
889
- this.variables.set(node.name, value);
890
- return value;
891
- }
892
- executeNullishAssignment(node) {
893
- if (this.variables.has(node.name)) {
894
- 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
+ }
895
838
  }
896
839
  const value = this.execute(node.value);
897
840
  this.variables.set(node.name, value);
@@ -909,420 +852,47 @@ function execute(source, context) {
909
852
  }
910
853
  // src/optimizer.ts
911
854
  function optimize(node) {
912
- if (!isProgram(node)) {
913
- return basicOptimize(node);
914
- }
915
- const analysis = analyzeProgram(node);
916
- const { propagated, allConstants } = propagateConstantsOptimal(node, analysis);
917
- const optimized = eliminateDeadCodeOptimal(propagated, analysis, allConstants);
918
- return optimized;
919
- }
920
- function analyzeProgram(node) {
921
- if (!isProgram(node)) {
922
- return {
923
- constants: new Map,
924
- tainted: new Set,
925
- dependencies: new Map,
926
- liveVariables: new Set,
927
- assignmentIndices: new Map,
928
- evaluationOrder: []
929
- };
930
- }
931
- const constants = new Map;
932
- const tainted = new Set;
933
- const dependencies = new Map;
934
- const assignmentIndices = new Map;
935
- const assignmentCounts = new Map;
936
- for (let i = 0;i < node.statements.length; i++) {
937
- const stmt = node.statements[i];
938
- if (!stmt)
939
- continue;
940
- if (isAssignment(stmt) || isNullishAssignment(stmt)) {
941
- const varName = stmt.name;
942
- const count = assignmentCounts.get(varName) || 0;
943
- assignmentCounts.set(varName, count + 1);
944
- assignmentIndices.set(varName, i);
945
- const deps = new Set;
946
- const hasFunctionCall = collectDependencies(stmt.value, deps);
947
- dependencies.set(varName, deps);
948
- if (isNullishAssignment(stmt)) {
949
- tainted.add(varName);
950
- } else {
951
- if (count === 0 && isNumberLiteral(stmt.value)) {
952
- constants.set(varName, stmt.value.value);
953
- }
954
- if (hasFunctionCall) {
955
- tainted.add(varName);
956
- }
957
- }
958
- }
959
- }
960
- for (const [varName, count] of assignmentCounts) {
961
- if (count > 1) {
962
- constants.delete(varName);
963
- tainted.add(varName);
964
- }
965
- }
966
- let taintChanged = true;
967
- while (taintChanged) {
968
- taintChanged = false;
969
- for (const [varName, deps] of dependencies) {
970
- if (tainted.has(varName))
971
- continue;
972
- for (const dep of deps) {
973
- if (tainted.has(dep)) {
974
- tainted.add(varName);
975
- constants.delete(varName);
976
- taintChanged = true;
977
- break;
978
- }
979
- }
980
- }
981
- }
982
- const liveVariables = new Set;
983
- const lastStmt = node.statements[node.statements.length - 1];
984
- if (lastStmt) {
985
- if (isAssignment(lastStmt)) {
986
- const deps = new Set;
987
- collectDependencies(lastStmt.value, deps);
988
- for (const dep of deps) {
989
- liveVariables.add(dep);
990
- }
991
- } else {
992
- const deps = new Set;
993
- collectDependencies(lastStmt, deps);
994
- for (const dep of deps) {
995
- liveVariables.add(dep);
996
- }
997
- }
998
- }
999
- let liveChanged = true;
1000
- while (liveChanged) {
1001
- liveChanged = false;
1002
- for (const [varName, deps] of dependencies) {
1003
- if (liveVariables.has(varName)) {
1004
- for (const dep of deps) {
1005
- if (!liveVariables.has(dep)) {
1006
- liveVariables.add(dep);
1007
- liveChanged = true;
1008
- }
1009
- }
1010
- }
1011
- }
1012
- }
1013
- const evaluationOrder = topologicalSort(dependencies, liveVariables);
1014
- return {
1015
- constants,
1016
- tainted,
1017
- dependencies,
1018
- liveVariables,
1019
- assignmentIndices,
1020
- evaluationOrder
1021
- };
1022
- }
1023
- function collectDependencies(node, deps) {
1024
- if (isIdentifier(node)) {
1025
- deps.add(node.name);
1026
- return false;
1027
- }
1028
855
  if (isNumberLiteral(node)) {
1029
- return false;
1030
- }
1031
- if (isAssignment(node) || isNullishAssignment(node)) {
1032
- return collectDependencies(node.value, deps);
1033
- }
1034
- if (isBinaryOp(node)) {
1035
- const leftHasCall = collectDependencies(node.left, deps);
1036
- const rightHasCall = collectDependencies(node.right, deps);
1037
- return leftHasCall || rightHasCall;
1038
- }
1039
- if (isUnaryOp(node)) {
1040
- return collectDependencies(node.argument, deps);
1041
- }
1042
- if (isFunctionCall(node)) {
1043
- for (const arg of node.arguments) {
1044
- collectDependencies(arg, deps);
1045
- }
1046
- return true;
1047
- }
1048
- if (isConditionalExpression(node)) {
1049
- const condHasCall = collectDependencies(node.condition, deps);
1050
- const consHasCall = collectDependencies(node.consequent, deps);
1051
- const altHasCall = collectDependencies(node.alternate, deps);
1052
- return condHasCall || consHasCall || altHasCall;
1053
- }
1054
- if (isProgram(node)) {
1055
- let hasCall = false;
1056
- for (const stmt of node.statements) {
1057
- hasCall = collectDependencies(stmt, deps) || hasCall;
1058
- }
1059
- return hasCall;
1060
- }
1061
- return false;
1062
- }
1063
- function topologicalSort(dependencies, liveVariables) {
1064
- const result = [];
1065
- const visited = new Set;
1066
- const visiting = new Set;
1067
- function visit(varName) {
1068
- if (visited.has(varName))
1069
- return;
1070
- if (visiting.has(varName)) {
1071
- return;
1072
- }
1073
- visiting.add(varName);
1074
- const deps = dependencies.get(varName);
1075
- if (deps) {
1076
- for (const dep of deps) {
1077
- if (liveVariables.has(dep)) {
1078
- visit(dep);
1079
- }
1080
- }
1081
- }
1082
- visiting.delete(varName);
1083
- visited.add(varName);
1084
- result.push(varName);
1085
- }
1086
- for (const varName of liveVariables) {
1087
- visit(varName);
1088
- }
1089
- return result;
1090
- }
1091
- function propagateConstantsOptimal(node, analysis) {
1092
- if (!isProgram(node)) {
1093
- return { propagated: node, allConstants: new Map };
1094
- }
1095
- const allConstants = new Map(analysis.constants);
1096
- for (const varName of analysis.evaluationOrder) {
1097
- if (allConstants.has(varName))
1098
- continue;
1099
- if (analysis.tainted.has(varName))
1100
- continue;
1101
- const deps = analysis.dependencies.get(varName);
1102
- if (!deps)
1103
- continue;
1104
- let allDepsConstant = true;
1105
- for (const dep of deps) {
1106
- if (!allConstants.has(dep)) {
1107
- allDepsConstant = false;
1108
- break;
1109
- }
1110
- }
1111
- if (allDepsConstant) {
1112
- const assignmentIdx = analysis.assignmentIndices.get(varName);
1113
- if (assignmentIdx !== undefined) {
1114
- const stmt = node.statements[assignmentIdx];
1115
- if (stmt && isAssignment(stmt)) {
1116
- const evaluated = evaluateWithConstants(stmt.value, allConstants);
1117
- if (isNumberLiteral(evaluated)) {
1118
- allConstants.set(varName, evaluated.value);
1119
- }
1120
- }
1121
- }
1122
- }
1123
- }
1124
- const statements = node.statements.map((stmt) => replaceWithConstants(stmt, allConstants));
1125
- return {
1126
- propagated: {
1127
- type: "Program",
1128
- statements
1129
- },
1130
- allConstants
1131
- };
1132
- }
1133
- function evaluateWithConstants(node, constants) {
1134
- if (isIdentifier(node)) {
1135
- const value = constants.get(node.name);
1136
- if (value !== undefined) {
1137
- return number(value);
1138
- }
1139
856
  return node;
1140
857
  }
1141
- if (isNumberLiteral(node)) {
858
+ if (node.type === "Identifier") {
1142
859
  return node;
1143
860
  }
1144
861
  if (isBinaryOp(node)) {
1145
- const left = evaluateWithConstants(node.left, constants);
1146
- const right = evaluateWithConstants(node.right, constants);
862
+ const left = optimize(node.left);
863
+ const right = optimize(node.right);
1147
864
  if (isNumberLiteral(left) && isNumberLiteral(right)) {
1148
865
  const result = evaluateBinaryOperation(node.operator, left.value, right.value);
1149
866
  return number(result);
1150
867
  }
1151
- return {
1152
- ...node,
1153
- left,
1154
- right
1155
- };
868
+ return binaryOp(left, node.operator, right);
1156
869
  }
1157
870
  if (isUnaryOp(node)) {
1158
- const argument = evaluateWithConstants(node.argument, constants);
871
+ const argument = optimize(node.argument);
1159
872
  if (isNumberLiteral(argument)) {
1160
873
  return number(-argument.value);
1161
874
  }
1162
- return {
1163
- ...node,
1164
- argument
1165
- };
875
+ return unaryOp(argument);
1166
876
  }
1167
877
  if (isFunctionCall(node)) {
1168
- return {
1169
- ...node,
1170
- arguments: node.arguments.map((arg) => evaluateWithConstants(arg, constants))
1171
- };
1172
- }
1173
- if (isConditionalExpression(node)) {
1174
- const condition = evaluateWithConstants(node.condition, constants);
1175
- const consequent = evaluateWithConstants(node.consequent, constants);
1176
- const alternate = evaluateWithConstants(node.alternate, constants);
1177
- if (isNumberLiteral(condition)) {
1178
- return condition.value !== 0 ? consequent : alternate;
1179
- }
1180
- return {
1181
- ...node,
1182
- condition,
1183
- consequent,
1184
- alternate
1185
- };
1186
- }
1187
- if (isAssignment(node)) {
1188
- return {
1189
- ...node,
1190
- value: evaluateWithConstants(node.value, constants)
1191
- };
1192
- }
1193
- if (isNullishAssignment(node)) {
1194
- return {
1195
- ...node,
1196
- value: evaluateWithConstants(node.value, constants)
1197
- };
1198
- }
1199
- return node;
1200
- }
1201
- function replaceWithConstants(node, constants) {
1202
- return evaluateWithConstants(node, constants);
1203
- }
1204
- function eliminateDeadCodeOptimal(node, analysis, allConstants) {
1205
- if (!isProgram(node))
1206
- return node;
1207
- const lastStmt = node.statements[node.statements.length - 1];
1208
- if (lastStmt) {
1209
- const evaluated = evaluateWithConstants(lastStmt, allConstants);
1210
- if (isNumberLiteral(evaluated)) {
1211
- return evaluated;
1212
- }
1213
- }
1214
- const filteredStatements = [];
1215
- const variablesInUse = new Set;
1216
- for (const stmt of node.statements) {
1217
- collectDependencies(stmt, variablesInUse);
1218
- }
1219
- for (let i = 0;i < node.statements.length; i++) {
1220
- const stmt = node.statements[i];
1221
- if (!stmt)
1222
- continue;
1223
- if (i === node.statements.length - 1) {
1224
- filteredStatements.push(stmt);
1225
- continue;
1226
- }
1227
- if (isAssignment(stmt) || isNullishAssignment(stmt)) {
1228
- if (variablesInUse.has(stmt.name)) {
1229
- filteredStatements.push(stmt);
1230
- }
1231
- } else {
1232
- filteredStatements.push(stmt);
1233
- }
1234
- }
1235
- if (filteredStatements.length === 1) {
1236
- const singleStmt = filteredStatements[0];
1237
- if (!singleStmt) {
1238
- return node;
1239
- }
1240
- if (isNumberLiteral(singleStmt)) {
1241
- return singleStmt;
1242
- }
1243
- if (isAssignment(singleStmt) && isNumberLiteral(singleStmt.value)) {
1244
- if (!analysis.liveVariables.has(singleStmt.name)) {
1245
- return singleStmt.value;
1246
- }
1247
- }
1248
- const evaluated = evaluateWithConstants(singleStmt, allConstants);
1249
- if (isNumberLiteral(evaluated)) {
1250
- return evaluated;
1251
- }
878
+ const optimizedArgs = node.arguments.map((arg) => optimize(arg));
879
+ return functionCall(node.name, optimizedArgs);
1252
880
  }
1253
- if (filteredStatements.length === 0) {
1254
- const lastStmt2 = node.statements[node.statements.length - 1];
1255
- if (lastStmt2 && isAssignment(lastStmt2) && isNumberLiteral(lastStmt2.value)) {
1256
- return lastStmt2.value;
1257
- }
1258
- return node;
1259
- }
1260
- return {
1261
- type: "Program",
1262
- statements: filteredStatements
1263
- };
1264
- }
1265
- function basicOptimize(node) {
1266
881
  if (isAssignment(node)) {
1267
- return {
1268
- ...node,
1269
- value: basicOptimize(node.value)
1270
- };
1271
- }
1272
- if (isNullishAssignment(node)) {
1273
- return {
1274
- ...node,
1275
- value: basicOptimize(node.value)
1276
- };
1277
- }
1278
- if (isBinaryOp(node)) {
1279
- const left = basicOptimize(node.left);
1280
- const right = basicOptimize(node.right);
1281
- if (isNumberLiteral(left) && isNumberLiteral(right)) {
1282
- const result = evaluateBinaryOperation(node.operator, left.value, right.value);
1283
- return number(result);
1284
- }
1285
- return {
1286
- ...node,
1287
- left,
1288
- right
1289
- };
1290
- }
1291
- if (isUnaryOp(node)) {
1292
- const argument = basicOptimize(node.argument);
1293
- if (isNumberLiteral(argument)) {
1294
- return number(-argument.value);
1295
- }
1296
- return {
1297
- ...node,
1298
- argument
1299
- };
1300
- }
1301
- if (isFunctionCall(node)) {
1302
- return {
1303
- ...node,
1304
- arguments: node.arguments.map((arg) => basicOptimize(arg))
1305
- };
882
+ return assign(node.name, optimize(node.value));
1306
883
  }
1307
884
  if (isConditionalExpression(node)) {
1308
- const condition = basicOptimize(node.condition);
1309
- const consequent = basicOptimize(node.consequent);
1310
- const alternate = basicOptimize(node.alternate);
885
+ const condition = optimize(node.condition);
1311
886
  if (isNumberLiteral(condition)) {
1312
- return condition.value !== 0 ? consequent : alternate;
887
+ return condition.value !== 0 ? optimize(node.consequent) : optimize(node.alternate);
1313
888
  }
1314
- return {
1315
- ...node,
1316
- condition,
1317
- consequent,
1318
- alternate
1319
- };
889
+ const consequent = optimize(node.consequent);
890
+ const alternate = optimize(node.alternate);
891
+ return conditional(condition, consequent, alternate);
1320
892
  }
1321
893
  if (isProgram(node)) {
1322
- return {
1323
- ...node,
1324
- statements: node.statements.map((stmt) => basicOptimize(stmt))
1325
- };
894
+ const optimizedStatements = node.statements.map((stmt) => optimize(stmt));
895
+ return program(optimizedStatements);
1326
896
  }
1327
897
  return node;
1328
898
  }
@@ -1332,7 +902,6 @@ export {
1332
902
  isUnaryOp,
1333
903
  isProgram,
1334
904
  isNumberLiteral,
1335
- isNullishAssignment,
1336
905
  isIdentifier,
1337
906
  isFunctionCall,
1338
907
  isConditionalExpression,