littlewing 2.1.1 → 2.3.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
@@ -3,10 +3,10 @@
3
3
  A minimal, high-performance multi-type expression language for JavaScript. Seven types, zero compromise, built for the browser.
4
4
 
5
5
  ```typescript
6
- import { evaluate, defaultContext } from "littlewing";
6
+ import { evaluate, defaultContext } from 'littlewing';
7
7
 
8
8
  // Arithmetic
9
- evaluate("2 + 3 * 4"); // → 14
9
+ evaluate('2 + 3 * 4'); // → 14
10
10
 
11
11
  // Strings
12
12
  evaluate('"hello" + " world"'); // → "hello world"
@@ -15,16 +15,16 @@ evaluate('"hello" + " world"'); // → "hello world"
15
15
  evaluate('price = 100; if price > 50 then "expensive" else "cheap"'); // → "expensive"
16
16
 
17
17
  // Date arithmetic
18
- evaluate("DIFFERENCE_IN_DAYS(TODAY(), DATE(2025, 12, 31))", defaultContext);
18
+ evaluate('DIFFERENCE_IN_DAYS(TODAY(), DATE(2025, 12, 31))', defaultContext);
19
19
 
20
20
  // Array comprehensions
21
- evaluate("for x in 1..=5 then x ^ 2"); // → [1, 4, 9, 16, 25]
21
+ evaluate('for x in 1..=5 then x ^ 2'); // → [1, 4, 9, 16, 25]
22
22
 
23
23
  // Reduce with accumulator
24
- evaluate("for x in [1, 2, 3, 4] into sum = 0 then sum + x"); // → 10
24
+ evaluate('for x in [1, 2, 3, 4] into sum = 0 then sum + x'); // → 10
25
25
 
26
26
  // Pipe operator — chain values through functions
27
- evaluate("-5 |> ABS(?) |> STR(?)", defaultContext); // → "5"
27
+ evaluate('-5 |> ABS(?) |> STR(?)', defaultContext); // → "5"
28
28
  ```
29
29
 
30
30
  ## Features
@@ -37,7 +37,7 @@ evaluate("-5 |> ABS(?) |> STR(?)", defaultContext); // → "5"
37
37
  - **Pipe operator** — `x |> FUN(?) |> OTHER(?, 1)` chains values through function calls
38
38
  - **Range expressions** — `1..5` (exclusive), `1..=5` (inclusive)
39
39
  - **Deep equality** — `[1, 2] == [1, 2]` → `true`; cross-type `==` → `false`
40
- - **85 built-in functions** — Math, string, array, date, time, and datetime operations
40
+ - **82 built-in functions** — Math, string, array, date, time, and datetime operations
41
41
  - **O(n) performance** — Linear time parsing and execution
42
42
  - **Safe evaluation** — Tree-walk interpreter, no code generation
43
43
  - **Extensible** — Add custom functions and variables via context
@@ -55,93 +55,93 @@ npm install littlewing
55
55
  ### Basic Usage
56
56
 
