littlewing 0.5.0 → 0.5.2

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
@@ -306,11 +306,6 @@ declare class CodeGenerator {
306
306
  * - For left-associative operators: parens if lower or equal precedence
307
307
  */
308
308
  private needsParensRight;
309
- /**
310
- * Get precedence of an operator (higher number = higher precedence)
311
- * Must match the precedence in parser.ts
312
- */
313
- private getPrecedence;
314
309
  }
315
310
  /**
316
311
  * Generate source code from an AST node
@@ -498,21 +493,10 @@ declare class Parser {
498
493
  */
499
494
  private parseFunctionArguments;
500
495
  /**
501
- * Get operator precedence
502
- * Precedence hierarchy:
503
- * 0: None
504
- * 1: Assignment (=, ??=)
505
- * 2: Ternary conditional (? :)
506
- * 3: Logical OR (||)
507
- * 4: Logical AND (&&)
508
- * 5: Comparison (==, !=, <, >, <=, >=)
509
- * 6: Addition/Subtraction (+, -)
510
- * 7: Multiplication/Division/Modulo (*, /, %)
511
- * 8: Exponentiation (^)
512
- */
513
- private getPrecedence;
514
- /**
515
496
  * Get unary operator precedence
497
+ * Returns 6 which is higher than add/sub (6) but lower than exponentiation (8)
498
+ * This means: -2^2 parses as -(2^2) = -4, not (-2)^2 = 4
499
+ * This matches the behavior of Python, Ruby, and most languages
516
500
  */
517
501
  private getUnaryPrecedence;
518
502
  /**
package/dist/index.js CHANGED
@@ -194,6 +194,105 @@ function isNullishAssignment(node) {
194
194
  return node.type === "NullishAssignment";
195
195
  }
196
196
 
197
+ // src/utils.ts
198
+ function evaluateBinaryOperation(operator, left, right) {
199
+ switch (operator) {
200
+ case "+":
201
+ return left + right;
202
+ case "-":
203
+ return left - right;
204
+ case "*":
205
+ return left * right;
206
+ case "/":
207
+ if (right === 0) {
208
+ throw new Error("Division by zero");
209
+ }
210
+ return left / right;
211
+ case "%":
212
+ if (right === 0) {
213
+ throw new Error("Modulo by zero");
214
+ }
215
+ return left % right;
216
+ case "^":
217
+ return left ** right;
218
+ case "==":
219
+ return left === right ? 1 : 0;
220
+ case "!=":
221
+ return left !== right ? 1 : 0;
222
+ case "<":
223
+ return left < right ? 1 : 0;
224
+ case ">":
225
+ return left > right ? 1 : 0;
226
+ case "<=":
227
+ return left <= right ? 1 : 0;
228
+ case ">=":
229
+ return left >= right ? 1 : 0;
230
+ case "&&":
231
+ return left !== 0 && right !== 0 ? 1 : 0;
232
+ case "||":
233
+ return left !== 0 || right !== 0 ? 1 : 0;
234
+ default:
235
+ throw new Error(`Unknown operator: ${operator}`);
236
+ }
237
+ }
238
+ function getOperatorPrecedence(operator) {
239
+ switch (operator) {
240
+ case "^":
241
+ return 8;
242
+ case "*":
243
+ case "/":
244
+ case "%":
245
+ return 7;
246
+ case "+":
247
+ case "-":
248
+ return 6;
249
+ case "==":
250
+ case "!=":
251
+ case "<":
252
+ case ">":
253
+ case "<=":
254
+ case ">=":
255
+ return 5;
256
+ case "&&":
257
+ return 4;
258
+ case "||":
259
+ return 3;
260
+ default:
261
+ return 0;
262
+ }
263
+ }
264
+ function getTokenPrecedence(type) {
265
+ switch (type) {
266
+ case "EQUALS" /* EQUALS */:
267
+ case "NULLISH_ASSIGN" /* NULLISH_ASSIGN */:
268
+ return 1;
269
+ case "QUESTION" /* QUESTION */:
270
+ return 2;
271
+ case "LOGICAL_OR" /* LOGICAL_OR */:
272
+ return 3;
273
+ case "LOGICAL_AND" /* LOGICAL_AND */:
274
+ return 4;
275
+ case "DOUBLE_EQUALS" /* DOUBLE_EQUALS */:
276
+ case "NOT_EQUALS" /* NOT_EQUALS */:
277
+ case "LESS_THAN" /* LESS_THAN */:
278
+ case "GREATER_THAN" /* GREATER_THAN */:
279
+ case "LESS_EQUAL" /* LESS_EQUAL */:
280
+ case "GREATER_EQUAL" /* GREATER_EQUAL */:
281
+ return 5;
282
+ case "PLUS" /* PLUS */:
283
+ case "MINUS" /* MINUS */:
284
+ return 6;
285
+ case "STAR" /* STAR */:
286
+ case "SLASH" /* SLASH */:
287
+ case "PERCENT" /* PERCENT */:
288
+ return 7;
289
+ case "CARET" /* CARET */:
290
+ return 8;
291
+ default:
292
+ return 0;
293
+ }
294
+ }
295
+
197
296
  // src/codegen.ts
