littlewing 0.5.3 → 0.7.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,270 @@ 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
11
+ // Variables and functions
12
+ execute("radius = 5; area = 3.14159 * radius ^ 2", defaultContext); // → 78.54
34
13
 
35
- // Functions from context
36
- execute("abs(-42)", defaultContext); // → 42
37
- execute("sqrt(16)", defaultContext); // → 4
38
- ```
14
+ // Date arithmetic with timestamps
15
+ execute("deadline = NOW() + FROM_DAYS(7)", defaultContext); // → timestamp 7 days from now
39
16
 
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
17
+ // Conditional logic
18
+ execute("score = 85; grade = score >= 90 ? 100 : 90", {
19
+ variables: { score: 85 },
20
+ }); // 90
59
21
  ```
60
22
 
61
- ### Timestamp Arithmetic
62
-
63
- Littlewing uses a numbers-only type system. Timestamps (milliseconds since Unix epoch) are just numbers, enabling clean date arithmetic:
64
-
65
- ```typescript
66
- import { execute, defaultContext } from "littlewing";
67
-
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
95
- ```
96
-
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** - 6.15 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
32
+ - **99%+ test coverage** - 276 tests with 99.39% function coverage, 99.56% line coverage
101
33
 
102
- const expr = ast.add(ast.number(2), ast.multiply(ast.number(3), ast.number(4)));
34
+ ## Installation
103
35
 
104
- const executor = new Executor();
105
- executor.execute(expr); // → 14
36
+ ```bash
37
+ npm install littlewing
106
38
  ```
107
39
 
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
- ```
40
+ ## Quick Start
118
41
 
119
- ### Variables
42
+ ### Basic Usage
120
43
 
121
44
  ```typescript
122
- x = 5;
123
- y = x + 10;
124
- z = x * y;
125
- ```
126
-
127
- ### Operators
45
+ import { execute } from "littlewing";
128
46
 
129
- #### Arithmetic Operators
47
+ // Arithmetic expressions
48
+ execute("2 + 3 * 4"); // → 14
49
+ execute("10 ^ 2"); // → 100
50
+ execute("17 % 5"); // → 2
130
51
 
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
- ```
52
+ // Variables
53
+ execute("x = 10; y = 20; x + y"); // → 30
140
54
 
141
- #### Comparison Operators
55
+ // Comparisons (return 1 for true, 0 for false)
56
+ execute("5 > 3"); // → 1
57
+ execute("10 == 10"); // → 1
58
+ execute("2 != 2"); // → 0
142
59
 
143
- Returns `1` for true, `0` for false (following the numbers-only philosophy):
60
+ // Logical operators
61
+ execute("1 && 1"); // → 1
62
+ execute("0 || 1"); // → 1
144
63
 
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
64
+ // Ternary conditionals
65
+ execute("age >= 18 ? 100 : 0", { variables: { age: 21 } }); // → 100
152
66
  ```
153
67
 
154
- #### Logical Operators
155
-
156
- Returns `1` for true, `0` for false. Treats `0` as false, any non-zero value as true:
68
+ ### With Built-in Functions
157
69
 
158
70
  ```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
- ```
71
+ import { execute, defaultContext } from "littlewing";
170
72
 
171
- #### Ternary Operator
73
+ // Math functions
74
+ execute("ABS(-42)", defaultContext); // → 42
75
+ execute("SQRT(16)", defaultContext); // → 4
76
+ execute("MAX(3, 7, 2)", defaultContext); // → 7
172
77
 
173
- Conditional expression with `? :` syntax:
78
+ // Current timestamp
79
+ execute("NOW()", defaultContext); // → 1704067200000
174
80
 
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
81
+ // Date arithmetic
82
+ execute("NOW() + FROM_HOURS(2)", defaultContext); // → timestamp 2 hours from now
83
+ execute("tomorrow = NOW() + FROM_DAYS(1)", defaultContext); // → tomorrow's timestamp
179
84
 
180
- // Nested ternaries
181
- age < 18 ? 10 : age >= 65 ? 15 : 0; // age-based discount
182
- ```
85
+ // Extract date components
86
+ const ctx = { ...defaultContext, variables: { ts: Date.now() } };
87
+ execute("GET_YEAR(ts)", ctx); // → 2024
88
+ execute("GET_MONTH(ts)", ctx); // → 11
89
+ execute("GET_DAY(ts)", ctx); // → 6
183
90
 