57
57
  ```typescript
58
- import { evaluate } from "littlewing";
58
+ import { evaluate } from 'littlewing';
59
59
 
60
60
  // Arithmetic
61
- evaluate("2 + 3 * 4"); // → 14
62
- evaluate("2 ^ 10"); // → 1024
61
+ evaluate('2 + 3 * 4'); // → 14
62
+ evaluate('2 ^ 10'); // → 1024
63
63
 
64
64
  // Strings
65
65
  evaluate('"hello" + " world"'); // → "hello world"
66
66
 
67
67
  // Booleans (comparisons return boolean, not 1/0)
68
- evaluate("5 > 3"); // → true
69
- evaluate("!(5 > 10)"); // → true
68
+ evaluate('5 > 3'); // → true
69
+ evaluate('!(5 > 10)'); // → true
70
70
 
71
71
  // Variables
72
- evaluate("x = 10; y = 20; x + y"); // → 30
72
+ evaluate('x = 10; y = 20; x + y'); // → 30
73
73
 
74
74
  // Conditionals (condition must be boolean, else is required)
75
75
  evaluate('age = 21; if age >= 18 then "adult" else "minor"'); // → "adult"
76
76
 
77
77
  // Arrays and indexing
78
- evaluate("[10, 20, 30][-1]"); // → 30
79
- evaluate("[1, 2] + [3, 4]"); // → [1, 2, 3, 4]
78
+ evaluate('[10, 20, 30][-1]'); // → 30
79
+ evaluate('[1, 2] + [3, 4]'); // → [1, 2, 3, 4]
80
80
 
81
81
  // Ranges
82
- evaluate("1..=5"); // → [1, 2, 3, 4, 5]
82
+ evaluate('1..=5'); // → [1, 2, 3, 4, 5]
83
83
 
84
84
  // For comprehensions (map, filter, reduce)
85
- evaluate("for x in 1..=5 then x * 2"); // → [2, 4, 6, 8, 10]
86
- evaluate("for x in 1..=10 when x % 2 == 0 then x"); // → [2, 4, 6, 8, 10]
87
- evaluate("for x in [1, 2, 3] into sum = 0 then sum + x"); // → 6
85
+ evaluate('for x in 1..=5 then x * 2'); // → [2, 4, 6, 8, 10]
86
+ evaluate('for x in 1..=10 when x % 2 == 0 then x'); // → [2, 4, 6, 8, 10]
87
+ evaluate('for x in [1, 2, 3] into sum = 0 then sum + x'); // → 6
88
88
 
89
89
  // Pipe operator
90
- evaluate("-42 |> ABS(?)", defaultContext); // → 42
91
- evaluate("150 |> CLAMP(?, 0, 100)", defaultContext); // → 100
92
- evaluate("-3 |> ABS(?) |> STR(?)", defaultContext); // → "3"
90
+ evaluate('-42 |> ABS(?)', defaultContext); // → 42
91
+ evaluate('150 |> CLAMP(?, 0, 100)', defaultContext); // → 100
92
+ evaluate('-3 |> ABS(?) |> STR(?)', defaultContext); // → "3"
93
93
  ```
94
94
 
95
95
  ### With Built-in Functions
96
96
 
97
97
  ```typescript
98
- import { evaluate, defaultContext } from "littlewing";
98
+ import { evaluate, defaultContext } from 'littlewing';
99
99
 
100
100
  // Math
101
- evaluate("ABS(-42)", defaultContext); // → 42
102
- evaluate("ROUND(3.7)", defaultContext); // → 4
101
+ evaluate('ABS(-42)', defaultContext); // → 42
102
+ evaluate('ROUND(3.7)', defaultContext); // → 4
103
103
 
104
104
  // Type conversion
105
105
  evaluate('NUM("42")', defaultContext); // → 42
106
- evaluate("STR(42)", defaultContext); // → "42"
106
+ evaluate('STR(42)', defaultContext); // → "42"
107
107
 
108
108
  // String functions
109
109
  evaluate('STR_UPPER("hello")', defaultContext); // → "HELLO"
110
110
  evaluate('STR_SPLIT("a,b,c", ",")', defaultContext); // → ["a", "b", "c"]
111
111
 
112
112
  // Array functions
113
- evaluate("ARR_SORT([3, 1, 2])", defaultContext); // → [1, 2, 3]
114
- evaluate("ARR_SUM([10, 20, 30])", defaultContext); // → 60
113
+ evaluate('ARR_SORT([3, 1, 2])', defaultContext); // → [1, 2, 3]
114
+ evaluate('ARR_SUM([10, 20, 30])', defaultContext); // → 60
115
115
  evaluate('ARR_JOIN(["a", "b", "c"], "-")', defaultContext); // → "a-b-c"
116
116
 
117
117
  // Date functions
118
- evaluate("TODAY()", defaultContext); // → Temporal.PlainDate
119
- evaluate("ADD_DAYS(TODAY(), 7)", defaultContext); // → 7 days from now
120
- evaluate("IS_WEEKEND(TODAY())", defaultContext); // → true or false
118
+ evaluate('TODAY()', defaultContext); // → Temporal.PlainDate
119
+ evaluate('ADD_DAYS(TODAY(), 7)', defaultContext); // → 7 days from now
120
+ evaluate('IS_WEEKEND(TODAY())', defaultContext); // → true or false
121
121
 
122
122
  // Time functions
123
- evaluate("TIME(14, 30, 0)", defaultContext); // → Temporal.PlainTime
124
- evaluate("ADD_HOURS(TIME(10, 0, 0), 3)", defaultContext); // → 13:00:00
123
+ evaluate('TIME(14, 30, 0)', defaultContext); // → Temporal.PlainTime
124
+ evaluate('ADD_HOURS(TIME(10, 0, 0), 3)', defaultContext); // → 13:00:00
125
125
 
126
126
  // DateTime functions
127
- evaluate("NOW()", defaultContext); // → Temporal.PlainDateTime
128
- evaluate("TO_DATE(NOW())", defaultContext); // → today's date
127
+ evaluate('NOW()', defaultContext); // → Temporal.PlainDateTime
128
+ evaluate('TO_DATE(NOW())', defaultContext); // → today's date
129
129
  ```
