littlewing 0.5.3 → 0.6.0

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
@@ -1,27 +1,6 @@
1
1
  # littlewing
2
2
 
3
- A minimal, high-performance arithmetic expression language with a complete lexer, parser, and executor. Optimized for browsers with **zero dependencies** and **type-safe execution**.
4
-
5
- ## Features
6
-
7
- - 🚀 **Minimal & Fast** - O(n) algorithms throughout (lexer, parser, executor)
8
- - 📦 **Small Bundle** - 6.89 KB gzipped, zero dependencies
9
- - 🌐 **Browser Ready** - 100% ESM, no Node.js APIs
10
- - 🔒 **Type-Safe** - Strict TypeScript with full type coverage
11
- - ✅ **Thoroughly Tested** - 247 tests, 98.61% line coverage
12
- - 📐 **Pure Arithmetic** - Numbers-only, clean semantics
13
- - 🎯 **Clean API** - Intuitive dual API (class-based + functional)
14
- - 📝 **Well Documented** - Complete JSDoc and examples
15
-
16
- ## Installation
17
-
18
- ```bash
19
- npm install littlewing
20
- ```
21
-
22
- ## Quick Start
23
-
24
- ### Basic Usage
3
+ A minimal, high-performance arithmetic expression language for JavaScript. Pure numbers, zero dependencies, built for the browser.
25
4
 
26
5
  ```typescript
27
6
  import { execute, defaultContext } from "littlewing";
@@ -29,762 +8,246 @@ import { execute, defaultContext } from "littlewing";
29
8
  // Simple arithmetic
30
9
  execute("2 + 3 * 4"); // → 14
31
10
 
32
- // Variables
33
- execute("x = 5; y = 10; x + y"); // → 15
34
-
35
- // Functions from context
36
- execute("abs(-42)", defaultContext); // → 42
37
- execute("sqrt(16)", defaultContext); // → 4
38
- ```
39
-
40
- ### With Custom Context
41
-
42
- ```typescript
43
- import { execute } from "littlewing";
44
-
45
- const context = {
46
- functions: {
47
- double: (n) => n * 2,
48
- triple: (n) => n * 3,
49
- },
50
- variables: {
51
- pi: 3.14159,
52
- maxValue: 100,
53
- },
54
- };
55
-
56
- execute("double(5)", context); // → 10
57
- execute("pi * 2", context); // → 6.28318
58
- execute("maxValue - 25", context); // → 75
59
- ```
60
-
61
- ### Timestamp Arithmetic
62
-
63
- Littlewing uses a numbers-only type system. Timestamps (milliseconds since Unix epoch) are just numbers, enabling clean date arithmetic:
11
+ // Variables and functions
12
+ execute("radius = 5; area = 3.14159 * radius ^ 2", defaultContext); // → 78.54
64
13
 
65
- ```typescript
66
- import { execute, defaultContext } from "littlewing";
14
+ // Date arithmetic with timestamps
15
+ execute("deadline = now() + days(7)", defaultContext); // → timestamp 7 days from now
67
16
 
68
- // Get current timestamp
69
- execute("now()", defaultContext); // 1704067200000 (number)
70
-
71
- // Create timestamp from date components
72
- execute("timestamp(2025, 10, 1)", defaultContext); // → timestamp for Oct 1, 2025
73
-
74
- // Add time durations (all return milliseconds)
75
- execute("now() + minutes(30)", defaultContext); // → timestamp 30 minutes from now
76
- execute("now() + hours(2) + minutes(15)", defaultContext); // → 2h 15m from now
77
-
78
- // Time conversion helpers
79
- execute("seconds(30)", defaultContext); // → 30000 (milliseconds)
80
- execute("minutes(5)", defaultContext); // → 300000 (milliseconds)
81
- execute("hours(2)", defaultContext); // → 7200000 (milliseconds)
82
- execute("days(7)", defaultContext); // → 604800000 (milliseconds)
83
- execute("weeks(2)", defaultContext); // → 1209600000 (milliseconds)
84
-
85
- // Extract components from timestamps
86
- const timestamp = new Date("2024-06-15T14:30:00").getTime();
87
- execute("year(t)", { ...defaultContext, variables: { t: timestamp } }); // → 2024
88
- execute("month(t)", { ...defaultContext, variables: { t: timestamp } }); // → 6 (June)
89
- execute("day(t)", { ...defaultContext, variables: { t: timestamp } }); // → 15
90
- execute("hour(t)", { ...defaultContext, variables: { t: timestamp } }); // → 14
91
-
92
- // Convert result back to Date when needed
93
- const result = execute("now() + days(7)", defaultContext);
94
- const futureDate = new Date(result); // JavaScript Date object
17
+ // Conditional logic
18
+ execute("score = 85; grade = score >= 90 ? 100 : 90", {
19
+ variables: { score: 85 },
20
+ }); // 90
95
21
  ```