184
- #### Assignment Operators
91
+ // Calculate time differences
92
+ const ts1 = Date.now();
93
+ const ts2 = ts1 + 1000 * 60 * 60 * 5; // 5 hours later
94
+ const context = { ...defaultContext, variables: { ts1, ts2 } };
95
+ execute("DIFFERENCE_IN_HOURS(ts1, ts2)", context); // → 5
185
96
 
186
- ```typescript
187
- x = 5; // regular assignment
188
- price ??= 100; // nullish assignment - only assigns if variable doesn't exist
97
+ // Date arithmetic and comparisons
98
+ execute("ADD_DAYS(NOW(), 7)", defaultContext); // 7 days from now
99
+ execute("START_OF_DAY(NOW())", defaultContext); // today at 00:00:00.000
100
+ execute("IS_WEEKEND(NOW())", defaultContext); // → 1 if today is Sat/Sun, else 0
189
101
  ```
190
102
 
191
- The `??=` operator is useful for providing default values to external variables:
103
+ ### Custom Functions and Variables
192
104
 
193
105
  ```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
- ```
106
+ import { execute } from "littlewing";
202
107
 
203
- Unlike `||`, the `??=` operator preserves `0` values:
108
+ const context = {
109
+ functions: {
110
+ // Custom functions must return numbers
111
+ fahrenheit: (celsius: number) => (celsius * 9) / 5 + 32,
112
+ discount: (price: number, percent: number) => price * (1 - percent / 100),
113
+ },
114
+ variables: {
115
+ pi: 3.14159,
116
+ taxRate: 0.08,
117
+ },
118
+ };
204
119
 
205
- ```typescript
206
- execute("x ??= 10; x", { variables: { x: 0 } }); // → 0 (preserves zero)
207
- execute("x ??= 10; x", {}); // → 10 (assigns default)
120
+ execute("fahrenheit(20)", context); // → 68
121
+ execute("discount(100, 15)", context); // → 85
122
+ execute("100 * (1 + taxRate)", context); // → 108
208
123
  ```
209
124
 
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:
125
+ ### External Variables Override Script Defaults
225
126
 
226
127
  ```typescript
227
- (2 + 3) * 4; // → 20 (not 14)
228
- 5 > 3 && 10 > 8; // 1 (explicit grouping, though not necessary)
229
- ```
128
+ // Scripts can define default values
129
+ const formula = "multiplier = 2; value = 100; value * multiplier";
230
130
 
231
- ### Functions
232
-
233
- Functions accept any number of arguments:
234
-
235
- ```typescript
236
- abs(-5); // → 5
237
- max(1, 5, 3); // → 5
238
- timestamp(2025, 1, 1); // → timestamp
239
- ```
131
+ // Without external variables: uses script defaults
132
+ execute(formula); // → 200
240
133
 
241
- ### Comments
134
+ // External variables override script assignments
135
+ execute(formula, { variables: { multiplier: 3 } }); // → 300
136
+ execute(formula, { variables: { value: 50 } }); // → 100
242
137
 
243
- Single-line comments with `//`:
138
+ // Useful for configurable formulas
139
+ const pricing = `
140
+ basePrice = 100;
141
+ taxRate = 0.08;
142
+ discount = 0;
143
+ finalPrice = basePrice * (1 - discount) * (1 + taxRate)
144
+ `;
244
145
 
245
- ```typescript
246
- x = 5; // this is a comment
247
- y = x + 10; // another comment
146
+ execute(pricing); // → 108 (uses all defaults)
147
+ execute(pricing, { variables: { discount: 0.1 } }); // 97.2 (10% discount)
148
+ execute(pricing, { variables: { basePrice: 200, discount: 0.2 } }); // 172.8
248
149
  ```
249
150
 
250
- ### Return Value
151
+ ## Language Reference
251
152
 
252
- The last expression is always returned:
153
+ For complete language documentation including all operators, functions, and examples, see [LANGUAGE.md](./LANGUAGE.md).
253
154
 
254
- ```typescript
255
- execute("x = 5; x + 10"); // → 15
256
- execute("42"); // → 42
257
- ```
258
-
259
- ## API Reference
155
+ ## API
260
156
 
261
157
  ### Main Functions
262
158
 
263
159
  #### `execute(source: string, context?: ExecutionContext): number`
264
160
 
265
- Execute source code with an optional execution context. Always returns a number.
161
+ Execute an expression and return the result.
266
162
 
267
163
  ```typescript
268
- execute("2 + 2");
269
- execute("abs(-5)", { functions: { abs: Math.abs } });
164
+ execute("2 + 2"); // → 4
165
+ execute("ABS(-5)", { functions: { ABS: Math.abs } }); // → 5
270
166
  ```
