littlewing 0.9.4 → 0.9.5

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.d.ts CHANGED
@@ -652,16 +652,16 @@ declare class Lexer {
652
652
  private isWhitespace;
653
653
  }
654
654
  /**
655
- * Optimize an AST using constant folding and expression simplification.
655
+ * Optimize an AST using constant folding, expression simplification, and dead code elimination.
656
656
  *
657
- * This optimizer performs LOCAL, SAFE optimizations that preserve program semantics:
657
+ * This optimizer performs SAFE optimizations that preserve program semantics:
658
658
  * - Constant folding: Evaluates arithmetic with literal operands at compile-time
659
659
  * - Function argument pre-evaluation: Simplifies expressions passed to functions
660
660
  * - Conditional folding: Evaluates ternary with constant condition
661
+ * - Dead code elimination: Removes unused variable assignments
661
662
  *
662
663
  * Optimizations that are NOT performed (because they're unsafe with context variables):
663
664
  * - Variable propagation: Variables can be overridden by ExecutionContext
664
- * - Dead code elimination: Cannot determine if variables are "dead" without knowing context
665
665
  * - Cross-statement analysis: Each statement may affect external state
666
666
  *
667
667
  * Time complexity: O(n) where n is the number of AST nodes
@@ -669,7 +669,7 @@ declare class Lexer {
669
669
  *
670
670
  * Algorithm properties:
671
671
  * - Sound: Preserves program semantics exactly
672
- * - Safe: No assumptions about variable values or liveness
672
+ * - Safe: No assumptions about variable values
673
673
  * - Local: Only optimizes within individual expressions
674
674
  *
675
675
  * @param node - The AST node to optimize
package/dist/index.js CHANGED
@@ -93,6 +93,139 @@ function visitPartial(node, visitor, defaultHandler) {
93
93
  return defaultHandler(node, recurse);
94
94
  }
95
95
 
96
+ // src/utils.ts
97
+ function evaluateBinaryOperation(operator, left, right) {
98
+ switch (operator) {
99
+ case "+":
100
+ return left + right;
101
+ case "-":
102
+ return left - right;
103
+ case "*":
104
+ return left * right;
105
+ case "/":
106
+ if (right === 0) {
107
+ throw new Error("Division by zero");
108
+ }
109
+ return left / right;
110
+ case "%":
111
+ if (right === 0) {
112
+ throw new Error("Modulo by zero");
113
+ }
114
+ return left % right;
115
+ case "^":
116
+ return left ** right;
117
+ case "==":
118
+ return left === right ? 1 : 0;
119
+ case "!=":
120
+ return left !== right ? 1 : 0;
121
+ case "<":
122
+ return left < right ? 1 : 0;
123
+ case ">":
124
+ return left > right ? 1 : 0;
125
+ case "<=":
126
+ return left <= right ? 1 : 0;
127
+ case ">=":
128
+ return left >= right ? 1 : 0;
129
+ case "&&":
130
+ return left !== 0 && right !== 0 ? 1 : 0;
131
+ case "||":
132
+ return left !== 0 || right !== 0 ? 1 : 0;
133
+ default:
134
+ throw new Error(`Unknown operator: ${operator}`);
135
+ }
136
+ }
137
+ function getOperatorPrecedence(operator) {
138
+ switch (operator) {
139
+ case "^":
140
+ return 8;
141
+ case "*":
142
+ case "/":
143
+ case "%":
144
+ return 7;
145
+ case "+":
146
+ case "-":
147
+ return 6;
148
+ case "==":
149
+ case "!=":
150
+ case "<":
151
+ case ">":
152
+ case "<=":
153
+ case ">=":
154
+ return 5;
155
+ case "&&":
156
+ return 4;
157
+ case "||":
158
+ return 3;
159
+ default:
160
+ return 0;
161
+ }
162
+ }
163
+ function collectAllIdentifiers(node) {
164
+ const identifiers = new Set;
165
+ visit(node, {
166
+ Program: (n, recurse) => {
167
+ for (const stmt of n.statements) {
168
+ recurse(stmt);
169
+ }
170
+ },
171
+ NumberLiteral: () => {},
172
+ Identifier: (n) => {
173
+ identifiers.add(n.name);
174
+ },
175
+ BinaryOp: (n, recurse) => {
176
+ recurse(n.left);
177
+ recurse(n.right);
178
+ },
179
+ UnaryOp: (n, recurse) => {
180
+ recurse(n.argument);
181
+ },
182
+ FunctionCall: (n, recurse) => {
183
+ for (const arg of n.arguments) {
184
+ recurse(arg);
185
+ }
186
+ },
187
+ Assignment: (n, recurse) => {
188
+ recurse(n.value);
189
+ },
190
+ ConditionalExpression: (n, recurse) => {
191
+ recurse(n.condition);
192
+ recurse(n.consequent);
193
+ recurse(n.alternate);
194
+ }
195
+ });
196
+ return identifiers;
197
+ }
198
+ function getTokenPrecedence(type) {
199
+ switch (type) {
200
+ case "EQUALS" /* EQUALS */:
201
+ return 1;
202
+ case "QUESTION" /* QUESTION */:
203
+ return 2;
204
+ case "LOGICAL_OR" /* LOGICAL_OR */:
205
+ return 3;
206
+ case "LOGICAL_AND" /* LOGICAL_AND */:
207
+ return 4;
208
+ case "DOUBLE_EQUALS" /* DOUBLE_EQUALS */:
209
+ case "NOT_EQUALS" /* NOT_EQUALS */:
210
+ case "LESS_THAN" /* LESS_THAN */:
211
+ case "GREATER_THAN" /* GREATER_THAN */:
212
+ case "LESS_EQUAL" /* LESS_EQUAL */:
213
+ case "GREATER_EQUAL" /* GREATER_EQUAL */:
214
+ return 5;
215
+ case "PLUS" /* PLUS */:
216
+ case "MINUS" /* MINUS */:
217
+ return 6;
218
+ case "STAR" /* STAR */:
219
+ case "SLASH" /* SLASH */:
220
+ case "PERCENT" /* PERCENT */:
221
+ return 7;
222
+ case "CARET" /* CARET */:
223
+ return 8;
224
+ default:
225
+ return 0;
226
+ }
227
+ }
228
+
96
229
  // src/analyzer.ts