96
22
 
97
- ### Manual AST Construction
23
+ ## Features
98
24
 
99
- ```typescript
100
- import { ast, Executor } from "littlewing";
25
+ - **Numbers-only** - Every value is a number. Simple, predictable, fast.
26
+ - **Zero dependencies** - 5.20 KB gzipped, perfect for browser bundles
27
+ - **O(n) performance** - Linear time parsing and execution
28
+ - **Safe evaluation** - No eval(), no code generation, no security risks
29
+ - **Timestamp arithmetic** - Built-in date/time functions using numeric timestamps
30
+ - **Extensible** - Add custom functions and variables via context
31
+ - **Type-safe** - Full TypeScript support with strict types
101
32
 
102
- const expr = ast.add(ast.number(2), ast.multiply(ast.number(3), ast.number(4)));
33
+ ## Installation
103
34
 
104
- const executor = new Executor();
105
- executor.execute(expr); // → 14
35
+ ```bash
36
+ npm install littlewing
106
37
  ```
107
38
 
108
- ## Language Syntax
109
-
110
- ### Literals
111
-
112
- ```typescript
113
- 42; // integer
114
- 3.14; // floating point
115
- 1.5e6; // scientific notation (1500000)
116
- 2e-3; // negative exponent (0.002)
117
- ```
39
+ ## Quick Start
118
40
 
119
- ### Variables
41
+ ### Basic Usage
120
42
 
121
43
  ```typescript
122
- x = 5;
123
- y = x + 10;
124
- z = x * y;
125
- ```
126
-
127
- ### Operators
44
+ import { execute } from "littlewing";
128
45
 
129
- #### Arithmetic Operators
46
+ // Arithmetic expressions
47
+ execute("2 + 3 * 4"); // → 14
48
+ execute("10 ^ 2"); // → 100
49
+ execute("17 % 5"); // → 2
130
50
 
131
- ```typescript
132
- 2 + 3; // addition
133
- 10 - 4; // subtraction
134
- 3 * 4; // multiplication
135
- 10 / 2; // division
136
- 10 % 3; // modulo
137
- 2 ^ 3; // exponentiation (power)
138
- -5; // unary minus
139
- ```
51
+ // Variables
52
+ execute("x = 10; y = 20; x + y"); // → 30
140
53
 
141
- #### Comparison Operators
54
+ // Comparisons (return 1 for true, 0 for false)
55
+ execute("5 > 3"); // → 1
56
+ execute("10 == 10"); // → 1
57
+ execute("2 != 2"); // → 0
142
58
 
143
- Returns `1` for true, `0` for false (following the numbers-only philosophy):
59
+ // Logical operators
60
+ execute("1 && 1"); // → 1
61
+ execute("0 || 1"); // → 1
144
62
 
145
- ```typescript
146
- 5 == 5; // equality 1
147
- 5 != 3; // not equal → 1
148
- 5 > 3; // greater than → 1
149
- 5 < 3; // less than → 0
150
- 5 >= 5; // greater than or equal → 1
151
- 5 <= 3; // less than or equal → 0
63
+ // Ternary conditionals
64
+ execute("age >= 18 ? 100 : 0", { variables: { age: 21 } }); // → 100
152
65
  ```
153
66
 
154
- #### Logical Operators
155
-
156
- Returns `1` for true, `0` for false. Treats `0` as false, any non-zero value as true:
67
+ ### With Built-in Functions
157
68
 
158
69
  ```typescript
159
- 1 && 1; // logical AND → 1 (both truthy)
160
- 1 && 0; // logical AND → 0 (right is falsy)
161
- 0 || 1; // logical OR → 1 (right is truthy)
162
- 0 || 0; // logical OR → 0 (both falsy)
163
-
164
- // Commonly used with comparisons
165
- 5 > 3 && 10 > 8; // → 1 (both conditions true)
166
- 5 < 3 || 10 > 8; // → 1 (second condition true)
167
- age >= 18 && age <= 65; // age range check
168
- isStudent || age >= 65; // student or senior discount
169
- ```
170
-
171
- #### Ternary Operator
172
-
173
- Conditional expression with `? :` syntax:
70
+ import { execute, defaultContext } from "littlewing";
174
71
 
175
- ```typescript
176
- 5 > 3 ? 100 : 50; // → 100 (condition is true)
177
- 0 ? 100 : 50; // → 50 (0 is falsy)
178
- x = age >= 18 ? 1 : 0; // assign based on condition
72
+ // Math functions
73
+ execute("abs(-42)", defaultContext); // → 42
74
+ execute("sqrt(16)", defaultContext); // → 4
75
+ execute("max(3, 7, 2)", defaultContext); // 7
179
76
 
