littlewing 0.5.1 → 0.5.3
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 +1 -20
- package/dist/index.js +133 -144
- 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
|
|
@@ -416,7 +411,7 @@ declare class Lexer {
|
|
|
416
411
|
private skipWhitespaceAndComments;
|
|
417
412
|
/**
|
|
418
413
|
* Read a number token
|
|
419
|
-
* Supports: integers (42), decimals (3.14), and scientific notation (1.5e6, 2e-3)
|
|
414
|
+
* Supports: integers (42), decimals (3.14), decimal shorthand (.2), and scientific notation (1.5e6, 2e-3, .5e2)
|
|
420
415
|
*/
|
|
421
416
|
private readNumber;
|
|
422
417
|
/**
|
|
@@ -498,20 +493,6 @@ 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
|
|
516
497
|
* Returns 6 which is higher than add/sub (6) but lower than exponentiation (8)
|
|
517
498
|
* This means: -2^2 parses as -(2^2) = -4, not (-2)^2 = 4
|
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;
|
|
@@ -373,6 +446,9 @@ class Lexer {
|
|
|
373
446
|
if (this.isDigit(char)) {
|
|
374
447
|
return this.readNumber();
|
|
375
448
|
}
|
|
449
|
+
if (char === "." && this.isDigit(this.peek())) {
|
|
450
|
+
return this.readNumber();
|
|
451
|
+
}
|
|
376
452
|
if (this.isLetter(char) || char === "_") {
|
|
377
453
|
return this.readIdentifier();
|
|
378
454
|
}
|
|
@@ -485,6 +561,10 @@ class Lexer {
|
|
|
485
561
|
const start = this.position;
|
|
486
562
|
let hasDecimal = false;
|
|
487
563
|
let hasExponent = false;
|
|
564
|
+
if (this.getCharAt(this.position) === ".") {
|
|
565
|
+
hasDecimal = true;
|
|
566
|
+
this.position++;
|
|
567
|
+
}
|
|
488
568
|
while (this.position < this.source.length) {
|
|
489
569
|
const char = this.getCharAt(this.position);
|
|
490
570
|
if (this.isDigit(char)) {
|
|
@@ -548,6 +628,23 @@ class Lexer {
|
|
|
548
628
|
}
|
|
549
629
|
|
|
550
630
|
// src/parser.ts
|
|
631
|
+
var BINARY_OPERATOR_TOKENS = new Set([
|
|
632
|
+
"PLUS" /* PLUS */,
|
|
633
|
+
"MINUS" /* MINUS */,
|
|
634
|
+
"STAR" /* STAR */,
|
|
635
|
+
"SLASH" /* SLASH */,
|
|
636
|
+
"PERCENT" /* PERCENT */,
|
|
637
|
+
"CARET" /* CARET */,
|
|
638
|
+
"DOUBLE_EQUALS" /* DOUBLE_EQUALS */,
|
|
639
|
+
"NOT_EQUALS" /* NOT_EQUALS */,
|
|
640
|
+
"LESS_THAN" /* LESS_THAN */,
|
|
641
|
+
"GREATER_THAN" /* GREATER_THAN */,
|
|
642
|
+
"LESS_EQUAL" /* LESS_EQUAL */,
|
|
643
|
+
"GREATER_EQUAL" /* GREATER_EQUAL */,
|
|
644
|
+
"LOGICAL_AND" /* LOGICAL_AND */,
|
|
645
|
+
"LOGICAL_OR" /* LOGICAL_OR */
|
|
646
|
+
]);
|
|
647
|
+
|
|
551
648
|
class Parser {
|
|
552
649
|
tokens;
|
|
553
650
|
current = 0;
|
|
@@ -578,7 +675,7 @@ class Parser {
|
|
|
578
675
|
let left = this.parsePrefix();
|
|
579
676
|
while (true) {
|
|
580
677
|
const token = this.peek();
|
|
581
|
-
const precedence =
|
|
678
|
+
const precedence = getTokenPrecedence(token.type);
|
|
582
679
|
if (precedence < minPrecedence) {
|
|
583
680
|
break;
|
|
584
681
|
}
|
|
@@ -698,42 +795,11 @@ class Parser {
|
|
|
698
795
|
}
|
|
699
796
|
return args;
|
|
700
797
|
}
|
|
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
798
|
getUnaryPrecedence() {
|
|
733
799
|
return 6;
|
|
734
800
|
}
|
|
735
801
|
isBinaryOperator(type) {
|
|
736
|
-
return type
|
|
802
|
+
return BINARY_OPERATOR_TOKENS.has(type);
|
|
737
803
|
}
|
|
738
804
|
peek() {
|
|
739
805
|
if (this.current >= this.tokens.length) {
|
|
@@ -805,44 +871,7 @@ class Executor {
|
|
|
805
871
|
executeBinaryOp(node) {
|
|
806
872
|
const left = this.execute(node.left);
|
|
807
873
|
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
|
-
}
|
|
874
|
+
return evaluateBinaryOperation(node.operator, left, right);
|
|
846
875
|
}
|
|
847
876
|
executeUnaryOp(node) {
|
|
848
877
|
const arg = this.execute(node.argument);
|
|
@@ -1123,7 +1152,7 @@ function evaluateWithConstants(node, constants) {
|
|
|
1123
1152
|
const left = evaluateWithConstants(node.left, constants);
|
|
1124
1153
|
const right = evaluateWithConstants(node.right, constants);
|
|
1125
1154
|
if (isNumberLiteral(left) && isNumberLiteral(right)) {
|
|
1126
|
-
const result =
|
|
1155
|
+
const result = evaluateBinaryOperation(node.operator, left.value, right.value);
|
|
1127
1156
|
return number(result);
|
|
1128
1157
|
}
|
|
1129
1158
|
return {
|
|
@@ -1257,7 +1286,7 @@ function basicOptimize(node) {
|
|
|
1257
1286
|
const left = basicOptimize(node.left);
|
|
1258
1287
|
const right = basicOptimize(node.right);
|
|
1259
1288
|
if (isNumberLiteral(left) && isNumberLiteral(right)) {
|
|
1260
|
-
const result =
|
|
1289
|
+
const result = evaluateBinaryOperation(node.operator, left.value, right.value);
|
|
1261
1290
|
return number(result);
|
|
1262
1291
|
}
|
|
1263
1292
|
return {
|
|
@@ -1304,46 +1333,6 @@ function basicOptimize(node) {
|
|
|
1304
1333
|
}
|
|
1305
1334
|
return node;
|
|
1306
1335
|
}
|
|
1307
|
-
function evaluateBinaryOp(operator, left, right) {
|
|
1308
|
-
switch (operator) {
|
|
1309
|
-
case "+":
|
|
1310
|
-
return left + right;
|
|
1311
|
-
case "-":
|
|
1312
|
-
return left - right;
|
|
1313
|
-
case "*":
|
|
1314
|
-
return left * right;
|
|
1315
|
-
case "/":
|
|
1316
|
-
if (right === 0) {
|
|
1317
|
-
throw new Error("Division by zero in constant folding");
|
|
1318
|
-
}
|
|
1319
|
-
return left / right;
|
|
1320
|
-
case "%":
|
|
1321
|
-
if (right === 0) {
|
|
1322
|
-
throw new Error("Modulo by zero in constant folding");
|
|
1323
|
-
}
|
|
1324
|
-
return left % right;
|
|
1325
|
-
case "^":
|
|
1326
|
-
return left ** right;
|
|
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 <= right ? 1 : 0;
|
|
1337
|
-
case ">=":
|
|
1338
|
-
return left >= right ? 1 : 0;
|
|
1339
|
-
case "&&":
|
|
1340
|
-
return left !== 0 && right !== 0 ? 1 : 0;
|
|
1341
|
-
case "||":
|
|
1342
|
-
return left !== 0 || right !== 0 ? 1 : 0;
|
|
1343
|
-
default:
|
|
1344
|
-
throw new Error(`Unknown operator: ${operator}`);
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
1336
|
export {
|
|
1348
1337
|
parseSource,
|
|
1349
1338
|
optimize,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "littlewing",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
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",
|