130
130
 
131
131
  ### Custom Functions and Variables
132
132
 
133
133
  ```typescript
134
- import { evaluate, assertNumber, assertString } from "littlewing";
134
+ import { evaluate, assertNumber, assertString } from 'littlewing';
135
135
 
136
136
  const context = {
137
137
  functions: {
138
138
  FAHRENHEIT: (celsius) => {
139
- assertNumber(celsius, "FAHRENHEIT");
139
+ assertNumber(celsius, 'FAHRENHEIT');
140
140
  return (celsius * 9) / 5 + 32;
141
141
  },
142
142
  DISCOUNT: (price, percent) => {
143
- assertNumber(price, "DISCOUNT", "price");
144
- assertNumber(percent, "DISCOUNT", "percent");
143
+ assertNumber(price, 'DISCOUNT', 'price');
144
+ assertNumber(percent, 'DISCOUNT', 'percent');
145
145
  return price * (1 - percent / 100);
146
146
  },
147
147
  },
@@ -151,9 +151,9 @@ const context = {
151
151
  },
152
152
  };
153
153
 
154
- evaluate("FAHRENHEIT(20)", context); // → 68
155
- evaluate("DISCOUNT(100, 15)", context); // → 85
156
- evaluate("100 * (1 + taxRate)", context); // → 108
154
+ evaluate('FAHRENHEIT(20)', context); // → 68
155
+ evaluate('DISCOUNT(100, 15)', context); // → 85
156
+ evaluate('100 * (1 + taxRate)', context); // → 108
157
157
  ```
158
158
 
159
159
  The assertion helpers (`assertNumber`, `assertString`, `assertBoolean`, `assertArray`, `assertDate`, `assertTime`, `assertDateTime`, `assertDateOrDateTime`, `assertTimeOrDateTime`) are the same ones used by the built-in standard library. They throw `TypeError` with consistent messages on type mismatch.
