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.
Files changed (3) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +400 -0
  3. 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.0",
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": {