180
- // Nested ternaries
181
- age < 18 ? 10 : age >= 65 ? 15 : 0; // age-based discount
182
- ```
77
+ // Current timestamp
78
+ execute("now()", defaultContext); // 1704067200000
183
79
 
184
- #### Assignment Operators
80
+ // Date arithmetic
81
+ execute("now() + hours(2)", defaultContext); // → timestamp 2 hours from now
82
+ execute("tomorrow = now() + days(1)", defaultContext); // → tomorrow's timestamp
185
83
 
186
- ```typescript
187
- x = 5; // regular assignment
188
- price ??= 100; // nullish assignment - only assigns if variable doesn't exist
84
+ // Extract date components
85
+ const ctx = { ...defaultContext, variables: { ts: Date.now() } };
86
+ execute("year(ts)", ctx); // 2024
87
+ execute("month(ts)", ctx); // → 11
88
+ execute("day(ts)", ctx); // → 6
189
89
  ```
190
90
 
191
- The `??=` operator is useful for providing default values to external variables:
91
+ ### Custom Functions and Variables
192
92
 
193
93
  ```typescript
194
- // Without ??=
195
- execute("price * 2", { variables: { price: 50 } }); // → 100
196
- execute("price * 2", {}); // Error: Undefined variable: price
197
-
198
- // With ??=
199
- execute("price ??= 100; price * 2", { variables: { price: 50 } }); // → 100 (uses existing)
200
- execute("price ??= 100; price * 2", {}); // → 200 (uses default)
201
- ```
94
+ import { execute } from "littlewing";
202
95
 
203
- Unlike `||`, the `??=` operator preserves `0` values:
96
+ const context = {
97
+ functions: {
98
+ // Custom functions must return numbers
99
+ fahrenheit: (celsius: number) => (celsius * 9) / 5 + 32,
100
+ discount: (price: number, percent: number) => price * (1 - percent / 100),
101
+ },
102
+ variables: {
103
+ pi: 3.14159,
104
+ taxRate: 0.08,
105
+ },
106
+ };
204
107
 
205
- ```typescript
206
- execute("x ??= 10; x", { variables: { x: 0 } }); // → 0 (preserves zero)
207
- execute("x ??= 10; x", {}); // → 10 (assigns default)
108
+ execute("fahrenheit(20)", context); // → 68
109
+ execute("discount(100, 15)", context); // → 85
110
+ execute("100 * (1 + taxRate)", context); // → 108
208
111
  ```
209
112
 
210
- ### Operator Precedence
211
-
212
- From lowest to highest:
213
-
214
- 1. Assignment (`=`, `??=`) - Lowest
215
- 2. Ternary conditional (`? :`)
216
- 3. Logical OR (`||`)
217
- 4. Logical AND (`&&`)
218
- 5. Comparison (`==`, `!=`, `<`, `>`, `<=`, `>=`)
219
- 6. Addition, subtraction (`+`, `-`)
220
- 7. Multiplication, division, modulo (`*`, `/`, `%`)
221
- 8. Exponentiation (`^`)
222
- 9. Unary minus (`-`) - Highest
223
-
224
- Parentheses override precedence:
113
+ ### External Variables Override Script Defaults
225
114
 
226
115
  ```typescript
227
- (2 + 3) * 4; // → 20 (not 14)
228
- 5 > 3 && 10 > 8; // 1 (explicit grouping, though not necessary)
229
- ```
116
+ // Scripts can define default values
117
+ const formula = "multiplier = 2; value = 100; value * multiplier";
230
118
 
231
- ### Functions
119
+ // Without external variables: uses script defaults
120
+ execute(formula); // → 200
232
121
 
233
- Functions accept any number of arguments:
122
+ // External variables override script assignments
123
+ execute(formula, { variables: { multiplier: 3 } }); // → 300
124
+ execute(formula, { variables: { value: 50 } }); // → 100
234
125
 
235
- ```typescript
236
- abs(-5); // 5
237
- max(1, 5, 3); // → 5
238
- timestamp(2025, 1, 1); // → timestamp
239
- ```
240
-
241
- ### Comments
242
-
243
- Single-line comments with `//`:
126
+ // Useful for configurable formulas
127
+ const pricing = `
128
+ basePrice = 100;
129
+ taxRate = 0.08;
130
+ discount = 0;
131
+ finalPrice = basePrice * (1 - discount) * (1 + taxRate)
132
+ `;
244
133
 
