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 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(source: string, context?: ExecutionContext): number`
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
- // Use with Executor class or optimize() function
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(source: string, context?: ExecutionContext): RuntimeValue;
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 tokens;
665
- private current;
666
- constructor(tokens: Token[]);
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 6 which is higher than add/sub (6) but lower than exponentiation (8)
688
- * This means: -2^2 parses as -(2^2) = -4, not (-2)^2 = 4
689
- * Matches the behavior of Python, Ruby, and most languages
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
- tokens;
810
- current = 0;
811
- constructor(tokens) {
812
- this.tokens = tokens;
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 right = this.parseExpression(precedence + 1);
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 6;
918
+ return 7;
917
919
  }
918
920
  isBinaryOperator(type) {
919
921
  return BINARY_OPERATOR_TOKENS.has(type);
920
922
  }
921
923
  peek() {
922
- if (this.current >= this.tokens.length) {
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.current++;
927
+ this.currentToken = this.lexer.nextToken();
933
928
  }
934
929
  }
935
930
  function parseSource(source) {
936
931
  const lexer = new Lexer(source);
937
- const tokens = lexer.tokenize();
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(source, context) {
1028
- const ast = parseSource(source);
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(ast);
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.8.3",
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",