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 +75 -66
- package/dist/index.d.ts +95 -123
- package/dist/index.js +1030 -870
- package/package.json +7 -7
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
|
|
6
|
+
import { evaluate, defaultContext } from 'littlewing';
|
|
7
7
|
|
|
8
8
|
// Arithmetic
|
|
9
|
-
evaluate(
|
|
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(
|
|
18
|
+
evaluate('DIFFERENCE_IN_DAYS(TODAY(), DATE(2025, 12, 31))', defaultContext);
|
|
19
19
|
|
|
20
20
|
// Array comprehensions
|
|
21
|
-
evaluate(
|
|
21
|
+
evaluate('for x in 1..=5 then x ^ 2'); // → [1, 4, 9, 16, 25]
|
|
22
22
|
|
|
23
23
|
// Reduce with accumulator
|
|
24
|
-
evaluate(
|
|
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(
|
|
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
|
-
- **
|
|
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
|
|
58
|
+
import { evaluate } from 'littlewing';
|
|
59
59
|
|
|
60
60
|
// Arithmetic
|
|
61
|
-
evaluate(
|
|
62
|
-
evaluate(
|
|
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(
|
|
69
|
-
evaluate(
|
|
68
|
+
evaluate('5 > 3'); // → true
|
|
69
|
+
evaluate('!(5 > 10)'); // → true
|
|
70
70
|
|
|
71
71
|
// Variables
|
|
72
|
-
evaluate(
|
|
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(
|
|
79
|
-
evaluate(
|
|
78
|
+
evaluate('[10, 20, 30][-1]'); // → 30
|
|
79
|
+
evaluate('[1, 2] + [3, 4]'); // → [1, 2, 3, 4]
|
|
80
80
|
|
|
81
81
|
// Ranges
|
|
82
|
-
evaluate(
|
|
82
|
+
evaluate('1..=5'); // → [1, 2, 3, 4, 5]
|
|
83
83
|
|
|
84
84
|
// For comprehensions (map, filter, reduce)
|
|
85
|
-
evaluate(
|
|
86
|
-
evaluate(
|
|
87
|
-
evaluate(
|
|
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(
|
|
91
|
-
evaluate(
|
|
92
|
-
evaluate(
|
|
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
|
|
98
|
+
import { evaluate, defaultContext } from 'littlewing';
|
|
99
99
|
|
|
100
100
|
// Math
|
|
101
|
-
evaluate(
|
|
102
|
-
evaluate(
|
|
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(
|
|
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(
|
|
114
|
-
evaluate(
|
|
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(
|
|
119
|
-
evaluate(
|
|
120
|
-
evaluate(
|
|
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(
|
|
124
|
-
evaluate(
|
|
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(
|
|
128
|
-
evaluate(
|
|
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
|
|
134
|
+
import { evaluate, assertNumber, assertString } from 'littlewing';
|
|
135
135
|
|
|
136
136
|
const context = {
|
|
137
137
|
functions: {
|
|
138
138
|
FAHRENHEIT: (celsius) => {
|
|
139
|
-
assertNumber(celsius,
|
|
139
|
+
assertNumber(celsius, 'FAHRENHEIT');
|
|
140
140
|
return (celsius * 9) / 5 + 32;
|
|
141
141
|
},
|
|
142
142
|
DISCOUNT: (price, percent) => {
|
|
143
|
-
assertNumber(price,
|
|
144
|
-
assertNumber(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(
|
|
155
|
-
evaluate(
|
|
156
|
-
evaluate(
|
|
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 =
|
|
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(
|
|
184
|
+
evaluate('2 + 2'); // → 4
|
|
185
185
|
|
|
186
186
|
// Evaluate pre-parsed AST (parse once, evaluate many)
|
|
187
|
-
const ast = parse(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
227
|
-
optimize(ast2, new Set([
|
|
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(
|
|
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
|
|
255
|
+
import { visit, parse } from 'littlewing';
|
|
247
256
|
|
|
248
|
-
const count = visit(parse(
|
|
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
|
|
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
|
-
|
|
287
|
-
ast.identifier(
|
|
295
|
+
'x',
|
|
296
|
+
ast.identifier('arr'),
|
|
288
297
|
null,
|
|
289
298
|
null,
|
|
290
|
-
ast.multiply(ast.identifier(
|
|
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 **
|
|
332
|
+
The `defaultContext` includes **82 built-in functions**:
|
|
324
333
|
|
|
325
|
-
**
|
|
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 (
|
|
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 (
|
|
340
|
+
**Array (7):** `ARR_SORT`, `ARR_UNIQUE`, `ARR_FLAT`, `ARR_JOIN`, `ARR_SUM`, `ARR_MIN`, `ARR_MAX`
|
|
332
341
|
|
|
333
|
-
**Date (
|
|
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`, `
|
|
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
|
|
357
|
+
import { evaluate, parse } from 'littlewing';
|
|
349
358
|
|
|
350
|
-
const formula = parse(
|
|
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
|