97
230
  function extractInputVariables(ast) {
98
231
  const inputVars = new Set;
@@ -107,55 +240,7 @@ function extractInputVariables(ast) {
107
240
  return Array.from(inputVars);
108
241
  }
109
242
  function containsVariableReference(node) {
110
- let hasIdentifier = false;
111
- visitPartial(node, {
112
- Identifier: () => {
113
- hasIdentifier = true;
114
- return;
115
- }
116
- }, (n, recurse) => {
117
- if (hasIdentifier) {
118
- return;
119
- }
120
- switch (n.type) {
121
- case "NumberLiteral":
122
- return;
123
- case "BinaryOp":
124
- recurse(n.left);
125
- recurse(n.right);
126
- return;
127
- case "UnaryOp":
128
- recurse(n.argument);
129
- return;
130
- case "FunctionCall":
131
- for (const arg of n.arguments) {
132
- recurse(arg);
133
- if (hasIdentifier)
134
- break;
135
- }
136
- return;
137
- case "ConditionalExpression":
138
- recurse(n.condition);
139
- if (!hasIdentifier)
140
- recurse(n.consequent);
141
- if (!hasIdentifier)
142
- recurse(n.alternate);
143
- return;
144
- case "Program":
145
- for (const stmt of n.statements) {
146
- recurse(stmt);
147
- if (hasIdentifier)
148
- break;
149
- }
150
- return;
151
- case "Assignment":
152
- recurse(n.value);
153
- return;
154
- default:
155
- return;
156
- }
157
- });
158
- return hasIdentifier;
243
+ return collectAllIdentifiers(node).size > 0;
159
244
  }
160
245
  // src/ast.ts
161
246
  var exports_ast = {};
@@ -288,104 +373,6 @@ function logicalAnd(left, right) {
288
373
  function logicalOr(left, right) {
289
374
  return binaryOp(left, "||", right);
290
375
  }
291
- // src/utils.ts
292
- function evaluateBinaryOperation(operator, left, right) {
293
- switch (operator) {
294
- case "+":
295
- return left + right;
296
- case "-":
297
- return left - right;
298
- case "*":
299
- return left * right;
300
- case "/":
301
- if (right === 0) {
302
- throw new Error("Division by zero");
303
- }
304
- return left / right;
305
- case "%":
306
- if (right === 0) {
307
- throw new Error("Modulo by zero");
308
- }
309
- return left % right;
310
- case "^":
311
- return left ** right;
312
- case "==":
313
- return left === right ? 1 : 0;
314
- case "!=":
315
- return left !== right ? 1 : 0;
316
- case "<":
317
- return left < right ? 1 : 0;
318
- case ">":
319
- return left > right ? 1 : 0;
320
- case "<=":
321
- return left <= right ? 1 : 0;
322
- case ">=":
323
- return left >= right ? 1 : 0;
324
- case "&&":
325
- return left !== 0 && right !== 0 ? 1 : 0;
326
- case "||":
327
- return left !== 0 || right !== 0 ? 1 : 0;
328
- default:
329
- throw new Error(`Unknown operator: ${operator}`);
330
- }
331
- }
332
- function getOperatorPrecedence(operator) {
333
- switch (operator) {
334
- case "^":
335
- return 8;
336
- case "*":
337
- case "/":
338
- case "%":
339
- return 7;
340
- case "+":
341
- case "-":
342
- return 6;
343
- case "==":
344
- case "!=":
345
- case "<":
346
- case ">":
347
- case "<=":
348
- case ">=":
349
- return 5;
350
- case "&&":
351
- return 4;
352
- case "||":
353
- return 3;
354
- default:
355
- return 0;
356
- }
357
- }
358
- function getTokenPrecedence(type) {
359
- switch (type) {
360
- case "EQUALS" /* EQUALS */:
361
- return 1;
362
- case "QUESTION" /* QUESTION */:
363
- return 2;
364
- case "LOGICAL_OR" /* LOGICAL_OR */:
365
- return 3;
366
- case "LOGICAL_AND" /* LOGICAL_AND */:
367
- return 4;
368
- case "DOUBLE_EQUALS" /* DOUBLE_EQUALS */:
369
- case "NOT_EQUALS" /* NOT_EQUALS */:
370
- case "LESS_THAN" /* LESS_THAN */:
371
- case "GREATER_THAN" /* GREATER_THAN */:
372
- case "LESS_EQUAL" /* LESS_EQUAL */:
373
- case "GREATER_EQUAL" /* GREATER_EQUAL */:
374
- return 5;
375
- case "PLUS" /* PLUS */:
376
- case "MINUS" /* MINUS */:
377
- return 6;
378
- case "STAR" /* STAR */:
379
- case "SLASH" /* SLASH */:
380
- case "PERCENT" /* PERCENT */:
381
- return 7;
382
- case "CARET" /* CARET */:
383
- return 8;
384
- default:
385
- return 0;
386
- }
387
- }
388
-
389
376
  // src/codegen.ts
390
377
  class CodeGenerator {
391
378
  generate(node) {
@@ -1228,8 +1215,42 @@ function humanize(node, options) {
1228
1215
  return humanizer.humanize(node);
1229
1216
  }
1230
1217
  // src/optimizer.ts
1218
+ function eliminateDeadCode(program2) {
1219
+ const statements = program2.statements;
1220
+ const liveVars = new Set;
1221
+ const keptStatements = [];
1222
+ for (let i = statements.length - 1;i >= 0; i--) {
1223
+ const stmt = statements[i];
1224
+ if (!stmt)
1225
+ continue;
1226
+ if (i === statements.length - 1) {
1227
+ keptStatements.push(stmt);
1228
+ const identifiers = collectAllIdentifiers(stmt);
1229
+ for (const id of identifiers) {
1230
+ liveVars.add(id);
1231
+ }
1232
+ continue;
1233
+ }
1234
+ if (stmt.type === "Assignment") {
1235
+ if (liveVars.has(stmt.name)) {
1236
+ keptStatements.push(stmt);
1237
+ const identifiers = collectAllIdentifiers(stmt.value);
1238
+ for (const id of identifiers) {
1239
+ liveVars.add(id);
1240
+ }
1241
+ }
1242
+ } else {
1243
+ keptStatements.push(stmt);
1244
+ const identifiers = collectAllIdentifiers(stmt);
1245
+ for (const id of identifiers) {
1246
+ liveVars.add(id);
1247
+ }
1248
+ }
1249
+ }
1250
+ return program(keptStatements.reverse());
1251
+ }
1231
1252
  function optimize(node) {
1232
- return visit(node, {
1253
+ const folded = visit(node, {
1233
1254
  NumberLiteral: (n) => n,
1234
1255
  Identifier: (n) => n,
1235
1256
  BinaryOp: (n, recurse) => {
@@ -1274,6 +1295,10 @@ function optimize(node) {
1274
1295
  return program(optimizedStatements);
1275
1296
  }
1276
1297
  });
1298
+ if (isProgram(folded) && folded.statements.length > 0) {
1299
+ return eliminateDeadCode(folded);
1300
+ }
1301
+ return folded;
1277
1302
  }
1278
1303
  export {
1279
1304
  visitPartial,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "littlewing",
3
- "version": "0.9.4",
3
+ "version": "0.9.5",
4
4
  "description": "A minimal, high-performance arithmetic expression language with lexer, parser, and executor. Optimized for browsers with zero dependencies and type-safe execution.",
5
5
  "keywords": [
6
6
  "arithmetic",