271
167
 
272
168
  #### `parseSource(source: string): ASTNode`
273
169
 
274
- Parse source code into an Abstract Syntax Tree without executing.
170
+ Parse source into an Abstract Syntax Tree without executing.
275
171
 
276
172
  ```typescript
277
173
  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
174
+ // Use with Executor class or optimize() function
297
175
  ```
298
176
 
299
177
  #### `optimize(node: ASTNode): ASTNode`
300
178
 
301
- Optimize an AST by performing constant folding. Evaluates expressions with only literals at compile-time.
179
+ Optimize an AST by folding constants. Safe for use with external variables.
302
180
 
303
181
  ```typescript
304
- import { optimize, parseSource } from "littlewing";
305
-
306
- // Parse first, then optimize
307
182
  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); // ??=
183
+ const optimized = optimize(ast); // → NumberLiteral(14)
401
184
 
402
- // Functions
403
- ast.functionCall("abs", [ast.number(-5)]);
185
+ // Variables are NOT folded (can be overridden by context)
186
+ const ast2 = parseSource("x = 5; x + 10");
187
+ const opt2 = optimize(ast2); // Still has variable reference
404
188
  ```
405
189
 
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";
444
-
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!
473
- ```
474
-
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
190
+ #### `generate(node: ASTNode): string`
521
191
 
522
- Littlewing supports scientific notation for large or small numbers:
192
+ Convert AST back to source code.
523
193
 
524
194
  ```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)
195
+ const ast = parseSource("2 + 3 * 4");
196
+ generate(ast); // → "2 + 3 * 4"
533
197
  ```
534
198
 
535
- ## Examples
536
-
537
- ### Calculator
199
+ ### ExecutionContext
538
200
 
539
201
  ```typescript