198
297
  class CodeGenerator {
199
298
  generate(node) {
@@ -257,15 +356,15 @@ class CodeGenerator {
257
356
  const condition = this.generate(node.condition);
258
357
  const consequent = this.generate(node.consequent);
259
358
  const alternate = this.generate(node.alternate);
260
- const conditionNeedsParens = isAssignment(node.condition) || isBinaryOp(node.condition) && this.getPrecedence(node.condition.operator) <= 2;
359
+ const conditionNeedsParens = isAssignment(node.condition) || isBinaryOp(node.condition) && getOperatorPrecedence(node.condition.operator) <= 2;
261
360
  const conditionCode = conditionNeedsParens ? `(${condition})` : condition;
262
361
  return `${conditionCode} ? ${consequent} : ${alternate}`;
263
362
  }
264
363
  needsParensLeft(node, operator) {
265
364
  if (!isBinaryOp(node))
266
365
  return false;
267
- const nodePrecedence = this.getPrecedence(node.operator);
268
- const operatorPrecedence = this.getPrecedence(operator);
366
+ const nodePrecedence = getOperatorPrecedence(node.operator);
367
+ const operatorPrecedence = getOperatorPrecedence(operator);
269
368
  if (operator === "^") {
270
369
  return nodePrecedence <= operatorPrecedence;
271
370
  }
@@ -274,39 +373,13 @@ class CodeGenerator {
274
373
  needsParensRight(node, operator) {
275
374
  if (!isBinaryOp(node))
276
375
  return false;
277
- const nodePrecedence = this.getPrecedence(node.operator);
278
- const operatorPrecedence = this.getPrecedence(operator);
376
+ const nodePrecedence = getOperatorPrecedence(node.operator);
377
+ const operatorPrecedence = getOperatorPrecedence(operator);
279
378
  if (operator === "^") {
280
379
  return nodePrecedence < operatorPrecedence;
281
380
  }
282
381
  return nodePrecedence <= operatorPrecedence;
283
382
  }
284
- getPrecedence(operator) {
285
- switch (operator) {
286
- case "^":
287
- return 8;
288
- case "*":
289
- case "/":
290
- case "%":
291
- return 7;
292
- case "+":
293
- case "-":
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;
306
- default:
307
- return 0;
308
- }
309
- }
310
383
  }
311
384
  function generate(node) {
312
385
  const generator = new CodeGenerator;
@@ -548,6 +621,23 @@ class Lexer {
548
621
  }
549
622
 
550
623
  // src/parser.ts
624
+ var BINARY_OPERATOR_TOKENS = new Set([
625
+ "PLUS" /* PLUS */,
626
+ "MINUS" /* MINUS */,
627
+ "STAR" /* STAR */,
628
+ "SLASH" /* SLASH */,
629
+ "PERCENT" /* PERCENT */,
630
+ "CARET" /* CARET */,
631
+ "DOUBLE_EQUALS" /* DOUBLE_EQUALS */,
632
+ "NOT_EQUALS" /* NOT_EQUALS */,
633
+ "LESS_THAN" /* LESS_THAN */,
634
+ "GREATER_THAN" /* GREATER_THAN */,
635
+ "LESS_EQUAL" /* LESS_EQUAL */,
636
+ "GREATER_EQUAL" /* GREATER_EQUAL */,
637
+ "LOGICAL_AND" /* LOGICAL_AND */,
638
+ "LOGICAL_OR" /* LOGICAL_OR */
639
+ ]);
640
+
551
641
  class Parser {
552
642
  tokens;
553
643
  current = 0;
@@ -578,7 +668,7 @@ class Parser {
578
668
  let left = this.parsePrefix();
579
669
  while (true) {
580
670
  const token = this.peek();
581
- const precedence = this.getPrecedence(token.type);
671
+ const precedence = getTokenPrecedence(token.type);
582
672
  if (precedence < minPrecedence) {
583
673
  break;
584
674
  }
@@ -698,42 +788,11 @@ class Parser {
698
788
  }
699
789
  return args;
700
790
  }
701
- getPrecedence(type) {
702
- switch (type) {
703
- case "EQUALS" /* EQUALS */:
704
- case "NULLISH_ASSIGN" /* NULLISH_ASSIGN */:
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;
719
- case "PLUS" /* PLUS */:
720
- case "MINUS" /* MINUS */:
721
- return 6;
722
- case "STAR" /* STAR */:
723
- case "SLASH" /* SLASH */:
724
- case "PERCENT" /* PERCENT */:
725
- return 7;
726
- case "CARET" /* CARET */:
727
- return 8;
728
- default:
729
- return 0;
730
- }
731
- }
732
791
  getUnaryPrecedence() {
733
792
  return 6;
734
793
  }
735
794
  isBinaryOperator(type) {
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 */;
795
+ return BINARY_OPERATOR_TOKENS.has(type);
737
796
  }
738
797
  peek() {
739
798
  if (this.current >= this.tokens.length) {
@@ -805,44 +864,7 @@ class Executor {
805
864
  executeBinaryOp(node) {
806
865
  const left = this.execute(node.left);
807
866
  const right = this.execute(node.right);
808
- switch (node.operator) {
809
- case "+":
810
- return left + right;
811
- case "-":
812
- return left - right;
813
- case "*":
814
- return left * right;
815
- case "/":
816
- if (right === 0) {
817
- throw new Error("Division by zero");
818
- }
819
- return left / right;
820
- case "%":
821
- if (right === 0) {
822
- throw new Error("Modulo by zero");
823
- }
824
- return left % right;
825
- case "^":
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;
843
- default:
844
- throw new Error(`Unknown operator: ${node.operator}`);
845
- }
867
+ return evaluateBinaryOperation(node.operator, left, right);
846
868
  }
847
869
  executeUnaryOp(node) {
848
870
  const arg = this.execute(node.argument);
@@ -923,11 +945,15 @@ function analyzeProgram(node) {
923
945
  const deps = new Set;
924
946
  const hasFunctionCall = collectDependencies(stmt.value, deps);
925
947
  dependencies.set(varName, deps);
926
- if (count === 0 && isNumberLiteral(stmt.value)) {
927
- constants.set(varName, stmt.value.value);
928
- }
929
- if (hasFunctionCall) {
948
+ if (isNullishAssignment(stmt)) {
930
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
+ }
931
957
  }
932
958
  }
933
959
  }
@@ -1119,7 +1145,7 @@ function evaluateWithConstants(node, constants) {
1119
1145
  const left = evaluateWithConstants(node.left, constants);
1120
1146
  const right = evaluateWithConstants(node.right, constants);
1121
1147
  if (isNumberLiteral(left) && isNumberLiteral(right)) {
1122
- const result = evaluateBinaryOp(node.operator, left.value, right.value);
1148
+ const result = evaluateBinaryOperation(node.operator, left.value, right.value);
1123
1149
  return number(result);
1124
1150
  }
1125
1151
  return {
@@ -1253,7 +1279,7 @@ function basicOptimize(node) {
1253
1279
  const left = basicOptimize(node.left);
1254
1280
  const right = basicOptimize(node.right);
1255
1281
  if (isNumberLiteral(left) && isNumberLiteral(right)) {
1256
- const result = evaluateBinaryOp(node.operator, left.value, right.value);
1282
+ const result = evaluateBinaryOperation(node.operator, left.value, right.value);
1257
1283
  return number(result);
1258
1284
  }
1259
1285
  return {
@@ -1300,46 +1326,6 @@ function basicOptimize(node) {
1300
1326
  }
1301
1327
  return node;
1302
1328
  }
1303
- function evaluateBinaryOp(operator, left, right) {
1304
- switch (operator) {
1305
- case "+":
1306
- return left + right;
1307
- case "-":
1308
- return left - right;
1309
- case "*":
1310
- return left * right;
1311
- case "/":
1312
- if (right === 0) {
1313
- throw new Error("Division by zero in constant folding");
1314
- }
1315
- return left / right;
1316
- case "%":
1317
- if (right === 0) {
1318
- throw new Error("Modulo by zero in constant folding");
1319
- }
1320
- return left % right;
1321
- case "^":
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;
1339
- default:
1340
- throw new Error(`Unknown operator: ${operator}`);
1341
- }
1342
- }
1343
1329
  export {
1344
1330
  parseSource,
1345
1331
  optimize,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "littlewing",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
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",