245
- ```typescript
246
- x = 5; // this is a comment
247
- y = x + 10; // another comment
134
+ execute(pricing); // → 108 (uses all defaults)
135
+ execute(pricing, { variables: { discount: 0.1 } }); // 97.2 (10% discount)
136
+ execute(pricing, { variables: { basePrice: 200, discount: 0.2 } }); // 172.8
248
137
  ```
249
138
 
250
- ### Return Value
251
-
252
- The last expression is always returned:
139
+ ## Language Reference
253
140
 
254
- ```typescript
255
- execute("x = 5; x + 10"); // → 15
256
- execute("42"); // → 42
257
- ```
141
+ For complete language documentation including all operators, functions, and examples, see [LANGUAGE.md](./LANGUAGE.md).
258
142
 
259
- ## API Reference
143
+ ## API
260
144
 
261
145
  ### Main Functions
262
146
 
263
147
  #### `execute(source: string, context?: ExecutionContext): number`
264
148
 
265
- Execute source code with an optional execution context. Always returns a number.
149
+ Execute an expression and return the result.
266
150
 
267
151
  ```typescript
268
- execute("2 + 2");
269
- execute("abs(-5)", { functions: { abs: Math.abs } });
152
+ execute("2 + 2"); // → 4
153
+ execute("abs(-5)", { functions: { abs: Math.abs } }); // → 5
270
154
  ```
271
155
 
272
156
  #### `parseSource(source: string): ASTNode`
273
157
 
274
- Parse source code into an Abstract Syntax Tree without executing.
158
+ Parse source into an Abstract Syntax Tree without executing.
275
159
 
276
160
  ```typescript
277
161
  const ast = parseSource("2 + 3 * 4");
278
- // Returns: BinaryOp(+, NumberLiteral(2), BinaryOp(*, ...))
279
- ```
280
-
281
- #### `generate(node: ASTNode): string`
282
-
283
- Convert an AST node back to source code. Intelligently adds parentheses only when necessary to preserve semantics.
284
-
285
- ```typescript
286
- import { generate, ast } from "littlewing";
287
-
288
- // From AST builders
289
- const expr = ast.multiply(ast.add(ast.number(2), ast.number(3)), ast.number(4));
290
- generate(expr); // → "(2 + 3) * 4"
291
-
292
- // Round-trip: parse → generate → parse
293
- const code = "2 + 3 * 4";
294
- const tree = parseSource(code);
295
- const regenerated = generate(tree); // → "2 + 3 * 4"
296
- parseSource(regenerated); // Same AST structure
162
+ // Use with Executor class or optimize() function
297
163
  ```
298
164
 
299
165
  #### `optimize(node: ASTNode): ASTNode`
300
166
 
301
- Optimize an AST by performing constant folding. Evaluates expressions with only literals at compile-time.
167
+ Optimize an AST by folding constants. Safe for use with external variables.
302
168
 
