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 +3 -19
- package/dist/index.js +134 -148
- package/package.json +1 -1
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) &&
|
|
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 =
|
|
268
|
-
const operatorPrecedence =
|
|
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 =
|
|
278
|
-
const operatorPrecedence =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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 =
|
|
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 =
|
|
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.
|
|
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",
|