littlewing 0.8.3 → 0.9.1
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/README.md +46 -4
- package/dist/index.d.ts +14 -9
- package/dist/index.js +15 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -156,22 +156,38 @@ For complete language documentation including all operators, functions, and exam
|
|
|
156
156
|
|
|
157
157
|
### Main Functions
|
|
158
158
|
|
|
159
|
-
#### `execute(
|
|
159
|
+
#### `execute(input: string | ASTNode, context?: ExecutionContext): number`
|
|
160
160
|
|
|
161
|
-
Execute an expression and return the result.
|
|
161
|
+
Execute an expression or AST and return the result. Accepts either a source string or a pre-parsed AST node.
|
|
162
162
|
|
|
163
163
|
```typescript
|
|
164
|
+
// Execute source string directly
|
|
164
165
|
execute("2 + 2"); // → 4
|
|
165
166
|
execute("ABS(-5)", { functions: { ABS: Math.abs } }); // → 5
|
|
167
|
+
|
|
168
|
+
// Execute pre-parsed AST (useful for parse-once, execute-many scenarios)
|
|
169
|
+
const ast = parseSource("2 + 2");
|
|
170
|
+
execute(ast); // → 4
|
|
171
|
+
execute(ast); // → 4 (no re-parsing)
|
|
166
172
|
```
|
|
167
173
|
|
|
168
174
|
#### `parseSource(source: string): ASTNode`
|
|
169
175
|
|
|
170
|
-
Parse source into an Abstract Syntax Tree without executing.
|
|
176
|
+
Parse source into an Abstract Syntax Tree without executing. Useful for parse-once, execute-many scenarios.
|
|
171
177
|
|
|
172
178
|
```typescript
|
|
173
179
|
const ast = parseSource("2 + 3 * 4");
|
|
174
|
-
|
|
180
|
+
|
|
181
|
+
// Execute multiple times with different contexts (no re-parsing)
|
|
182
|
+
execute(ast); // → 14
|
|
183
|
+
execute(ast, {
|
|
184
|
+
variables: {
|
|
185
|
+
/* ... */
|
|
186
|
+
},
|
|
187
|
+
}); // → 14 (with context)
|
|
188
|
+
|
|
189
|
+
// Or use with Executor class or optimize() function
|
|
190
|
+
const optimized = optimize(ast);
|
|
175
191
|
```
|
|
176
192
|
|
|
177
193
|
#### `optimize(node: ASTNode): ASTNode`
|
|
@@ -225,6 +241,32 @@ The `defaultContext` includes these built-in functions:
|
|
|
225
241
|
|
|
226
242
|
**Date comparisons:** `IS_SAME_DAY`, `IS_WEEKEND`, `IS_LEAP_YEAR` (use `<`, `>`, `<=`, `>=` operators for before/after comparisons)
|
|
227
243
|
|
|
244
|
+
## Performance Optimization
|
|
245
|
+
|
|
246
|
+
For expressions that are executed multiple times with different contexts, parse once and reuse the AST:
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
import { execute, parseSource } from "littlewing";
|
|
250
|
+
|
|
251
|
+
// Parse once
|
|
252
|
+
const formula = parseSource(
|
|
253
|
+
"price * quantity * (1 - discount) * (1 + taxRate)",
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
// Execute many times with different values (no re-parsing)
|
|
257
|
+
execute(formula, {
|
|
258
|
+
variables: { price: 10, quantity: 5, discount: 0.1, taxRate: 0.08 },
|
|
259
|
+
});
|
|
260
|
+
execute(formula, {
|
|
261
|
+
variables: { price: 20, quantity: 3, discount: 0.15, taxRate: 0.08 },
|
|
262
|
+
});
|
|
263
|
+
execute(formula, {
|
|
264
|
+
variables: { price: 15, quantity: 10, discount: 0.2, taxRate: 0.08 },
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// This avoids lexing and parsing overhead on every execution
|
|
268
|
+
```
|
|
269
|
+
|
|
228
270
|
## Use Cases
|
|
229
271
|
|
|
230
272
|
- **User-defined formulas** - Let users write safe arithmetic expressions
|
package/dist/index.d.ts
CHANGED
|
@@ -585,9 +585,11 @@ declare class Executor {
|
|
|
585
585
|
private executeConditionalExpression;
|
|
586
586
|
}
|
|
587
587
|
/**
|
|
588
|
-
* Execute source code with given context
|
|
588
|
+
* Execute source code or AST with given context
|
|
589
|
+
* @param input - Either a source code string or an AST node
|
|
590
|
+
* @param context - Optional execution context with variables and functions
|
|
589
591
|
*/
|
|
590
|
-
declare function execute(
|
|
592
|
+
declare function execute(input: string | ASTNode, context?: ExecutionContext): RuntimeValue;
|
|
591
593
|
/**
|
|
592
594
|
* Lexer - converts source code into tokens
|
|
593
595
|
* Implements a single-pass O(n) tokenization algorithm
|
|
@@ -659,11 +661,12 @@ declare function optimize(node: ASTNode): ASTNode;
|
|
|
659
661
|
/**
|
|
660
662
|
* Parser using Pratt parsing (top-down operator precedence)
|
|
661
663
|
* Implements an efficient O(n) parsing algorithm
|
|
664
|
+
* Uses lazy lexing - calls lexer on-demand instead of receiving all tokens upfront
|
|
662
665
|
*/
|
|
663
666
|
declare class Parser {
|
|
664
|
-
private
|
|
665
|
-
private
|
|
666
|
-
constructor(
|
|
667
|
+
private lexer;
|
|
668
|
+
private currentToken;
|
|
669
|
+
constructor(lexer: Lexer);
|
|
667
670
|
/**
|
|
668
671
|
* Parse tokens into an AST
|
|
669
672
|
* Supports multiple statements separated by semicolons or newlines
|
|
@@ -684,9 +687,11 @@ declare class Parser {
|
|
|
684
687
|
private parseFunctionArguments;
|
|
685
688
|
/**
|
|
686
689
|
* Get unary operator precedence
|
|
687
|
-
* Returns
|
|
688
|
-
* This means:
|
|
689
|
-
*
|
|
690
|
+
* Returns 7 which is higher than add/sub (6) but lower than exponentiation (8)
|
|
691
|
+
* This means:
|
|
692
|
+
* - Binds tighter than addition: -2 + 3 parses as (-2) + 3 = 1
|
|
693
|
+
* - Binds looser than exponentiation: -2^2 parses as -(2^2) = -4, not (-2)^2 = 4
|
|
694
|
+
* Matches the behavior of Python, JavaScript, and most languages
|
|
690
695
|
*/
|
|
691
696
|
private getUnaryPrecedence;
|
|
692
697
|
/**
|
|
@@ -698,7 +703,7 @@ declare class Parser {
|
|
|
698
703
|
*/
|
|
699
704
|
private peek;
|
|
700
705
|
/**
|
|
701
|
-
* Advance to next token
|
|
706
|
+
* Advance to next token by calling lexer
|
|
702
707
|
*/
|
|
703
708
|
private advance;
|
|
704
709
|
}
|
package/dist/index.js
CHANGED
|
@@ -353,7 +353,7 @@ class CodeGenerator {
|
|
|
353
353
|
generateBinaryOp(node) {
|
|
354
354
|
const left = this.generate(node.left);
|
|
355
355
|
const right = this.generate(node.right);
|
|
356
|
-
const leftNeedsParens = this.needsParensLeft(node.left, node.operator);
|
|
356
|
+
const leftNeedsParens = this.needsParensLeft(node.left, node.operator) || node.operator === "^" && isUnaryOp(node.left);
|
|
357
357
|
const leftCode = leftNeedsParens ? `(${left})` : left;
|
|
358
358
|
const rightNeedsParens = this.needsParensRight(node.right, node.operator);
|
|
359
359
|
const rightCode = rightNeedsParens ? `(${right})` : right;
|
|
@@ -806,10 +806,11 @@ var BINARY_OPERATOR_TOKENS = new Set([
|
|
|
806
806
|
]);
|
|
807
807
|
|
|
808
808
|
class Parser {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
constructor(
|
|
812
|
-
this.
|
|
809
|
+
lexer;
|
|
810
|
+
currentToken;
|
|
811
|
+
constructor(lexer) {
|
|
812
|
+
this.lexer = lexer;
|
|
813
|
+
this.currentToken = lexer.nextToken();
|
|
813
814
|
}
|
|
814
815
|
parse() {
|
|
815
816
|
const statements = [];
|
|
@@ -856,7 +857,8 @@ class Parser {
|
|
|
856
857
|
} else if (this.isBinaryOperator(token.type)) {
|
|
857
858
|
const operator = token.value;
|
|
858
859
|
this.advance();
|
|
859
|
-
const
|
|
860
|
+
const isRightAssociative = operator === "^";
|
|
861
|
+
const right = this.parseExpression(isRightAssociative ? precedence : precedence + 1);
|
|
860
862
|
left = binaryOp(left, operator, right);
|
|
861
863
|
} else {
|
|
862
864
|
break;
|
|
@@ -913,29 +915,21 @@ class Parser {
|
|
|
913
915
|
return args;
|
|
914
916
|
}
|
|
915
917
|
getUnaryPrecedence() {
|
|
916
|
-
return
|
|
918
|
+
return 7;
|
|
917
919
|
}
|
|
918
920
|
isBinaryOperator(type) {
|
|
919
921
|
return BINARY_OPERATOR_TOKENS.has(type);
|
|
920
922
|
}
|
|
921
923
|
peek() {
|
|
922
|
-
|
|
923
|
-
return { type: "EOF" /* EOF */, value: "", position: -1 };
|
|
924
|
-
}
|
|
925
|
-
const token = this.tokens[this.current];
|
|
926
|
-
if (token === undefined) {
|
|
927
|
-
return { type: "EOF" /* EOF */, value: "", position: -1 };
|
|
928
|
-
}
|
|
929
|
-
return token;
|
|
924
|
+
return this.currentToken;
|
|
930
925
|
}
|
|
931
926
|
advance() {
|
|
932
|
-
this.
|
|
927
|
+
this.currentToken = this.lexer.nextToken();
|
|
933
928
|
}
|
|
934
929
|
}
|
|
935
930
|
function parseSource(source) {
|
|
936
931
|
const lexer = new Lexer(source);
|
|
937
|
-
const
|
|
938
|
-
const parser = new Parser(tokens);
|
|
932
|
+
const parser = new Parser(lexer);
|
|
939
933
|
return parser.parse();
|
|
940
934
|
}
|
|
941
935
|
|
|
@@ -1024,10 +1018,10 @@ class Executor {
|
|
|
1024
1018
|
return condition !== 0 ? this.execute(node.consequent) : this.execute(node.alternate);
|
|
1025
1019
|
}
|
|
1026
1020
|
}
|
|
1027
|
-
function execute(
|
|
1028
|
-
const
|
|
1021
|
+
function execute(input, context) {
|
|
1022
|
+
const node = typeof input === "string" ? parseSource(input) : input;
|
|
1029
1023
|
const executor = new Executor(context);
|
|
1030
|
-
return executor.execute(
|
|
1024
|
+
return executor.execute(node);
|
|
1031
1025
|
}
|
|
1032
1026
|
// src/optimizer.ts
|
|
1033
1027
|
function optimize(node) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "littlewing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
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",
|