303
169
  ```typescript
304
- import { optimize, parseSource } from "littlewing";
305
-
306
- // Parse first, then optimize
307
170
  const ast = parseSource("2 + 3 * 4");
308
- const optimized = optimize(ast);
309
- // Transforms BinaryOp tree to NumberLiteral(14)
310
-
311
- // Useful for storing compact ASTs
312
- const compactAst = optimize(parseSource("1e6 + 2e6"));
313
- // → NumberLiteral(3000000)
314
- ```
315
-
316
- ### Classes
317
-
318
- #### `Lexer`
319
-
320
- Tokenize source code into a token stream.
321
-
322
- ```typescript
323
- import { Lexer, TokenType } from "littlewing";
324
-
325
- const lexer = new Lexer("x = 42");
326
- const tokens = lexer.tokenize();
327
- // → [Identifier('x'), Equals, Number(42), EOF]
328
- ```
329
-
330
- #### `Parser`
331
-
332
- Parse tokens into an AST.
333
-
334
- ```typescript
335
- import { Parser } from "littlewing";
336
-
337
- const parser = new Parser(tokens);
338
- const ast = parser.parse();
339
- ```
340
-
341
- #### `Executor`
342
-
343
- Execute an AST with a given context.
344
-
345
- ```typescript
346
- import { Executor } from "littlewing";
347
-
348
- const executor = new Executor(context);
349
- const result = executor.execute(ast);
350
- ```
351
-
352
- #### `CodeGenerator`
353
-
354
- Convert AST nodes back to source code. Handles operator precedence and associativity automatically.
355
-
356
- ```typescript
357
- import { CodeGenerator } from "littlewing";
358
-
359
- const generator = new CodeGenerator();
360
- const code = generator.generate(ast);
361
- ```
362
-
363
- ### AST Builders
364
-
365
- The `ast` namespace provides convenient functions for building AST nodes:
366
-
367
- ```typescript
368
- import { ast } from "littlewing";
369
-
370
- // Literals and identifiers
371
- ast.number(42);
372
- ast.identifier("x");
373
-
374
- // Arithmetic operators
375
- ast.add(left, right);
376
- ast.subtract(left, right);
377
- ast.multiply(left, right);
378
- ast.divide(left, right);
379
- ast.modulo(left, right);
380
- ast.exponentiate(left, right);
381
- ast.negate(argument);
382
-
383
- // Comparison operators
384
- ast.equals(left, right); // ==
385
- ast.notEquals(left, right); // !=
386
- ast.lessThan(left, right); // <
387
- ast.greaterThan(left, right); // >
388
- ast.lessEqual(left, right); // <=
389
- ast.greaterEqual(left, right); // >=
390
-
391
- // Logical operators
392
- ast.logicalAnd(left, right); // &&
393
- ast.logicalOr(left, right); // ||
394
-
395
- // Control flow
396
- ast.conditional(condition, consequent, alternate); // ? :
397
-
398
- // Assignment
399
- ast.assign("x", value); // =
400
- ast.nullishAssign("x", value); // ??=
401
-
402
- // Functions
403
- ast.functionCall("abs", [ast.number(-5)]);
404
- ```
405
-
406
- ### Default Context
407
-
408
- The `defaultContext` provides a comprehensive set of built-in functions:
409
-
410
- ```typescript
411
- import { defaultContext } from "littlewing";
412
-
413
- // Math functions
414
- (abs, ceil, floor, round, sqrt, min, max);
415
- (sin, cos, tan, log, log10, exp);
416
-
417
- // Timestamp functions
418
- now(); // Current timestamp
419
- timestamp(year, month, day); // Create timestamp from date components
420
-
421
- // Time conversion (returns milliseconds)
422
- (milliseconds(n), seconds(n), minutes(n), hours(n), days(n), weeks(n));
423
-
424
- // Timestamp component extractors
425
- year(timestamp); // Extract year (e.g., 2024)
426
- month(timestamp); // Extract month (1-12, 1 = January)
427
- day(timestamp); // Extract day of month (1-31)
428
- hour(timestamp); // Extract hour (0-23)
429
- minute(timestamp); // Extract minute (0-59)
430
- second(timestamp); // Extract second (0-59)
431
- weekday(timestamp); // Extract day of week (0-6, 0 = Sunday)
432
- ```
433
-
434
- ## Advanced Features
435
-
436
- ### Advanced Optimization
437
-
438
- The `optimize()` function implements a **production-grade, O(n) optimization algorithm** that achieves maximum AST compaction through constant propagation and dead code elimination.
439
-
440
- #### Simple Example
441
-
442
- ```typescript
443
- import { optimize, parseSource } from "littlewing";
171
+ const optimized = optimize(ast); // → NumberLiteral(14)
444
172
 
445
- // Basic constant folding
446
- const ast = optimize(parseSource("2 + 3 * 4"));
447
- // Result: NumberLiteral(14) - reduced from 3 nodes to 1!
448
-
449
- // Transitive constant propagation
450
- const ast2 = optimize(parseSource("x = 5; y = x + 10; y * 2"));
451
- // Result: NumberLiteral(30) - fully evaluated!
452
- ```
453
-
454
- #### Complex Example
455
-
456
- ```typescript
457
- import { optimize, parseSource } from "littlewing";
458
-
459
- const source = `
460
- principal = 1000;
461
- rate = 0.05;
462
- years = 10;
463
- n = 12;
464
- base = 1 + (rate / n);
465
- exponent = n * years;
466
- result = principal * (base ^ exponent);
467
- result
468
- `;
469
-
470
- const optimized = optimize(parseSource(source));
471
- // Result: NumberLiteral(1647.0095406619717)
472
- // Reduced from 8 statements (40+ nodes) to a single literal!
173
+ // Variables are NOT folded (can be overridden by context)
174
+ const ast2 = parseSource("x = 5; x + 10");
175
+ const opt2 = optimize(ast2); // Still has variable reference
473
176
  ```
474
177
 
