littlewing 2.1.0 → 2.1.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/LICENSE +21 -0
- package/README.md +400 -0
- package/package.json +2 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Gabriel Vaquer <brielov@icloud.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
# littlewing
|
|
2
|
+
|
|
3
|
+
A minimal, high-performance multi-type expression language for JavaScript. Seven types, zero compromise, built for the browser.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { evaluate, defaultContext } from "littlewing";
|
|
7
|
+
|
|
8
|
+
// Arithmetic
|
|
9
|
+
evaluate("2 + 3 * 4"); // → 14
|
|
10
|
+
|
|
11
|
+
// Strings
|
|
12
|
+
evaluate('"hello" + " world"'); // → "hello world"
|
|
13
|
+
|
|
14
|
+
// Variables and conditionals
|
|
15
|
+
evaluate('price = 100; if price > 50 then "expensive" else "cheap"'); // → "expensive"
|
|
16
|
+
|
|
17
|
+
// Date arithmetic
|
|
18
|
+
evaluate("DIFFERENCE_IN_DAYS(TODAY(), DATE(2025, 12, 31))", defaultContext);
|
|
19
|
+
|
|
20
|
+
// Array comprehensions
|
|
21
|
+
evaluate("for x in 1..=5 then x ^ 2"); // → [1, 4, 9, 16, 25]
|
|
22
|
+
|
|
23
|
+
// Reduce with accumulator
|
|
24
|
+
evaluate("for x in [1, 2, 3, 4] into sum = 0 then sum + x"); // → 10
|
|
25
|
+
|
|
26
|
+
// Pipe operator — chain values through functions
|
|
27
|
+
evaluate("-5 |> ABS(?) |> STR(?)", defaultContext); // → "5"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- **Seven types** — Numbers, strings, booleans, dates (`Temporal.PlainDate`), times (`Temporal.PlainTime`), datetimes (`Temporal.PlainDateTime`), and homogeneous arrays
|
|
33
|
+
- **No implicit coercion** — Explicit type conversion via `STR()`, `NUM()`, etc.
|
|
34
|
+
- **Strict boolean logic** — `!`, `&&`, `||`, and `if` conditions require booleans
|
|
35
|
+
- **Control flow** — `if/then/else` expressions and `for/in/then` comprehensions with optional `when` guard and `into` accumulator
|
|
36
|
+
- **Bracket indexing** — `arr[0]`, `str[-1]`, with chaining (`matrix[0][1]`)
|
|
37
|
+
- **Pipe operator** — `x |> FUN(?) |> OTHER(?, 1)` chains values through function calls
|
|
38
|
+
- **Range expressions** — `1..5` (exclusive), `1..=5` (inclusive)
|
|
39
|
+
- **Deep equality** — `[1, 2] == [1, 2]` → `true`; cross-type `==` → `false`
|
|
40
|
+
- **85 built-in functions** — Math, string, array, date, time, and datetime operations
|
|
41
|
+
- **O(n) performance** — Linear time parsing and execution
|
|
42
|
+
- **Safe evaluation** — Tree-walk interpreter, no code generation
|
|
43
|
+
- **Extensible** — Add custom functions and variables via context
|
|
44
|
+
- **Type-safe** — Full TypeScript support with strict types
|
|
45
|
+
- **Zero runtime dependencies** — Requires global `Temporal` API (native or polyfill)
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install littlewing
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
### Basic Usage
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { evaluate } from "littlewing";
|
|
59
|
+
|
|
60
|
+
// Arithmetic
|
|
61
|
+
evaluate("2 + 3 * 4"); // → 14
|
|
62
|
+
evaluate("2 ^ 10"); // → 1024
|
|
63
|
+
|
|
64
|
+
// Strings
|
|
65
|
+
evaluate('"hello" + " world"'); // → "hello world"
|
|
66
|
+
|
|
67
|
+
// Booleans (comparisons return boolean, not 1/0)
|
|
68
|
+
evaluate("5 > 3"); // → true
|
|
69
|
+
evaluate("!(5 > 10)"); // → true
|
|
70
|
+
|
|
71
|
+
// Variables
|
|
72
|
+
evaluate("x = 10; y = 20; x + y"); // → 30
|
|
73
|
+
|
|
74
|
+
// Conditionals (condition must be boolean, else is required)
|
|
75
|
+
evaluate('age = 21; if age >= 18 then "adult" else "minor"'); // → "adult"
|
|
76
|
+
|
|
77
|
+
// Arrays and indexing
|
|
78
|
+
evaluate("[10, 20, 30][-1]"); // → 30
|
|
79
|
+
evaluate("[1, 2] + [3, 4]"); // → [1, 2, 3, 4]
|
|
80
|
+
|
|
81
|
+
// Ranges
|
|
82
|
+
evaluate("1..=5"); // → [1, 2, 3, 4, 5]
|
|
83
|
+
|
|
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
|
|
88
|
+
|
|
89
|
+
// Pipe operator
|
|
90
|
+
evaluate("-42 |> ABS(?)", defaultContext); // → 42
|
|
91
|
+
evaluate("150 |> CLAMP(?, 0, 100)", defaultContext); // → 100
|
|
92
|
+
evaluate("-3 |> ABS(?) |> STR(?)", defaultContext); // → "3"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### With Built-in Functions
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import { evaluate, defaultContext } from "littlewing";
|
|
99
|
+
|
|
100
|
+
// Math
|
|
101
|
+
evaluate("ABS(-42)", defaultContext); // → 42
|
|
102
|
+
evaluate("ROUND(3.7)", defaultContext); // → 4
|
|
103
|
+
|
|
104
|
+
// Type conversion
|
|
105
|
+
evaluate('NUM("42")', defaultContext); // → 42
|
|
106
|
+
evaluate("STR(42)", defaultContext); // → "42"
|
|
107
|
+
|
|
108
|
+
// String functions
|
|
109
|
+
evaluate('STR_UPPER("hello")', defaultContext); // → "HELLO"
|
|
110
|
+
evaluate('STR_SPLIT("a,b,c", ",")', defaultContext); // → ["a", "b", "c"]
|
|
111
|
+
|
|
112
|
+
// Array functions
|
|
113
|
+
evaluate("ARR_SORT([3, 1, 2])", defaultContext); // → [1, 2, 3]
|
|
114
|
+
evaluate("ARR_SUM([10, 20, 30])", defaultContext); // → 60
|
|
115
|
+
evaluate('ARR_JOIN(["a", "b", "c"], "-")', defaultContext); // → "a-b-c"
|
|
116
|
+
|
|
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
|
|
121
|
+
|
|
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
|
|
125
|
+
|
|
126
|
+
// DateTime functions
|
|
127
|
+
evaluate("NOW()", defaultContext); // → Temporal.PlainDateTime
|
|
128
|
+
evaluate("TO_DATE(NOW())", defaultContext); // → today's date
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Custom Functions and Variables
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { evaluate, assertNumber, assertString } from "littlewing";
|
|
135
|
+
|
|
136
|
+
const context = {
|
|
137
|
+
functions: {
|
|
138
|
+
FAHRENHEIT: (celsius) => {
|
|
139
|
+
assertNumber(celsius, "FAHRENHEIT");
|
|
140
|
+
return (celsius * 9) / 5 + 32;
|
|
141
|
+
},
|
|
142
|
+
DISCOUNT: (price, percent) => {
|
|
143
|
+
assertNumber(price, "DISCOUNT", "price");
|
|
144
|
+
assertNumber(percent, "DISCOUNT", "percent");
|
|
145
|
+
return price * (1 - percent / 100);
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
variables: {
|
|
149
|
+
pi: 3.14159,
|
|
150
|
+
taxRate: 0.08,
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
evaluate("FAHRENHEIT(20)", context); // → 68
|
|
155
|
+
evaluate("DISCOUNT(100, 15)", context); // → 85
|
|
156
|
+
evaluate("100 * (1 + taxRate)", context); // → 108
|
|
157
|
+
```
|
|
158
|
+
|
|
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.
|
|
160
|
+
|
|
161
|
+
### External Variables Override Script Defaults
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
const formula = "multiplier = 2; value = 100; value * multiplier";
|
|
165
|
+
|
|
166
|
+
evaluate(formula); // → 200
|
|
167
|
+
evaluate(formula, { variables: { multiplier: 3 } }); // → 300
|
|
168
|
+
evaluate(formula, { variables: { value: 50 } }); // → 100
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Language Reference
|
|
172
|
+
|
|
173
|
+
For complete language documentation including all operators, control flow, and built-in functions, see [LANGUAGE.md](./LANGUAGE.md).
|
|
174
|
+
|
|
175
|
+
## API
|
|
176
|
+
|
|
177
|
+
### Main Functions
|
|
178
|
+
|
|
179
|
+
#### `evaluate(input: string | ASTNode, context?: ExecutionContext): RuntimeValue`
|
|
180
|
+
|
|
181
|
+
Evaluate an expression or AST and return the result.
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
evaluate("2 + 2"); // → 4
|
|
185
|
+
|
|
186
|
+
// Evaluate pre-parsed AST (parse once, evaluate many)
|
|
187
|
+
const ast = parse("price * quantity");
|
|
188
|
+
evaluate(ast, { variables: { price: 10, quantity: 5 } }); // → 50
|
|
189
|
+
evaluate(ast, { variables: { price: 20, quantity: 3 } }); // → 60
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
#### `evaluateScope(input: string | ASTNode, context?: ExecutionContext): Record<string, RuntimeValue>`
|
|
193
|
+
|
|
194
|
+
Evaluate and return all assigned variables as a record.
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
evaluateScope("x = 10; y = x * 2"); // → { x: 10, y: 20 }
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
#### `parse(source: string): ASTNode`
|
|
201
|
+
|
|
202
|
+
Parse source into an Abstract Syntax Tree without evaluating.
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
const ast = parse("2 + 3 * 4");
|
|
206
|
+
evaluate(ast); // → 14
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### `generate(node: ASTNode): string`
|
|
210
|
+
|
|
211
|
+
Convert AST back to source code (preserves comments).
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
generate(parse("2 + 3 * 4")); // → "2 + 3 * 4"
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
#### `optimize(node: ASTNode, externalVariables?: ReadonlySet<string>): ASTNode`
|
|
218
|
+
|
|
219
|
+
Optimize an AST with constant folding, constant propagation, and dead code elimination.
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
const ast = parse("2 + 3 * 4");
|
|
223
|
+
optimize(ast); // → NumberLiteral(14)
|
|
224
|
+
|
|
225
|
+
// 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
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
#### `extractInputVariables(ast: ASTNode): string[]`
|
|
231
|
+
|
|
232
|
+
Extract variable names assigned to constant values (useful for building UIs with input controls).
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
const ast = parse("price = 100; tax = price * 0.08");
|
|
236
|
+
extractInputVariables(ast); // → ["price"]
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### AST Visitor Pattern
|
|
240
|
+
|
|
241
|
+
#### `visit<T>(node: ASTNode, visitor: Visitor<T>): T`
|
|
242
|
+
|
|
243
|
+
Exhaustively visit every node in an AST. All 16 node types must be handled.
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
import { visit, parse } from "littlewing";
|
|
247
|
+
|
|
248
|
+
const count = visit(parse("2 + 3"), {
|
|
249
|
+
Program: (n, recurse) => n.statements.reduce((s, stmt) => s + recurse(stmt), 0),
|
|
250
|
+
NumberLiteral: () => 1,
|
|
251
|
+
StringLiteral: () => 1,
|
|
252
|
+
BooleanLiteral: () => 1,
|
|
253
|
+
ArrayLiteral: (n, recurse) => 1 + n.elements.reduce((s, el) => s + recurse(el), 0),
|
|
254
|
+
Identifier: () => 1,
|
|
255
|
+
BinaryOp: (n, recurse) => 1 + recurse(n.left) + recurse(n.right),
|
|
256
|
+
UnaryOp: (n, recurse) => 1 + recurse(n.argument),
|
|
257
|
+
Assignment: (n, recurse) => 1 + recurse(n.value),
|
|
258
|
+
FunctionCall: (n, recurse) => 1 + n.args.reduce((s, arg) => s + recurse(arg), 0),
|
|
259
|
+
IfExpression: (n, recurse) =>
|
|
260
|
+
1 + recurse(n.condition) + recurse(n.consequent) + recurse(n.alternate),
|
|
261
|
+
ForExpression: (n, recurse) =>
|
|
262
|
+
1 + recurse(n.iterable) + (n.guard ? recurse(n.guard) : 0) + recurse(n.body),
|
|
263
|
+
IndexAccess: (n, recurse) => 1 + recurse(n.object) + recurse(n.index),
|
|
264
|
+
RangeExpression: (n, recurse) => 1 + recurse(n.start) + recurse(n.end),
|
|
265
|
+
PipeExpression: (n, recurse) =>
|
|
266
|
+
1 + recurse(n.value) + n.args.reduce((s, arg) => s + recurse(arg), 0),
|
|
267
|
+
Placeholder: () => 1,
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
#### `visitPartial<T>(node, visitor, defaultHandler): T`
|
|
272
|
+
|
|
273
|
+
Visit only specific node types with a fallback for unhandled types.
|
|
274
|
+
|
|
275
|
+
### AST Builder Functions
|
|
276
|
+
|
|
277
|
+
The `ast` namespace provides builder functions for constructing AST nodes:
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import { ast, generate } from "littlewing";
|
|
281
|
+
|
|
282
|
+
generate(ast.add(ast.number(2), ast.number(3))); // → "2 + 3"
|
|
283
|
+
generate(ast.ifExpr(ast.boolean(true), ast.number(1), ast.number(0))); // → "if true then 1 else 0"
|
|
284
|
+
generate(
|
|
285
|
+
ast.forExpr(
|
|
286
|
+
"x",
|
|
287
|
+
ast.identifier("arr"),
|
|
288
|
+
null,
|
|
289
|
+
null,
|
|
290
|
+
ast.multiply(ast.identifier("x"), ast.number(2)),
|
|
291
|
+
),
|
|
292
|
+
);
|
|
293
|
+
// → "for x in arr then x * 2"
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Available builders:**
|
|
297
|
+
|
|
298
|
+
- Core: `program()`, `number()`, `string()`, `boolean()`, `array()`, `identifier()`, `binaryOp()`, `unaryOp()`, `functionCall()`, `assign()`, `ifExpr()`, `forExpr()`, `indexAccess()`, `rangeExpr()`, `pipeExpr()`, `placeholder()`
|
|
299
|
+
- Arithmetic: `add()`, `subtract()`, `multiply()`, `divide()`, `modulo()`, `exponentiate()`, `negate()`
|
|
300
|
+
- Comparison: `equals()`, `notEquals()`, `lessThan()`, `greaterThan()`, `lessEqual()`, `greaterEqual()`
|
|
301
|
+
- Logical: `logicalAnd()`, `logicalOr()`, `logicalNot()`
|
|
302
|
+
|
|
303
|
+
### ExecutionContext
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
interface ExecutionContext {
|
|
307
|
+
functions?: Record<string, (...args: RuntimeValue[]) => RuntimeValue>;
|
|
308
|
+
variables?: Record<string, RuntimeValue>;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
type RuntimeValue =
|
|
312
|
+
| number
|
|
313
|
+
| string
|
|
314
|
+
| boolean
|
|
315
|
+
| Temporal.PlainDate
|
|
316
|
+
| Temporal.PlainTime
|
|
317
|
+
| Temporal.PlainDateTime
|
|
318
|
+
| readonly RuntimeValue[];
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Default Context Functions
|
|
322
|
+
|
|
323
|
+
The `defaultContext` includes **85 built-in functions**:
|
|
324
|
+
|
|
325
|
+
**Type Conversion (3):** `STR`, `NUM`, `TYPE`
|
|
326
|
+
|
|
327
|
+
**Math (14):** `ABS`, `CEIL`, `FLOOR`, `ROUND`, `SQRT`, `MIN`, `MAX`, `CLAMP`, `SIN`, `COS`, `TAN`, `LOG`, `LOG10`, `EXP`
|
|
328
|
+
|
|
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`
|
|
330
|
+
|
|
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`
|
|
332
|
+
|
|
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`
|
|
334
|
+
|
|
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`
|
|
336
|
+
|
|
337
|
+
**DateTime (7):** `DATETIME`, `NOW`, `TO_DATE`, `TO_TIME`, `COMBINE`, `START_OF_DAY`, `END_OF_DAY`
|
|
338
|
+
|
|
339
|
+
**Temporal type support:** Date functions accept both `PlainDate` and `PlainDateTime` (preserving type). Time functions accept both `PlainTime` and `PlainDateTime`. Difference functions require both arguments to be the same type.
|
|
340
|
+
|
|
341
|
+
## Performance Optimization
|
|
342
|
+
|
|
343
|
+
### Parse Once, Evaluate Many
|
|
344
|
+
|
|
345
|
+
For expressions executed multiple times, parse once and reuse the AST:
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
import { evaluate, parse } from "littlewing";
|
|
349
|
+
|
|
350
|
+
const formula = parse("price * quantity * (1 - discount)");
|
|
351
|
+
|
|
352
|
+
evaluate(formula, { variables: { price: 10, quantity: 5, discount: 0.1 } }); // → 45
|
|
353
|
+
evaluate(formula, { variables: { price: 20, quantity: 3, discount: 0.15 } }); // → 51
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## Use Cases
|
|
357
|
+
|
|
358
|
+
- **User-defined formulas** — Let users write safe expressions
|
|
359
|
+
- **Business rules** — Express logic without `eval()` or `new Function()`
|
|
360
|
+
- **Financial calculators** — Compound interest, loan payments, etc.
|
|
361
|
+
- **Date arithmetic** — Deadlines, scheduling, date calculations
|
|
362
|
+
- **Data transformations** — Map, filter, and reduce arrays
|
|
363
|
+
- **Configuration expressions** — Dynamic config values
|
|
364
|
+
|
|
365
|
+
## Why Littlewing?
|
|
366
|
+
|
|
367
|
+
### The Problem
|
|
368
|
+
|
|
369
|
+
Your app needs to evaluate user-provided formulas or dynamic expressions. Using `eval()` is a security risk. Writing a parser is complex. Embedding a full scripting language is overkill.
|
|
370
|
+
|
|
371
|
+
### The Solution
|
|
372
|
+
|
|
373
|
+
Littlewing provides just enough: expressions with multiple types, variables, and functions. It's safe (no code execution), fast (linear time), and type-safe (no implicit coercion).
|
|
374
|
+
|
|
375
|
+
### What Makes It Different
|
|
376
|
+
|
|
377
|
+
1. **Multi-type with strict semantics** — Seven types, no implicit coercion, no surprises
|
|
378
|
+
2. **External variables override** — Scripts have defaults, runtime provides overrides
|
|
379
|
+
3. **Full Temporal support** — First-class `PlainDate`, `PlainTime`, and `PlainDateTime`
|
|
380
|
+
4. **Deep equality** — Arrays and dates compare by value
|
|
381
|
+
5. **O(n) everything** — Predictable performance at any scale
|
|
382
|
+
|
|
383
|
+
## Development
|
|
384
|
+
|
|
385
|
+
```bash
|
|
386
|
+
bun install # Install dependencies
|
|
387
|
+
bun test # Run tests
|
|
388
|
+
bun run build # Build
|
|
389
|
+
bun run --cwd packages/littlewing dev # Watch mode
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
For detailed development docs, see [CLAUDE.md](./CLAUDE.md).
|
|
393
|
+
|
|
394
|
+
## License
|
|
395
|
+
|
|
396
|
+
MIT
|
|
397
|
+
|
|
398
|
+
## Contributing
|
|
399
|
+
|
|
400
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "littlewing",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "A minimal, high-performance multi-type expression language with lexer, parser, and interpreter. Optimized for browsers with type-safe execution.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"calculator",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"test": "bun test",
|
|
52
52
|
"test:coverage": "bun test --coverage",
|
|
53
53
|
"test:watch": "bun test --watch",
|
|
54
|
+
"prepublishOnly": "cp ../../README.md ../../LICENSE .",
|
|
54
55
|
"release": "bumpp --commit --push --tag"
|
|
55
56
|
},
|
|
56
57
|
"devDependencies": {
|