littlewing 0.4.1 → 0.5.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
@@ -16,13 +16,23 @@ __export(exports_ast, {
16
16
  unaryOp: () => unaryOp,
17
17
  subtract: () => subtract,
18
18
  number: () => number,
19
+ nullishAssign: () => nullishAssign,
20
+ notEquals: () => notEquals,
19
21
  negate: () => negate,
20
22
  multiply: () => multiply,
21
23
  modulo: () => modulo,
24
+ logicalOr: () => logicalOr,
25
+ logicalAnd: () => logicalAnd,
26
+ lessThan: () => lessThan,
27
+ lessEqual: () => lessEqual,
22
28
  identifier: () => identifier,
29
+ greaterThan: () => greaterThan,
30
+ greaterEqual: () => greaterEqual,
23
31
  functionCall: () => functionCall,
24
32
  exponentiate: () => exponentiate,
33
+ equals: () => equals,
25
34
  divide: () => divide,
35
+ conditional: () => conditional,
26
36
  binaryOp: () => binaryOp,
27
37
  assign: () => assign,
28
38
  add: () => add
@@ -68,6 +78,21 @@ function assign(name, value) {
68
78
  value
69
79
  };
70
80
  }
81
+ function nullishAssign(name, value) {
82
+ return {
83
+ type: "NullishAssignment",
84
+ name,
85
+ value
86
+ };
87
+ }
88
+ function conditional(condition, consequent, alternate) {
89
+ return {
90
+ type: "ConditionalExpression",
91
+ condition,
92
+ consequent,
93
+ alternate
94
+ };
95
+ }
71
96
  function add(left, right) {
72
97
  return binaryOp(left, "+", right);
73
98
  }
@@ -89,6 +114,30 @@ function exponentiate(left, right) {
89
114
  function negate(argument) {
90
115
  return unaryOp(argument);
91
116
  }
117
+ function equals(left, right) {
118
+ return binaryOp(left, "==", right);
119
+ }
120
+ function notEquals(left, right) {
121
+ return binaryOp(left, "!=", right);
122
+ }
123
+ function lessThan(left, right) {
124
+ return binaryOp(left, "<", right);
125
+ }
126
+ function greaterThan(left, right) {
127
+ return binaryOp(left, ">", right);
128
+ }
129
+ function lessEqual(left, right) {
130
+ return binaryOp(left, "<=", right);
131
+ }
132
+ function greaterEqual(left, right) {
133
+ return binaryOp(left, ">=", right);
134
+ }
135
+ function logicalAnd(left, right) {
136
+ return binaryOp(left, "&&", right);
137
+ }
138
+ function logicalOr(left, right) {
139
+ return binaryOp(left, "||", right);
140
+ }
92
141
  // src/types.ts
93
142
  var TokenType;
94
143
  ((TokenType2) => {
@@ -100,10 +149,21 @@ var TokenType;
100
149
  TokenType2["SLASH"] = "SLASH";
101
150
  TokenType2["PERCENT"] = "PERCENT";
102
151
  TokenType2["CARET"] = "CARET";
152
+ TokenType2["DOUBLE_EQUALS"] = "DOUBLE_EQUALS";
153
+ TokenType2["NOT_EQUALS"] = "NOT_EQUALS";
154
+ TokenType2["LESS_THAN"] = "LESS_THAN";
155
+ TokenType2["GREATER_THAN"] = "GREATER_THAN";
156
+ TokenType2["LESS_EQUAL"] = "LESS_EQUAL";
157
+ TokenType2["GREATER_EQUAL"] = "GREATER_EQUAL";
158
+ TokenType2["LOGICAL_AND"] = "LOGICAL_AND";
159
+ TokenType2["LOGICAL_OR"] = "LOGICAL_OR";
103
160
  TokenType2["LPAREN"] = "LPAREN";
104
161
  TokenType2["RPAREN"] = "RPAREN";
105
162
  TokenType2["EQUALS"] = "EQUALS";
106
163
  TokenType2["COMMA"] = "COMMA";
164
+ TokenType2["QUESTION"] = "QUESTION";
165
+ TokenType2["COLON"] = "COLON";
166
+ TokenType2["NULLISH_ASSIGN"] = "NULLISH_ASSIGN";
107
167
  TokenType2["EOF"] = "EOF";
108
168
  })(TokenType ||= {});
109
169
  function isNumberLiteral(node) {
@@ -127,6 +187,12 @@ function isAssignment(node) {
127
187
  function isProgram(node) {
128
188
  return node.type === "Program";
129
189
  }
190
+ function isConditionalExpression(node) {
191
+ return node.type === "ConditionalExpression";
192
+ }
193
+ function isNullishAssignment(node) {
194
+ return node.type === "NullishAssignment";
195
+ }
130
196
 
131
197
  // src/codegen.ts
132
198
  class CodeGenerator {
@@ -145,6 +211,10 @@ class CodeGenerator {
145
211
  return this.generateFunctionCall(node);
146
212
  if (isAssignment(node))
147
213
  return this.generateAssignment(node);
214
+ if (isNullishAssignment(node))
215
+ return this.generateNullishAssignment(node);
216
+ if (isConditionalExpression(node))
217
+ return this.generateConditionalExpression(node);
148
218
  throw new Error(`Unknown node type`);
149
219
  }
150
220
  generateProgram(node) {
@@ -179,6 +249,18 @@ class CodeGenerator {
179
249
  const value = this.generate(node.value);
180
250
  return `${node.name} = ${value}`;
181
251
  }
252
+ generateNullishAssignment(node) {
253
+ const value = this.generate(node.value);
254
+ return `${node.name} ??= ${value}`;
255
+ }
256
+ generateConditionalExpression(node) {
257
+ const condition = this.generate(node.condition);
258
+ const consequent = this.generate(node.consequent);
259
+ const alternate = this.generate(node.alternate);
260
+ const conditionNeedsParens = isAssignment(node.condition) || isBinaryOp(node.condition) && this.getPrecedence(node.condition.operator) <= 2;
261
+ const conditionCode = conditionNeedsParens ? `(${condition})` : condition;
262
+ return `${conditionCode} ? ${consequent} : ${alternate}`;
263
+ }
182
264
  needsParensLeft(node, operator) {
183
265
  if (!isBinaryOp(node))
184
266
  return false;
@@ -202,14 +284,25 @@ class CodeGenerator {
202
284
  getPrecedence(operator) {
203
285
  switch (operator) {
204
286
  case "^":
205
- return 4;
287
+ return 8;
206
288
  case "*":
207
289
  case "/":
208
290
  case "%":
209
- return 3;
291
+ return 7;
210
292
  case "+":
211
293
  case "-":
212
- return 2;
294
+ return 6;
295
+ case "==":
296
+ case "!=":
297
+ case "<":
298
+ case ">":
299
+ case "<=":
300
+ case ">=":
301
+ return 5;
302
+ case "&&":
303
+ return 4;
304
+ case "||":
305
+ return 3;
213
306
  default:
214
307
  return 0;
215
308
  }
@@ -309,14 +402,64 @@ class Lexer {
309
402
  this.position++;
310
403
  return { type: "RPAREN" /* RPAREN */, value: ")", position: start };
311
404
  case "=":
405
+ if (this.peek() === "=") {
406
+ this.position += 2;
407
+ return { type: "DOUBLE_EQUALS" /* DOUBLE_EQUALS */, value: "==", position: start };
408
+ }
312
409
  this.position++;
313
410
  return { type: "EQUALS" /* EQUALS */, value: "=", position: start };
411
+ case "!":
412
+ if (this.peek() === "=") {
413
+ this.position += 2;
414
+ return { type: "NOT_EQUALS" /* NOT_EQUALS */, value: "!=", position: start };
415
+ }
416
+ throw new Error(`Unexpected character '${char}' at position ${start}`);
417
+ case "<":
418
+ if (this.peek() === "=") {
419
+ this.position += 2;
420
+ return { type: "LESS_EQUAL" /* LESS_EQUAL */, value: "<=", position: start };
421
+ }
422
+ this.position++;
423
+ return { type: "LESS_THAN" /* LESS_THAN */, value: "<", position: start };
424
+ case ">":
425
+ if (this.peek() === "=") {
426
+ this.position += 2;
427
+ return { type: "GREATER_EQUAL" /* GREATER_EQUAL */, value: ">=", position: start };
428
+ }
429
+ this.position++;
430
+ return { type: "GREATER_THAN" /* GREATER_THAN */, value: ">", position: start };
431
+ case "?":
432
+ if (this.peek() === "?" && this.peekAhead(2) === "=") {
433
+ this.position += 3;
434
+ return {
435
+ type: "NULLISH_ASSIGN" /* NULLISH_ASSIGN */,
436
+ value: "??=",
437
+ position: start
438
+ };
439
+ }
440
+ this.position++;
441
+ return { type: "QUESTION" /* QUESTION */, value: "?", position: start };
442
+ case ":":
443
+ this.position++;
444
+ return { type: "COLON" /* COLON */, value: ":", position: start };
314
445
  case ",":
315
446
  this.position++;
316
447
  return { type: "COMMA" /* COMMA */, value: ",", position: start };
317
448
  case ";":
318
449
  this.position++;
319
450
  return this.nextToken();
451
+ case "&":
452
+ if (this.peek() === "&") {
453
+ this.position += 2;
454
+ return { type: "LOGICAL_AND" /* LOGICAL_AND */, value: "&&", position: start };
455
+ }
456
+ throw new Error(`Unexpected character '${char}' at position ${start}`);
457
+ case "|":
458
+ if (this.peek() === "|") {
459
+ this.position += 2;
460
+ return { type: "LOGICAL_OR" /* LOGICAL_OR */, value: "||", position: start };
461
+ }
462
+ throw new Error(`Unexpected character '${char}' at position ${start}`);
320
463
  default:
321
464
  throw new Error(`Unexpected character '${char}' at position ${start}`);
322
465
  }
@@ -389,6 +532,9 @@ class Lexer {
389
532
  peek() {
390
533
  return this.getCharAt(this.position + 1);
391
534
  }
535
+ peekAhead(n) {
536
+ return this.getCharAt(this.position + n);
537
+ }
392
538
  isDigit(char) {
393
539
  return char >= "0" && char <= "9";
394
540
  }
@@ -442,12 +588,38 @@ class Parser {
442
588
  }
443
589
  const identName = left.name;
444
590
  this.advance();
445
- const value = this.parseExpression(precedence + 1);
591
+ const value = this.parseExpression(precedence);
446
592
  left = {
447
593
  type: "Assignment",
448
594
  name: identName,
449
595
  value
450
596
  };
597
+ } else if (token.type === "NULLISH_ASSIGN" /* NULLISH_ASSIGN */) {
598
+ if (left.type !== "Identifier") {
599
+ throw new Error("Invalid assignment target");
600
+ }
601
+ const identName = left.name;
602
+ this.advance();
603
+ const value = this.parseExpression(precedence);
604
+ left = {
605
+ type: "NullishAssignment",
606
+ name: identName,
607
+ value
608
+ };
609
+ } else if (token.type === "QUESTION" /* QUESTION */) {
610
+ this.advance();
611
+ const consequent = this.parseExpression(0);
612
+ if (this.peek().type !== "COLON" /* COLON */) {
613
+ throw new Error("Expected : in ternary expression");
614
+ }
615
+ this.advance();
616
+ const alternate = this.parseExpression(precedence);
617
+ left = {
618
+ type: "ConditionalExpression",
619
+ condition: left,
620
+ consequent,
621
+ alternate
622
+ };
451
623
  } else if (this.isBinaryOperator(token.type)) {
452
624
  const operator = token.value;
453
625
  this.advance();
@@ -529,25 +701,39 @@ class Parser {
529
701
  getPrecedence(type) {
530
702
  switch (type) {
531
703
  case "EQUALS" /* EQUALS */:
704
+ case "NULLISH_ASSIGN" /* NULLISH_ASSIGN */:
532
705
  return 1;
706
+ case "QUESTION" /* QUESTION */:
707
+ return 2;
708
+ case "LOGICAL_OR" /* LOGICAL_OR */:
709
+ return 3;
710
+ case "LOGICAL_AND" /* LOGICAL_AND */:
711
+ return 4;
712
+ case "DOUBLE_EQUALS" /* DOUBLE_EQUALS */:
713
+ case "NOT_EQUALS" /* NOT_EQUALS */:
714
+ case "LESS_THAN" /* LESS_THAN */:
715
+ case "GREATER_THAN" /* GREATER_THAN */:
716
+ case "LESS_EQUAL" /* LESS_EQUAL */:
717
+ case "GREATER_EQUAL" /* GREATER_EQUAL */:
718
+ return 5;
533
719
  case "PLUS" /* PLUS */:
534
720
  case "MINUS" /* MINUS */:
535
- return 2;
721
+ return 6;
536
722
  case "STAR" /* STAR */:
537
723
  case "SLASH" /* SLASH */:
538
724
  case "PERCENT" /* PERCENT */:
539
- return 3;
725
+ return 7;
540
726
  case "CARET" /* CARET */:
541
- return 4;
727
+ return 8;
542
728
  default:
543
729
  return 0;
544
730
  }
545
731
  }
546
732
  getUnaryPrecedence() {
547
- return 5;
733
+ return 6;
548
734
  }
549
735
  isBinaryOperator(type) {
550
- return type === "PLUS" /* PLUS */ || type === "MINUS" /* MINUS */ || type === "STAR" /* STAR */ || type === "SLASH" /* SLASH */ || type === "PERCENT" /* PERCENT */ || type === "CARET" /* CARET */;
736
+ return type === "PLUS" /* PLUS */ || type === "MINUS" /* MINUS */ || type === "STAR" /* STAR */ || type === "SLASH" /* SLASH */ || type === "PERCENT" /* PERCENT */ || type === "CARET" /* CARET */ || type === "DOUBLE_EQUALS" /* DOUBLE_EQUALS */ || type === "NOT_EQUALS" /* NOT_EQUALS */ || type === "LESS_THAN" /* LESS_THAN */ || type === "GREATER_THAN" /* GREATER_THAN */ || type === "LESS_EQUAL" /* LESS_EQUAL */ || type === "GREATER_EQUAL" /* GREATER_EQUAL */ || type === "LOGICAL_AND" /* LOGICAL_AND */ || type === "LOGICAL_OR" /* LOGICAL_OR */;
551
737
  }
552
738
  peek() {
553
739
  if (this.current >= this.tokens.length) {
@@ -593,6 +779,10 @@ class Executor {
593
779
  return this.executeFunctionCall(node);
594
780
  if (isAssignment(node))
595
781
  return this.executeAssignment(node);
782
+ if (isNullishAssignment(node))
783
+ return this.executeNullishAssignment(node);
784
+ if (isConditionalExpression(node))
785
+ return this.executeConditionalExpression(node);
596
786
  throw new Error(`Unknown node type`);
597
787
  }
598
788
  executeProgram(node) {
@@ -634,6 +824,22 @@ class Executor {
634
824
  return left % right;
635
825
  case "^":
636
826
  return left ** right;
827
+ case "==":
828
+ return left === right ? 1 : 0;
829
+ case "!=":
830
+ return left !== right ? 1 : 0;
831
+ case "<":
832
+ return left < right ? 1 : 0;
833
+ case ">":
834
+ return left > right ? 1 : 0;
835
+ case "<=":
836
+ return left <= right ? 1 : 0;
837
+ case ">=":
838
+ return left >= right ? 1 : 0;
839
+ case "&&":
840
+ return left !== 0 && right !== 0 ? 1 : 0;
841
+ case "||":
842
+ return left !== 0 || right !== 0 ? 1 : 0;
637
843
  default:
638
844
  throw new Error(`Unknown operator: ${node.operator}`);
639
845
  }
@@ -661,6 +867,18 @@ class Executor {
661
867
  this.variables.set(node.name, value);
662
868
  return value;
663
869
  }
870
+ executeNullishAssignment(node) {
871
+ if (this.variables.has(node.name)) {
872
+ return this.variables.get(node.name);
873
+ }
874
+ const value = this.execute(node.value);
875
+ this.variables.set(node.name, value);
876
+ return value;
877
+ }
878
+ executeConditionalExpression(node) {
879
+ const condition = this.execute(node.condition);
880
+ return condition !== 0 ? this.execute(node.consequent) : this.execute(node.alternate);
881
+ }
664
882
  }
665
883
  function execute(source, context) {
666
884
  const ast = parseSource(source);
@@ -669,21 +887,371 @@ function execute(source, context) {
669
887
  }
670
888
  // src/optimizer.ts
671
889
  function optimize(node) {
890
+ if (!isProgram(node)) {
891
+ return basicOptimize(node);
892
+ }
893
+ const analysis = analyzeProgram(node);
894
+ const { propagated, allConstants } = propagateConstantsOptimal(node, analysis);
895
+ const optimized = eliminateDeadCodeOptimal(propagated, analysis, allConstants);
896
+ return optimized;
897
+ }
898
+ function analyzeProgram(node) {
899
+ if (!isProgram(node)) {
900
+ return {
901
+ constants: new Map,
902
+ tainted: new Set,
903
+ dependencies: new Map,
904
+ liveVariables: new Set,
905
+ assignmentIndices: new Map,
906
+ evaluationOrder: []
907
+ };
908
+ }
909
+ const constants = new Map;
910
+ const tainted = new Set;
911
+ const dependencies = new Map;
912
+ const assignmentIndices = new Map;
913
+ const assignmentCounts = new Map;
914
+ for (let i = 0;i < node.statements.length; i++) {
915
+ const stmt = node.statements[i];
916
+ if (!stmt)
917
+ continue;
918
+ if (isAssignment(stmt) || isNullishAssignment(stmt)) {
919
+ const varName = stmt.name;
920
+ const count = assignmentCounts.get(varName) || 0;
921
+ assignmentCounts.set(varName, count + 1);
922
+ assignmentIndices.set(varName, i);
923
+ const deps = new Set;
924
+ const hasFunctionCall = collectDependencies(stmt.value, deps);
925
+ dependencies.set(varName, deps);
926
+ if (count === 0 && isNumberLiteral(stmt.value)) {
927
+ constants.set(varName, stmt.value.value);
928
+ }
929
+ if (hasFunctionCall) {
930
+ tainted.add(varName);
931
+ }
932
+ }
933
+ }
934
+ for (const [varName, count] of assignmentCounts) {
935
+ if (count > 1) {
936
+ constants.delete(varName);
937
+ tainted.add(varName);
938
+ }
939
+ }
940
+ let taintChanged = true;
941
+ while (taintChanged) {
942
+ taintChanged = false;
943
+ for (const [varName, deps] of dependencies) {
944
+ if (tainted.has(varName))
945
+ continue;
946
+ for (const dep of deps) {
947
+ if (tainted.has(dep)) {
948
+ tainted.add(varName);
949
+ constants.delete(varName);
950
+ taintChanged = true;
951
+ break;
952
+ }
953
+ }
954
+ }
955
+ }
956
+ const liveVariables = new Set;
957
+ const lastStmt = node.statements[node.statements.length - 1];
958
+ if (lastStmt) {
959
+ if (isAssignment(lastStmt)) {
960
+ const deps = new Set;
961
+ collectDependencies(lastStmt.value, deps);
962
+ for (const dep of deps) {
963
+ liveVariables.add(dep);
964
+ }
965
+ } else {
966
+ const deps = new Set;
967
+ collectDependencies(lastStmt, deps);
968
+ for (const dep of deps) {
969
+ liveVariables.add(dep);
970
+ }
971
+ }
972
+ }
973
+ let liveChanged = true;
974
+ while (liveChanged) {
975
+ liveChanged = false;
976
+ for (const [varName, deps] of dependencies) {
977
+ if (liveVariables.has(varName)) {
978
+ for (const dep of deps) {
979
+ if (!liveVariables.has(dep)) {
980
+ liveVariables.add(dep);
981
+ liveChanged = true;
982
+ }
983
+ }
984
+ }
985
+ }
986
+ }
987
+ const evaluationOrder = topologicalSort(dependencies, liveVariables);
988
+ return {
989
+ constants,
990
+ tainted,
991
+ dependencies,
992
+ liveVariables,
993
+ assignmentIndices,
994
+ evaluationOrder
995
+ };
996
+ }
997
+ function collectDependencies(node, deps) {
998
+ if (isIdentifier(node)) {
999
+ deps.add(node.name);
1000
+ return false;
1001
+ }
1002
+ if (isNumberLiteral(node)) {
1003
+ return false;
1004
+ }
1005
+ if (isAssignment(node) || isNullishAssignment(node)) {
1006
+ return collectDependencies(node.value, deps);
1007
+ }
1008
+ if (isBinaryOp(node)) {
1009
+ const leftHasCall = collectDependencies(node.left, deps);
1010
+ const rightHasCall = collectDependencies(node.right, deps);
1011
+ return leftHasCall || rightHasCall;
1012
+ }
1013
+ if (isUnaryOp(node)) {
1014
+ return collectDependencies(node.argument, deps);
1015
+ }
1016
+ if (isFunctionCall(node)) {
1017
+ for (const arg of node.arguments) {
1018
+ collectDependencies(arg, deps);
1019
+ }
1020
+ return true;
1021
+ }
1022
+ if (isConditionalExpression(node)) {
1023
+ const condHasCall = collectDependencies(node.condition, deps);
1024
+ const consHasCall = collectDependencies(node.consequent, deps);
1025
+ const altHasCall = collectDependencies(node.alternate, deps);
1026
+ return condHasCall || consHasCall || altHasCall;
1027
+ }
672
1028
  if (isProgram(node)) {
1029
+ let hasCall = false;
1030
+ for (const stmt of node.statements) {
1031
+ hasCall = collectDependencies(stmt, deps) || hasCall;
1032
+ }
1033
+ return hasCall;
1034
+ }
1035
+ return false;
1036
+ }
1037
+ function topologicalSort(dependencies, liveVariables) {
1038
+ const result = [];
1039
+ const visited = new Set;
1040
+ const visiting = new Set;
1041
+ function visit(varName) {
1042
+ if (visited.has(varName))
1043
+ return;
1044
+ if (visiting.has(varName)) {
1045
+ return;
1046
+ }
1047
+ visiting.add(varName);
1048
+ const deps = dependencies.get(varName);
1049
+ if (deps) {
1050
+ for (const dep of deps) {
1051
+ if (liveVariables.has(dep)) {
1052
+ visit(dep);
1053
+ }
1054
+ }
1055
+ }
1056
+ visiting.delete(varName);
1057
+ visited.add(varName);
1058
+ result.push(varName);
1059
+ }
1060
+ for (const varName of liveVariables) {
1061
+ visit(varName);
1062
+ }
1063
+ return result;
1064
+ }
1065
+ function propagateConstantsOptimal(node, analysis) {
1066
+ if (!isProgram(node)) {
1067
+ return { propagated: node, allConstants: new Map };
1068
+ }
1069
+ const allConstants = new Map(analysis.constants);
1070
+ for (const varName of analysis.evaluationOrder) {
1071
+ if (allConstants.has(varName))
1072
+ continue;
1073
+ if (analysis.tainted.has(varName))
1074
+ continue;
1075
+ const deps = analysis.dependencies.get(varName);
1076
+ if (!deps)
1077
+ continue;
1078
+ let allDepsConstant = true;
1079
+ for (const dep of deps) {
1080
+ if (!allConstants.has(dep)) {
1081
+ allDepsConstant = false;
1082
+ break;
1083
+ }
1084
+ }
1085
+ if (allDepsConstant) {
1086
+ const assignmentIdx = analysis.assignmentIndices.get(varName);
1087
+ if (assignmentIdx !== undefined) {
1088
+ const stmt = node.statements[assignmentIdx];
1089
+ if (stmt && isAssignment(stmt)) {
1090
+ const evaluated = evaluateWithConstants(stmt.value, allConstants);
1091
+ if (isNumberLiteral(evaluated)) {
1092
+ allConstants.set(varName, evaluated.value);
1093
+ }
1094
+ }
1095
+ }
1096
+ }
1097
+ }
1098
+ const statements = node.statements.map((stmt) => replaceWithConstants(stmt, allConstants));
1099
+ return {
1100
+ propagated: {
1101
+ type: "Program",
1102
+ statements
1103
+ },
1104
+ allConstants
1105
+ };
1106
+ }
1107
+ function evaluateWithConstants(node, constants) {
1108
+ if (isIdentifier(node)) {
1109
+ const value = constants.get(node.name);
1110
+ if (value !== undefined) {
1111
+ return number(value);
1112
+ }
1113
+ return node;
1114
+ }
1115
+ if (isNumberLiteral(node)) {
1116
+ return node;
1117
+ }
1118
+ if (isBinaryOp(node)) {
1119
+ const left = evaluateWithConstants(node.left, constants);
1120
+ const right = evaluateWithConstants(node.right, constants);
1121
+ if (isNumberLiteral(left) && isNumberLiteral(right)) {
1122
+ const result = evaluateBinaryOp(node.operator, left.value, right.value);
1123
+ return number(result);
1124
+ }
1125
+ return {
1126
+ ...node,
1127
+ left,
1128
+ right
1129
+ };
1130
+ }
1131
+ if (isUnaryOp(node)) {
1132
+ const argument = evaluateWithConstants(node.argument, constants);
1133
+ if (isNumberLiteral(argument)) {
1134
+ return number(-argument.value);
1135
+ }
1136
+ return {
1137
+ ...node,
1138
+ argument
1139
+ };
1140
+ }
1141
+ if (isFunctionCall(node)) {
673
1142
  return {
674
1143
  ...node,
675
- statements: node.statements.map((stmt) => optimize(stmt))
1144
+ arguments: node.arguments.map((arg) => evaluateWithConstants(arg, constants))
676
1145
  };
677
1146
  }
1147
+ if (isConditionalExpression(node)) {
1148
+ const condition = evaluateWithConstants(node.condition, constants);
1149
+ const consequent = evaluateWithConstants(node.consequent, constants);
1150
+ const alternate = evaluateWithConstants(node.alternate, constants);
1151
+ if (isNumberLiteral(condition)) {
1152
+ return condition.value !== 0 ? consequent : alternate;
1153
+ }
1154
+ return {
1155
+ ...node,
1156
+ condition,
1157
+ consequent,
1158
+ alternate
1159
+ };
1160
+ }
1161
+ if (isAssignment(node)) {
1162
+ return {
1163
+ ...node,
1164
+ value: evaluateWithConstants(node.value, constants)
1165
+ };
1166
+ }
1167
+ if (isNullishAssignment(node)) {
1168
+ return {
1169
+ ...node,
1170
+ value: evaluateWithConstants(node.value, constants)
1171
+ };
1172
+ }
1173
+ return node;
1174
+ }
1175
+ function replaceWithConstants(node, constants) {
1176
+ return evaluateWithConstants(node, constants);
1177
+ }
1178
+ function eliminateDeadCodeOptimal(node, analysis, allConstants) {
1179
+ if (!isProgram(node))
1180
+ return node;
1181
+ const lastStmt = node.statements[node.statements.length - 1];
1182
+ if (lastStmt) {
1183
+ const evaluated = evaluateWithConstants(lastStmt, allConstants);
1184
+ if (isNumberLiteral(evaluated)) {
1185
+ return evaluated;
1186
+ }
1187
+ }
1188
+ const filteredStatements = [];
1189
+ const variablesInUse = new Set;
1190
+ for (const stmt of node.statements) {
1191
+ collectDependencies(stmt, variablesInUse);
1192
+ }
1193
+ for (let i = 0;i < node.statements.length; i++) {
1194
+ const stmt = node.statements[i];
1195
+ if (!stmt)
1196
+ continue;
1197
+ if (i === node.statements.length - 1) {
1198
+ filteredStatements.push(stmt);
1199
+ continue;
1200
+ }
1201
+ if (isAssignment(stmt) || isNullishAssignment(stmt)) {
1202
+ if (variablesInUse.has(stmt.name)) {
1203
+ filteredStatements.push(stmt);
1204
+ }
1205
+ } else {
1206
+ filteredStatements.push(stmt);
1207
+ }
1208
+ }
1209
+ if (filteredStatements.length === 1) {
1210
+ const singleStmt = filteredStatements[0];
1211
+ if (!singleStmt) {
1212
+ return node;
1213
+ }
1214
+ if (isNumberLiteral(singleStmt)) {
1215
+ return singleStmt;
1216
+ }
1217
+ if (isAssignment(singleStmt) && isNumberLiteral(singleStmt.value)) {
1218
+ if (!analysis.liveVariables.has(singleStmt.name)) {
1219
+ return singleStmt.value;
1220
+ }
1221
+ }
1222
+ const evaluated = evaluateWithConstants(singleStmt, allConstants);
1223
+ if (isNumberLiteral(evaluated)) {
1224
+ return evaluated;
1225
+ }
1226
+ }
1227
+ if (filteredStatements.length === 0) {
1228
+ const lastStmt2 = node.statements[node.statements.length - 1];
1229
+ if (lastStmt2 && isAssignment(lastStmt2) && isNumberLiteral(lastStmt2.value)) {
1230
+ return lastStmt2.value;
1231
+ }
1232
+ return node;
1233
+ }
1234
+ return {
1235
+ type: "Program",
1236
+ statements: filteredStatements
1237
+ };
1238
+ }
1239
+ function basicOptimize(node) {
678
1240
  if (isAssignment(node)) {
679
1241
  return {
680
1242
  ...node,
681
- value: optimize(node.value)
1243
+ value: basicOptimize(node.value)
1244
+ };
1245
+ }
1246
+ if (isNullishAssignment(node)) {
1247
+ return {
1248
+ ...node,
1249
+ value: basicOptimize(node.value)
682
1250
  };
683
1251
  }
684
1252
  if (isBinaryOp(node)) {
685
- const left = optimize(node.left);
686
- const right = optimize(node.right);
1253
+ const left = basicOptimize(node.left);
1254
+ const right = basicOptimize(node.right);
687
1255
  if (isNumberLiteral(left) && isNumberLiteral(right)) {
688
1256
  const result = evaluateBinaryOp(node.operator, left.value, right.value);
689
1257
  return number(result);
@@ -695,7 +1263,7 @@ function optimize(node) {
695
1263
  };
696
1264
  }
697
1265
  if (isUnaryOp(node)) {
698
- const argument = optimize(node.argument);
1266
+ const argument = basicOptimize(node.argument);
699
1267
  if (isNumberLiteral(argument)) {
700
1268
  return number(-argument.value);
701
1269
  }
@@ -707,7 +1275,27 @@ function optimize(node) {
707
1275
  if (isFunctionCall(node)) {
708
1276
  return {
709
1277
  ...node,
710
- arguments: node.arguments.map((arg) => optimize(arg))
1278
+ arguments: node.arguments.map((arg) => basicOptimize(arg))
1279
+ };
1280
+ }
1281
+ if (isConditionalExpression(node)) {
1282
+ const condition = basicOptimize(node.condition);
1283
+ const consequent = basicOptimize(node.consequent);
1284
+ const alternate = basicOptimize(node.alternate);
1285
+ if (isNumberLiteral(condition)) {
1286
+ return condition.value !== 0 ? consequent : alternate;
1287
+ }
1288
+ return {
1289
+ ...node,
1290
+ condition,
1291
+ consequent,
1292
+ alternate
1293
+ };
1294
+ }
1295
+ if (isProgram(node)) {
1296
+ return {
1297
+ ...node,
1298
+ statements: node.statements.map((stmt) => basicOptimize(stmt))
711
1299
  };
712
1300
  }
713
1301
  return node;
@@ -732,6 +1320,22 @@ function evaluateBinaryOp(operator, left, right) {
732
1320
  return left % right;
733
1321
  case "^":
734
1322
  return left ** right;
1323
+ case "==":
1324
+ return left === right ? 1 : 0;
1325
+ case "!=":
1326
+ return left !== right ? 1 : 0;
1327
+ case "<":
1328
+ return left < right ? 1 : 0;
1329
+ case ">":
1330
+ return left > right ? 1 : 0;
1331
+ case "<=":
1332
+ return left <= right ? 1 : 0;
1333
+ case ">=":
1334
+ return left >= right ? 1 : 0;
1335
+ case "&&":
1336
+ return left !== 0 && right !== 0 ? 1 : 0;
1337
+ case "||":
1338
+ return left !== 0 || right !== 0 ? 1 : 0;
735
1339
  default:
736
1340
  throw new Error(`Unknown operator: ${operator}`);
737
1341
  }
@@ -742,8 +1346,10 @@ export {
742
1346
  isUnaryOp,
743
1347
  isProgram,
744
1348
  isNumberLiteral,
1349
+ isNullishAssignment,
745
1350
  isIdentifier,
746
1351
  isFunctionCall,
1352
+ isConditionalExpression,
747
1353
  isBinaryOp,
748
1354
  isAssignment,
749
1355
  generate,