475
- #### How It Works
476
-
477
- The optimizer uses a three-phase algorithm inspired by compiler optimization theory:
478
-
479
- 1. **Program Analysis** (O(n))
480
- - Builds dependency graph between variables
481
- - Identifies constants and tainted expressions
482
- - Performs topological sorting for evaluation order
483
-
484
- 2. **Constant Propagation** (O(n))
485
- - Evaluates constants in dependency order
486
- - Propagates values transitively (a = 5; b = a + 10 → b = 15)
487
- - Replaces variable references with computed values
488
-
489
- 3. **Dead Code Elimination** (O(n))
490
- - Removes unused assignments
491
- - Eliminates fully-propagated variables
492
- - Unwraps single-value programs
493
-
494
- **Time complexity:** O(n) guaranteed - no iteration, single pass through AST
495
-
496
- #### What Gets Optimized
497
-
498
- ✅ **Constant folding:** `2 + 3 * 4` → `14`
499
- ✅ **Variable propagation:** `x = 5; x + 10` → `15`
500
- ✅ **Transitive evaluation:** `a = 5; b = a + 10; b * 2` → `30`
501
- ✅ **Chained computations:** Multi-statement programs fully evaluated
502
- ✅ **Dead code elimination:** Unused variables removed
503
- ✅ **Scientific notation:** `1e6 + 2e6` → `3000000`
504
-
505
- #### What Stays (Correctly)
506
-
507
- ❌ **External variables:** Variables from `ExecutionContext`
508
- ❌ **Function calls:** `sqrt(16)`, `now()` (runtime behavior)
509
- ❌ **Reassigned variables:** `x = 5; x = 10; x` (not constant)
510
- ❌ **Tainted expressions:** Depend on function calls or external values
511
-
512
- #### When to Use
513
-
514
- - **Storage:** Compact ASTs for databases (87% size reduction typical)
515
- - **Performance:** Faster execution, pre-calculate once
516
- - **Network:** Smaller payload for transmitted ASTs
517
- - **Caching:** Store optimized expressions for repeated evaluation
518
- - **Build tools:** Optimize configuration files at compile time
519
-
520
- ### Scientific Notation
178
+ #### `generate(node: ASTNode): string`
521
179
 
522
- Littlewing supports scientific notation for large or small numbers:
180
+ Convert AST back to source code.
523
181
 
524
182
  ```typescript
525
- execute("1.5e6"); // → 1500000
526
- execute("2e10"); // → 20000000000
527
- execute("3e-2"); // → 0.03
528
- execute("4E+5"); // → 400000
529
-
530
- // Works with optimization too
531
- const ast = parseSource("1e6 * 2", { optimize: true });
532
- // → NumberLiteral(2000000)
183
+ const ast = parseSource("2 + 3 * 4");
184
+ generate(ast); // → "2 + 3 * 4"
533
185
  ```
534
186
 
535
- ## Examples
536
-
537
- ### Calculator
187
+ ### ExecutionContext
538
188
 
539
189
  ```typescript
540
- import { execute, defaultContext } from "littlewing";
541
-
542
- function calculate(expression: string): number {
543
- return execute(expression, defaultContext);
190
+ interface ExecutionContext {
191
+ functions?: Record<string, (...args: any[]) => number>;
192
+ variables?: Record<string, number>;
544
193
  }
545
-
546
- calculate("2 + 2 * 3"); // → 8
547
- calculate("(2 + 2) * 3"); // → 12
548
- calculate("sqrt(16) + abs(-5)"); // → 9
549
- ```
550
-
551
- ### Financial Calculations
552
-
553
- ```typescript
554
- import { execute } from "littlewing";
555
-
556
- const context = {
557
- functions: {},
558
- variables: {
559
- principal: 1000,
560
- rate: 0.05,
561
- years: 2,
562
- },
563
- };
564
-
565
- const compound = execute("principal * (1 + rate) ^ years", context);
566
- // → 1102.5
567
- ```
568
-
569
- ### Timestamp Arithmetic
570
-
571
- ```typescript
572
- import { execute, defaultContext } from "littlewing";
573
-
574
- // Calculate deadline
575
- const deadline = execute("now() + days(7)", defaultContext);
576
- const deadlineDate = new Date(deadline); // Convert to Date
577
-
578
- // Complex time calculations
579
- const result = execute("now() + weeks(2) + days(3) + hours(4)", defaultContext);
580
-
581
- // Time until event
582
- const eventTime = new Date("2025-12-31").getTime();
583
- const timeUntil = execute("event - now()", {
584
- ...defaultContext,
585
- variables: { event: eventTime },
586
- });
587
- const daysUntil = timeUntil / (1000 * 60 * 60 * 24);
588
- ```
589
-
590
- ### Custom Functions
591
-
592
- ```typescript
593
- import { execute } from "littlewing";
594
-
595
- const context = {
596
- functions: {
597
- fahrenheit: (celsius) => (celsius * 9) / 5 + 32,
598
- kilometers: (miles) => miles * 1.60934,
599
- factorial: (n) => (n <= 1 ? 1 : n * context.functions.factorial(n - 1)),
600
- },
601
- variables: {
602
- roomTemp: 20,
603
- },
604
- };
605
-
606
- execute("fahrenheit(roomTemp)", context); // → 68
607
- execute("kilometers(5)", context); // → 8.0467
608
194
  ```