@@ -161,7 +161,7 @@ The assertion helpers (`assertNumber`, `assertString`, `assertBoolean`, `assertA
161
161
  ### External Variables Override Script Defaults
162
162
 
163
163
  ```typescript
164
- const formula = "multiplier = 2; value = 100; value * multiplier";
164
+ const formula = 'multiplier = 2; value = 100; value * multiplier';
165
165
 
166
166
  evaluate(formula); // → 200
167
167
  evaluate(formula, { variables: { multiplier: 3 } }); // → 300
@@ -181,10 +181,10 @@ For complete language documentation including all operators, control flow, and b
181
181
  Evaluate an expression or AST and return the result.
182
182
 
183
183
  ```typescript
184
- evaluate("2 + 2"); // → 4
184
+ evaluate('2 + 2'); // → 4
185
185
 
186
186
  // Evaluate pre-parsed AST (parse once, evaluate many)
187
- const ast = parse("price * quantity");
187
+ const ast = parse('price * quantity');
188
188
  evaluate(ast, { variables: { price: 10, quantity: 5 } }); // → 50
189
189
  evaluate(ast, { variables: { price: 20, quantity: 3 } }); // → 60
190
190
  ```
@@ -194,7 +194,16 @@ evaluate(ast, { variables: { price: 20, quantity: 3 } }); // → 60
194
194
  Evaluate and return all assigned variables as a record.
195
195
 
196
196
  ```typescript
197
- evaluateScope("x = 10; y = x * 2"); // → { x: 10, y: 20 }
197
+ evaluateScope('x = 10; y = x * 2'); // → { x: 10, y: 20 }
198
+ ```
199
+
200
+ #### `evaluateWithScope(input: string | ASTNode, context?: ExecutionContext): ExecutionResult`
201
+
202
+ Evaluate once and return both the final value and the full variable scope.
203
+
204
+ ```typescript
205
+ evaluateWithScope('x = 10; y = x * 2; y + 1');
206
+ // → { value: 21, scope: { x: 10, y: 20 } }
198
207
  ```
199
208
 
200
209
  #### `parse(source: string): ASTNode`
@@ -202,7 +211,7 @@ evaluateScope("x = 10; y = x * 2"); // → { x: 10, y: 20 }
202
211
  Parse source into an Abstract Syntax Tree without evaluating.
203
212
 
204
213
  ```typescript
205
- const ast = parse("2 + 3 * 4");
214
+ const ast = parse('2 + 3 * 4');
206
215
  evaluate(ast); // → 14
207
216
  ```
208
217
 
@@ -211,7 +220,7 @@ evaluate(ast); // → 14
211
220
  Convert AST back to source code (preserves comments).
212
221
 
213
222
  ```typescript
214
- generate(parse("2 + 3 * 4")); // → "2 + 3 * 4"
223
+ generate(parse('2 + 3 * 4')); // → "2 + 3 * 4"
215
224
  ```
216
225
 
217
226
  #### `optimize(node: ASTNode, externalVariables?: ReadonlySet<string>): ASTNode`
@@ -219,12 +228,12 @@ generate(parse("2 + 3 * 4")); // → "2 + 3 * 4"
219
228
  Optimize an AST with constant folding, constant propagation, and dead code elimination.
220
229
 
221
230
  ```typescript
222
- const ast = parse("2 + 3 * 4");
231
+ const ast = parse('2 + 3 * 4');
223
232
  optimize(ast); // → NumberLiteral(14)
224
233
 
225
234
  // With external variables: propagates internal constants while preserving external ones
226
- const ast2 = parse("x = 5; y = 10; x + y");
227
- optimize(ast2, new Set(["x"])); // Propagates y=10, keeps x as external
235
+ const ast2 = parse('x = 5; y = 10; x + y');
236
+ optimize(ast2, new Set(['x'])); // Propagates y=10, keeps x as external
228
237
  ```
229
238
 
230
239
  #### `extractInputVariables(ast: ASTNode): string[]`
@@ -232,7 +241,7 @@ optimize(ast2, new Set(["x"])); // Propagates y=10, keeps x as external
232
241
  Extract variable names assigned to constant values (useful for building UIs with input controls).
233
242
 
234
243
  ```typescript
235
- const ast = parse("price = 100; tax = price * 0.08");
244
+ const ast = parse('price = 100; tax = price * 0.08');
236
245
  extractInputVariables(ast); // → ["price"]
237
246
  ```
238
247
 
@@ -243,9 +252,9 @@ extractInputVariables(ast); // → ["price"]
243
252
  Exhaustively visit every node in an AST. All 16 node types must be handled.
244
253
 
245
254
  ```typescript
246
- import { visit, parse } from "littlewing";
255
+ import { visit, parse } from 'littlewing';
247
256
 
248
- const count = visit(parse("2 + 3"), {
257
+ const count = visit(parse('2 + 3'), {
249
258
  Program: (n, recurse) => n.statements.reduce((s, stmt) => s + recurse(stmt), 0),
250
259
  NumberLiteral: () => 1,
251
260
  StringLiteral: () => 1,
@@ -277,17 +286,17 @@ Visit only specific node types with a fallback for unhandled types.
277
286
  The `ast` namespace provides builder functions for constructing AST nodes:
278
287
 
279
288
  ```typescript
280
- import { ast, generate } from "littlewing";
289
+ import { ast, generate } from 'littlewing';
281
290
 
282
291
  generate(ast.add(ast.number(2), ast.number(3))); // → "2 + 3"
283
292
  generate(ast.ifExpr(ast.boolean(true), ast.number(1), ast.number(0))); // → "if true then 1 else 0"
284
293
  generate(
285
294
  ast.forExpr(
286
- "x",
287
- ast.identifier("arr"),
295
+ 'x',
296
+ ast.identifier('arr'),
288
297
  null,
289
298
  null,
290
- ast.multiply(ast.identifier("x"), ast.number(2)),
299
+ ast.multiply(ast.identifier('x'), ast.number(2)),
291
300
  ),
292
301
  );
293
302
  // → "for x in arr then x * 2"
@@ -320,19 +329,19 @@ type RuntimeValue =
320
329
 
321
330
  ### Default Context Functions
322
331
 
323
- The `defaultContext` includes **85 built-in functions**:
332
+ The `defaultContext` includes **82 built-in functions**:
324
333
 
325
- **Type Conversion (3):** `STR`, `NUM`, `TYPE`
334
+ **Core (8):** `STR`, `NUM`, `TYPE`, `LEN`, `SLICE`, `CONTAINS`, `REVERSE`, `INDEX_OF`
326
335
 
327
336
  **Math (14):** `ABS`, `CEIL`, `FLOOR`, `ROUND`, `SQRT`, `MIN`, `MAX`, `CLAMP`, `SIN`, `COS`, `TAN`, `LOG`, `LOG10`, `EXP`
328
337
 
329
- **String (12):** `STR_LEN`, `STR_UPPER`, `STR_LOWER`, `STR_TRIM`, `STR_SLICE`, `STR_CONTAINS`, `STR_INDEX_OF`, `STR_SPLIT`, `STR_REPLACE`, `STR_STARTS_WITH`, `STR_ENDS_WITH`, `STR_REPEAT`
338
+ **String (8):** `STR_UPPER`, `STR_LOWER`, `STR_TRIM`, `STR_SPLIT`, `STR_REPLACE`, `STR_STARTS_WITH`, `STR_ENDS_WITH`, `STR_REPEAT`
330
339
 
331
- **Array (12):** `ARR_LEN`, `ARR_PUSH`, `ARR_SLICE`, `ARR_CONTAINS`, `ARR_REVERSE`, `ARR_SORT`, `ARR_UNIQUE`, `ARR_FLAT`, `ARR_JOIN`, `ARR_SUM`, `ARR_MIN`, `ARR_MAX`
340
+ **Array (7):** `ARR_SORT`, `ARR_UNIQUE`, `ARR_FLAT`, `ARR_JOIN`, `ARR_SUM`, `ARR_MIN`, `ARR_MAX`
332
341
 
333
- **Date (24):** `TODAY`, `DATE`, `GET_YEAR`, `GET_MONTH`, `GET_DAY`, `GET_WEEKDAY`, `GET_DAY_OF_YEAR`, `GET_QUARTER`, `ADD_DAYS`, `ADD_MONTHS`, `ADD_YEARS`, `DIFFERENCE_IN_DAYS`, `DIFFERENCE_IN_WEEKS`, `DIFFERENCE_IN_MONTHS`, `DIFFERENCE_IN_YEARS`, `START_OF_MONTH`, `END_OF_MONTH`, `START_OF_YEAR`, `END_OF_YEAR`, `START_OF_WEEK`, `START_OF_QUARTER`, `IS_SAME_DAY`, `IS_WEEKEND`, `IS_LEAP_YEAR`
342
+ **Date (25):** `TODAY`, `DATE`, `YEAR`, `MONTH`, `DAY`, `WEEKDAY`, `DAY_OF_YEAR`, `QUARTER`, `ADD_DAYS`, `ADD_MONTHS`, `ADD_YEARS`, `DIFFERENCE_IN_DAYS`, `DIFFERENCE_IN_WEEKS`, `DIFFERENCE_IN_MONTHS`, `DIFFERENCE_IN_YEARS`, `START_OF_MONTH`, `END_OF_MONTH`, `START_OF_YEAR`, `END_OF_YEAR`, `START_OF_WEEK`, `START_OF_QUARTER`, `IS_SAME_DAY`, `IS_WEEKEND`, `IS_LEAP_YEAR`, `AGE`
334
343
 
335
- **Time (13):** `TIME`, `NOW_TIME`, `GET_HOUR`, `GET_MINUTE`, `GET_SECOND`, `GET_MILLISECOND`, `ADD_HOURS`, `ADD_MINUTES`, `ADD_SECONDS`, `DIFFERENCE_IN_HOURS`, `DIFFERENCE_IN_MINUTES`, `DIFFERENCE_IN_SECONDS`, `IS_SAME_TIME`
344
+ **Time (13):** `TIME`, `NOW_TIME`, `HOUR`, `MINUTE`, `SECOND`, `MILLISECOND`, `ADD_HOURS`, `ADD_MINUTES`, `ADD_SECONDS`, `DIFFERENCE_IN_HOURS`, `DIFFERENCE_IN_MINUTES`, `DIFFERENCE_IN_SECONDS`, `IS_SAME_TIME`
336
345
 
337
346
  **DateTime (7):** `DATETIME`, `NOW`, `TO_DATE`, `TO_TIME`, `COMBINE`, `START_OF_DAY`, `END_OF_DAY`
338
347
 
@@ -345,9 +354,9 @@ The `defaultContext` includes **85 built-in functions**:
345
354
  For expressions executed multiple times, parse once and reuse the AST:
346
355
 
347
356
  ```typescript
348
- import { evaluate, parse } from "littlewing";
357
+ import { evaluate, parse } from 'littlewing';
349
358
 
350
- const formula = parse("price * quantity * (1 - discount)");
359
+ const formula = parse('price * quantity * (1 - discount)');
351
360
 
352
361
  evaluate(formula, { variables: { price: 10, quantity: 5, discount: 0.1 } }); // → 45
353
362
  evaluate(formula, { variables: { price: 20, quantity: 3, discount: 0.15 } }); // → 51