540
- import { execute, defaultContext } from "littlewing";
541
-
542
- function calculate(expression: string): number {
543
- return execute(expression, defaultContext);
202
+ interface ExecutionContext {
203
+ functions?: Record<string, (...args: any[]) => number>;
204
+ variables?: Record<string, number>;
544
205
  }
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
206
  ```
568
207
 
569
- ### Timestamp Arithmetic
208
+ ### Default Context Functions
570
209
 
571
- ```typescript
572
- import { execute, defaultContext } from "littlewing";
210
+ The `defaultContext` includes these built-in functions:
573
211
 
574
- // Calculate deadline
575
- const deadline = execute("now() + days(7)", defaultContext);
576
- const deadlineDate = new Date(deadline); // Convert to Date
212
+ **Math:** `ABS`, `CEIL`, `FLOOR`, `ROUND`, `SQRT`, `MIN`, `MAX`, `SIN`, `COS`, `TAN`, `LOG`, `LOG10`, `EXP`
577
213
 
578
- // Complex time calculations
579
- const result = execute("now() + weeks(2) + days(3) + hours(4)", defaultContext);
214
+ **Timestamps:** `NOW`, `DATE`
580
215
 
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
- ```
216
+ **Time converters (to milliseconds):** `FROM_SECONDS`, `FROM_MINUTES`, `FROM_HOURS`, `FROM_DAYS`, `FROM_WEEKS`, `FROM_MONTHS`, `FROM_YEARS`
589
217
 
590
- ### Custom Functions
218
+ **Date component extractors:** `GET_YEAR`, `GET_MONTH`, `GET_DAY`, `GET_HOUR`, `GET_MINUTE`, `GET_SECOND`, `GET_WEEKDAY`, `GET_MILLISECOND`, `GET_DAY_OF_YEAR`, `GET_QUARTER`
591
219
 
592
- ```typescript
593
- import { execute } from "littlewing";
220
+ **Time differences (always positive):** `DIFFERENCE_IN_SECONDS`, `DIFFERENCE_IN_MINUTES`, `DIFFERENCE_IN_HOURS`, `DIFFERENCE_IN_DAYS`, `DIFFERENCE_IN_WEEKS`
594
221
 
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
- ```
222
+ **Start/End of period:** `START_OF_DAY`, `END_OF_DAY`, `START_OF_WEEK`, `START_OF_MONTH`, `END_OF_MONTH`, `START_OF_YEAR`, `END_OF_YEAR`, `START_OF_QUARTER`
609
223
 
610
- ### Conditional Logic & Validation
224
+ **Date arithmetic:** `ADD_DAYS`, `ADD_MONTHS`, `ADD_YEARS`
611
225
 
612
- ```typescript
613
- import { execute } from "littlewing";
226
+ **Date comparisons:** `IS_BEFORE`, `IS_AFTER`, `IS_SAME_DAY`, `IS_WEEKEND`, `IS_LEAP_YEAR`
614
227
 
615
- // Age-based discount system
616
- const discountScript = `
617
- age ??= 30;
618
- isStudent ??= 0;
619
- isPremium ??= 0;
228
+ **Unix time:** `TO_UNIX_SECONDS`, `FROM_UNIX_SECONDS`
620
229
 
621
- discount = isPremium ? 0.2 :
622
- age < 18 ? 0.15 :
623
- age >= 65 ? 0.15 :
624
- isStudent ? 0.1 : 0;
230
+ ## Use Cases
625
231
 
626
- discount
627
- `;
232
+ - **User-defined formulas** - Let users write safe arithmetic expressions
233
+ - **Business rules** - Express logic without eval() or new Function()
234
+ - **Financial calculators** - Compound interest, loan payments, etc.
235
+ - **Date arithmetic** - Deadlines, scheduling, time calculations
236
+ - **Game mechanics** - Damage formulas, score calculations
237
+ - **Configuration expressions** - Dynamic config values
238
+ - **Data transformations** - Process numeric data streams
628
239
 
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
- `;
240
+ ## Why Littlewing?
653
241
 
654
- execute(eligibilityScript, {
655
- variables: { age: 25, income: 45000, creditScore: 750 },
656
- }); // → 1 (eligible)
657
- ```
242
+ ### The Problem
658
243
 
659
- ### Dynamic Pricing
244
+ 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.
660
245
 
661
- ```typescript
662
- import { execute } from "littlewing";
246
+ ### The Solution
663
247
 
664
- const pricingFormula = `
665
- // Defaults
666
- basePrice ??= 100;
667
- isPeakHour ??= 0;
668
- isWeekend ??= 0;
669
- quantity ??= 1;
670
- isMember ??= 0;
248
+ Littlewing provides just enough: arithmetic expressions with variables and functions. It's safe (no code execution), fast (linear time), and tiny (5KB gzipped).
671
249
 
672
- // Surge pricing
673
- surgeMultiplier = isPeakHour ? 1.5 : isWeekend ? 1.2 : 1.0;
250
+ ### What Makes It Different
674
251
 
675
- // Volume discount
676
- volumeDiscount = quantity >= 10 ? 0.15 :
677
- quantity >= 5 ? 0.1 :
678
- quantity >= 3 ? 0.05 : 0;
252
+ 1. **Numbers-only by design** - No string concatenation, no type coercion, no confusion
253
+ 2. **External variables override** - Scripts have defaults, runtime provides overrides
254
+ 3. **Timestamp arithmetic** - Dates are just numbers (milliseconds)
255
+ 4. **Zero dependencies** - No bloat, no supply chain risks
256
+ 5. **O(n) everything** - Predictable performance at any scale
679
257
 
680
- // Member discount (stacks with volume)
681
- memberDiscount = isMember ? 0.1 : 0;
258
+ ## Development
682
259
 
683
- // Calculate final price
684
- adjustedPrice = basePrice * surgeMultiplier;
685
- afterVolumeDiscount = adjustedPrice * (1 - volumeDiscount);
686
- finalPrice = afterVolumeDiscount * (1 - memberDiscount);
687
-
688
- finalPrice * quantity
689
- `;
690
-
691
- // Regular customer, 1 item
692
- execute(pricingFormula); // → 100
693
-
694
- // Peak hour, 5 items, member
695
- execute(pricingFormula, {
696
- variables: { isPeakHour: 1, quantity: 5, isMember: 1 },
697
- }); // → 607.5
698
-
699
- // Weekend, bulk order (10 items)
700
- execute(pricingFormula, {
701
- variables: { isWeekend: 1, quantity: 10 },
702
- }); // → 1020
703
- ```
260
+ ```bash
261
+ # Install dependencies
262
+ bun install
704
263
 
705
- ### Scheduling System
264
+ # Run tests
265
+ bun test
706
266
 
707
- ```typescript
708
- import { execute, defaultContext } from "littlewing";
267
+ # Build
268
+ bun run build
709
269
 
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
- }));
270
+ # Develop with watch mode
271
+ bun run dev
722
272
  ```
723
273
 
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.
274
+ For detailed development docs, see [CLAUDE.md](./CLAUDE.md).
788
275
 
789
276
  ## License
790
277
 
@@ -792,4 +279,4 @@ MIT
792
279
 
793
280
  ## Contributing
794
281
 
795
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.
282
+ See [CONTRIBUTING.md](./CONTRIBUTING.md).