609
195
 
610
- ### Conditional Logic & Validation
611
-
612
- ```typescript
613
- import { execute } from "littlewing";
614
-
615
- // Age-based discount system
616
- const discountScript = `
617
- age ??= 30;
618
- isStudent ??= 0;
619
- isPremium ??= 0;
196
+ ### Default Context Functions
620
197
 
621
- discount = isPremium ? 0.2 :
622
- age < 18 ? 0.15 :
623
- age >= 65 ? 0.15 :
624
- isStudent ? 0.1 : 0;
198
+ The `defaultContext` includes these built-in functions:
625
199
 
626
- discount
627
- `;
200
+ **Math:** `abs`, `ceil`, `floor`, `round`, `sqrt`, `min`, `max`, `sin`, `cos`, `tan`, `log`, `log10`, `exp`
628
201
 
629
- execute(discountScript); // 0 (default 30-year-old)
630
- execute(discountScript, { variables: { age: 16 } }); // → 0.15 (under 18)
631
- execute(discountScript, { variables: { isPremium: 1 } }); // → 0.2 (premium)
632
- execute(discountScript, { variables: { isStudent: 1 } }); // → 0.1 (student)
633
-
634
- // Range validation
635
- const validateAge = "age >= 18 && age <= 120";
636
- execute(validateAge, { variables: { age: 25 } }); // → 1 (valid)
637
- execute(validateAge, { variables: { age: 15 } }); // → 0 (too young)
638
- execute(validateAge, { variables: { age: 150 } }); // → 0 (invalid)
639
-
640
- // Complex business logic
641
- const eligibilityScript = `
642
- age ??= 0;
643
- income ??= 0;
644
- creditScore ??= 0;
645
-
646
- hasGoodCredit = creditScore >= 700;
647
- hasStableIncome = income >= 30000;
648
- isAdult = age >= 18;
649
-
650
- eligible = isAdult && hasGoodCredit && hasStableIncome;
651
- eligible
652
- `;
202
+ **Time:** `now`, `timestamp`, `milliseconds`, `seconds`, `minutes`, `hours`, `days`, `weeks`
653
203
 
654
- execute(eligibilityScript, {
655
- variables: { age: 25, income: 45000, creditScore: 750 },
656
- }); // → 1 (eligible)
657
- ```
204
+ **Date components:** `year`, `month`, `day`, `hour`, `minute`, `second`, `weekday`
658
205
 
659
- ### Dynamic Pricing
206
+ ## Use Cases
660
207
 
661
- ```typescript
662
- import { execute } from "littlewing";
208
+ - **User-defined formulas** - Let users write safe arithmetic expressions
209
+ - **Business rules** - Express logic without eval() or new Function()
210
+ - **Financial calculators** - Compound interest, loan payments, etc.
211
+ - **Date arithmetic** - Deadlines, scheduling, time calculations
212
+ - **Game mechanics** - Damage formulas, score calculations
213
+ - **Configuration expressions** - Dynamic config values
214
+ - **Data transformations** - Process numeric data streams
663
215
 
664
- const pricingFormula = `
665
- // Defaults
666
- basePrice ??= 100;
667
- isPeakHour ??= 0;
668
- isWeekend ??= 0;
669
- quantity ??= 1;
670
- isMember ??= 0;
216
+ ## Why Littlewing?
671
217
 
672
- // Surge pricing
673
- surgeMultiplier = isPeakHour ? 1.5 : isWeekend ? 1.2 : 1.0;
218
+ ### The Problem
674
219
 
675
- // Volume discount
676
- volumeDiscount = quantity >= 10 ? 0.15 :
677
- quantity >= 5 ? 0.1 :
678
- quantity >= 3 ? 0.05 : 0;
220
+ 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.
679
221
 
680
- // Member discount (stacks with volume)
681
- memberDiscount = isMember ? 0.1 : 0;
222
+ ### The Solution
682
223
 
683
- // Calculate final price
684
- adjustedPrice = basePrice * surgeMultiplier;
685
- afterVolumeDiscount = adjustedPrice * (1 - volumeDiscount);
686
- finalPrice = afterVolumeDiscount * (1 - memberDiscount);
224
+ Littlewing provides just enough: arithmetic expressions with variables and functions. It's safe (no code execution), fast (linear time), and tiny (5KB gzipped).
687
225
 
688
- finalPrice * quantity
689
- `;
226
+ ### What Makes It Different
690
227
 
691
- // Regular customer, 1 item
692
- execute(pricingFormula); // 100
228
+ 1. **Numbers-only by design** - No string concatenation, no type coercion, no confusion
229
+ 2. **External variables override** - Scripts have defaults, runtime provides overrides
230
+ 3. **Timestamp arithmetic** - Dates are just numbers (milliseconds)
231
+ 4. **Zero dependencies** - No bloat, no supply chain risks
232
+ 5. **O(n) everything** - Predictable performance at any scale
693
233
 
694
- // Peak hour, 5 items, member
695
- execute(pricingFormula, {
696
- variables: { isPeakHour: 1, quantity: 5, isMember: 1 },
697
- }); // → 607.5
234
+ ## Development
698
235
 
699
- // Weekend, bulk order (10 items)
700
- execute(pricingFormula, {
701
- variables: { isWeekend: 1, quantity: 10 },
702
- }); // → 1020
703
- ```
236
+ ```bash
237
+ # Install dependencies
238
+ bun install
704
239
 
705
- ### Scheduling System
240
+ # Run tests
241
+ bun test
706
242
 
707
- ```typescript
708
- import { execute, defaultContext } from "littlewing";
243
+ # Build
244
+ bun run build
709
245
 
710
- // Parse user's relative time expressions
711
- const tasks = [
712
- { name: "Review PR", due: "now() + hours(2)" },
713
- { name: "Deploy", due: "now() + days(1)" },
714
- { name: "Meeting", due: "timestamp(2025, 10, 15, 14, 30, 0)" },
715
- ];
716
-
717
- const dueTimes = tasks.map((task) => ({
718
- name: task.name,
719
- dueTimestamp: execute(task.due, defaultContext),
720
- dueDate: new Date(execute(task.due, defaultContext)),
721
- }));
246
+ # Develop with watch mode
247
+ bun run dev
722
248
  ```
723
249
 
724
- ## Performance
725
-
726
- ### Algorithms
727
-
728
- - **Lexer**: O(n) single-pass tokenization
729
- - **Parser**: Optimal Pratt parsing with O(n) time complexity
730
- - **Executor**: O(n) tree-walk evaluation with no type checking overhead
731
-
732
- ### Bundle Size
733
-
734
- - **6.89 KB gzipped** (37.66 KB raw)
735
- - Zero dependencies
736
- - Includes production-grade O(n) optimizer
737
- - Full feature set: arithmetic, comparisons, logical operators, ternary, assignments
738
- - Fully tree-shakeable
739
-
740
- ### Test Coverage
741
-
742
- - **247 tests** with **98.61% line coverage**
743
- - **98.21% function coverage**
744
- - Comprehensive coverage of all operators and features
745
- - All edge cases handled
746
- - Type-safe execution guaranteed
747
-
748
- ## Type Safety
749
-
750
- - Strict TypeScript mode
751
- - Zero implicit `any` types
752
- - Complete type annotations
753
- - Single `RuntimeValue = number` type
754
- - No runtime type checking overhead
755
-
756
- ## Error Handling
757
-
758
- Clear, actionable error messages for:
759
-
760
- - Undefined variables: `"Undefined variable: x"`
761
- - Undefined functions: `"Undefined function: abs"`
762
- - Division by zero: `"Division by zero"`
763
- - Modulo by zero: `"Modulo by zero"`
764
- - Syntax errors with position information
765
-
766
- ## Browser Support
767
-
768
- - ✅ All modern browsers (ES2023+)
769
- - ✅ No polyfills required
770
- - ✅ Tree-shakeable for optimal bundle sizes
771
- - ✅ 100% ESM, no CommonJS
772
-
773
- ## Node.js Support
774
-
775
- Works with Node.js 18+ via ESM imports.
776
-
777
- ## Philosophy
778
-
779
- Littlewing embraces a **numbers-only** type system for maximum simplicity and performance:
780
-
781
- - **Pure arithmetic**: Every operation works on numbers
782
- - **No type checking overhead**: Operators don't need runtime type discrimination
783
- - **Timestamps as numbers**: Date arithmetic uses millisecond timestamps
784
- - **Clean semantics**: No ambiguous operations like `Date + Date`
785
- - **Flexibility**: Convert to/from JavaScript Dates at the boundaries
786
-
787
- This design keeps the language minimal while remaining powerful enough for real-world use cases.
250
+ For detailed development docs, see [CLAUDE.md](./CLAUDE.md).
788
251
 
789
252
  ## License
790
253
 
@@ -792,4 +255,4 @@ MIT
792
255
 
793
256
  ## Contributing
794
257
 
795
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.
258
+ See [CONTRIBUTING.md](./CONTRIBUTING.md).