littlewing 0.9.5 → 1.0.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.
Files changed (4) hide show
  1. package/README.md +175 -114
  2. package/dist/index.d.ts +10 -342
  3. package/dist/index.js +185 -193
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -3,19 +3,19 @@
3
3
  A minimal, high-performance arithmetic expression language for JavaScript. Pure numbers, zero dependencies, built for the browser.
4
4
 
5
5
  ```typescript
6
- import { execute, defaultContext } from "littlewing";
6
+ import { evaluate, defaultContext } from "littlewing";
7
7
 
8
8
  // Simple arithmetic
9
- execute("2 + 3 * 4"); // → 14
9
+ evaluate("2 + 3 * 4"); // → 14
10
10
 
11
11
  // Variables and functions
12
- execute("radius = 5; area = 3.14159 * radius ^ 2", defaultContext); // → 78.54
12
+ evaluate("radius = 5; area = 3.14159 * radius ^ 2", defaultContext); // → 78.54
13
13
 
14
14
  // Date arithmetic with timestamps
15
- execute("deadline = NOW() + FROM_DAYS(7)", defaultContext); // → timestamp 7 days from now
15
+ evaluate("deadline = NOW() + FROM_DAYS(7)", defaultContext); // → timestamp 7 days from now
16
16
 
17
17
  // Conditional logic
18
- execute("score = 85; grade = score >= 90 ? 100 : 90", {
18
+ evaluate("score = 85; grade = score >= 90 ? 100 : 90", {
19
19
  variables: { score: 85 },
20
20
  }); // → 90
21
21
  ```
@@ -29,7 +29,7 @@ execute("score = 85; grade = score >= 90 ? 100 : 90", {
29
29
  - **Timestamp arithmetic** - Built-in date/time functions using numeric timestamps
30
30
  - **Extensible** - Add custom functions and variables via context
31
31
  - **Type-safe** - Full TypeScript support with strict types
32
- - **Excellent test coverage** - 485 tests with 99.45% function coverage, 98.57% line coverage
32
+ - **Excellent test coverage** - 502 tests with 99.45% function coverage, 98.57% line coverage
33
33
 
34
34
  ## Installation
35
35
 
@@ -42,77 +42,77 @@ npm install littlewing
42
42
  ### Basic Usage
43
43
 
44
44
  ```typescript
45
- import { execute } from "littlewing";
45
+ import { evaluate } from "littlewing";
46
46
 
47
47
  // Arithmetic expressions
48
- execute("2 + 3 * 4"); // → 14
49
- execute("10 ^ 2"); // → 100
50
- execute("17 % 5"); // → 2
48
+ evaluate("2 + 3 * 4"); // → 14
49
+ evaluate("10 ^ 2"); // → 100
50
+ evaluate("17 % 5"); // → 2
51
51
 
52
52
  // Variables
53
- execute("x = 10; y = 20; x + y"); // → 30
53
+ evaluate("x = 10; y = 20; x + y"); // → 30
54
54
 
55
55
  // Comparisons (return 1 for true, 0 for false)
56
- execute("5 > 3"); // → 1
57
- execute("10 == 10"); // → 1
58
- execute("2 != 2"); // → 0
56
+ evaluate("5 > 3"); // → 1
57
+ evaluate("10 == 10"); // → 1
58
+ evaluate("2 != 2"); // → 0
59
59
 
60
60
  // Logical operators
61
- execute("!0"); // → 1 (NOT)
62
- execute("1 && 1"); // → 1 (AND)
63
- execute("0 || 1"); // → 1 (OR)
64
- execute("!(5 > 10)"); // → 1 (negates comparison)
61
+ evaluate("!0"); // → 1 (NOT)
62
+ evaluate("1 && 1"); // → 1 (AND)
63
+ evaluate("0 || 1"); // → 1 (OR)
64
+ evaluate("!(5 > 10)"); // → 1 (negates comparison)
65
65
 
66
66
  // Ternary conditionals
67
- execute("age >= 18 ? 100 : 0", { variables: { age: 21 } }); // → 100
68
- execute("!isBlocked ? 100 : 0", { variables: { isBlocked: 0 } }); // → 100
67
+ evaluate("age >= 18 ? 100 : 0", { variables: { age: 21 } }); // → 100
68
+ evaluate("!isBlocked ? 100 : 0", { variables: { isBlocked: 0 } }); // → 100
69
69
  ```
70
70
 
71
71
  ### With Built-in Functions
72
72
 
73
73
  ```typescript
74
- import { execute, defaultContext } from "littlewing";
74
+ import { evaluate, defaultContext } from "littlewing";
75
75
 
76
76
  // Math functions
77
- execute("ABS(-42)", defaultContext); // → 42
78
- execute("SQRT(16)", defaultContext); // → 4
79
- execute("MAX(3, 7, 2)", defaultContext); // → 7
77
+ evaluate("ABS(-42)", defaultContext); // → 42
78
+ evaluate("SQRT(16)", defaultContext); // → 4
79
+ evaluate("MAX(3, 7, 2)", defaultContext); // → 7
80
80
 
81
81
  // Current timestamp
82
- execute("NOW()", defaultContext); // → 1704067200000
82
+ evaluate("NOW()", defaultContext); // → 1704067200000
83
83
 
84
84
  // Date arithmetic
85
- execute("NOW() + FROM_HOURS(2)", defaultContext); // → timestamp 2 hours from now
86
- execute("tomorrow = NOW() + FROM_DAYS(1)", defaultContext); // → tomorrow's timestamp
85
+ evaluate("NOW() + FROM_DAYS(7)", defaultContext); // → timestamp 7 days from now
86
+ evaluate("tomorrow = NOW() + FROM_DAYS(1)", defaultContext); // → tomorrow's timestamp
87
87
 
88
88
  // Extract date components
89
89
  const ctx = { ...defaultContext, variables: { ts: Date.now() } };
90
- execute("GET_YEAR(ts)", ctx); // → 2024
91
- execute("GET_MONTH(ts)", ctx); // → 11
92
- execute("GET_DAY(ts)", ctx); // → 6
90
+ evaluate("GET_YEAR(ts)", ctx); // → 2024
91
+ evaluate("GET_MONTH(ts)", ctx); // → 11
92
+ evaluate("GET_DAY(ts)", ctx); // → 6
93
93
 
94
94
  // Calculate time differences
95
95
  const ts1 = Date.now();
96
96
  const ts2 = ts1 + 1000 * 60 * 60 * 5; // 5 hours later
97
97
  const context = { ...defaultContext, variables: { ts1, ts2 } };
98
- execute("DIFFERENCE_IN_HOURS(ts1, ts2)", context); // → 5
98
+ evaluate("DIFFERENCE_IN_HOURS(ts1, ts2)", context); // → 5
99
99
 
100
100
  // Date arithmetic and comparisons
101
- execute("ADD_DAYS(NOW(), 7)", defaultContext); // → 7 days from now
102
- execute("START_OF_DAY(NOW())", defaultContext); // → today at 00:00:00.000
103
- execute("IS_WEEKEND(NOW())", defaultContext); // → 1 if today is Sat/Sun, else 0
101
+ evaluate("ADD_DAYS(NOW(), 7)", defaultContext); // → 7 days from now
102
+ evaluate("START_OF_DAY(NOW())", defaultContext); // → today at 00:00:00.000
103
+ evaluate("IS_WEEKEND(NOW())", defaultContext); // → 1 if today is Sat/Sun, else 0
104
104
  ```
105
105
 
106
106
  ### Custom Functions and Variables
107
107
 
108
108
  ```typescript
109
- import { execute } from "littlewing";
109
+ import { evaluate } from "littlewing";
110
110
 
111
111
  const context = {
112
112
  functions: {
113
- // Custom functions must return numbers
114
- fahrenheit: (celsius: number) => (celsius * 9) / 5 + 32,
115
- discount: (price: number, percent: number) => price * (1 - percent / 100),
113
+ // Custom functions should use UPPERCASE naming (like built-in functions)
114
+ FAHRENHEIT: (celsius: number) => (celsius * 9) / 5 + 32,
115
+ DISCOUNT: (price: number, percent: number) => price * (1 - percent / 100),
116
116
  },
117
117
  variables: {
118
118
  pi: 3.14159,
@@ -120,9 +120,9 @@ const context = {
120
120
  },
121
121
  };
122
122
 
123
- execute("fahrenheit(20)", context); // → 68
124
- execute("discount(100, 15)", context); // → 85
125
- execute("100 * (1 + taxRate)", context); // → 108
123
+ evaluate("FAHRENHEIT(20)", context); // → 68
124
+ evaluate("DISCOUNT(100, 15)", context); // → 85
125
+ evaluate("100 * (1 + taxRate)", context); // → 108
126
126
  ```
127
127
 
128
128
  ### External Variables Override Script Defaults
@@ -132,11 +132,11 @@ execute("100 * (1 + taxRate)", context); // → 108
132
132
  const formula = "multiplier = 2; value = 100; value * multiplier";
133
133
 
134
134
  // Without external variables: uses script defaults
135
- execute(formula); // → 200
135
+ evaluate(formula); // → 200
136
136
 
137
137
  // External variables override script assignments
138
- execute(formula, { variables: { multiplier: 3 } }); // → 300
139
- execute(formula, { variables: { value: 50 } }); // → 100
138
+ evaluate(formula, { variables: { multiplier: 3 } }); // → 300
139
+ evaluate(formula, { variables: { value: 50 } }); // → 100
140
140
 
141
141
  // Useful for configurable formulas
142
142
  const pricing = `
@@ -146,9 +146,9 @@ const pricing = `
146
146
  finalPrice = basePrice * (1 - discount) * (1 + taxRate)
147
147
  `;
148
148
 
149
- execute(pricing); // → 108 (uses all defaults)
150
- execute(pricing, { variables: { discount: 0.1 } }); // → 97.2 (10% discount)
151
- execute(pricing, { variables: { basePrice: 200, discount: 0.2 } }); // → 172.8
149
+ evaluate(pricing); // → 108 (uses all defaults)
150
+ evaluate(pricing, { variables: { discount: 0.1 } }); // → 97.2 (10% discount)
151
+ evaluate(pricing, { variables: { basePrice: 200, discount: 0.2 } }); // → 172.8
152
152
  ```
153
153
 
154
154
  ## Language Reference
@@ -159,51 +159,55 @@ For complete language documentation including all operators, functions, and exam
159
159
 
160
160
  ### Main Functions
161
161
 
162
- #### `execute(input: string | ASTNode, context?: ExecutionContext): number`
162
+ #### `evaluate(input: string | ASTNode, context?: ExecutionContext): number`
163
163
 
164
- Execute an expression or AST and return the result. Accepts either a source string or a pre-parsed AST node.
164
+ Evaluate an expression or AST and return the result. Accepts either a source string or a pre-parsed AST node.
165
165
 
166
166
  ```typescript
167
- // Execute source string directly
168
- execute("2 + 2"); // → 4
169
- execute("ABS(-5)", { functions: { ABS: Math.abs } }); // → 5
170
-
171
- // Execute pre-parsed AST (useful for parse-once, execute-many scenarios)
172
- const ast = parseSource("2 + 2");
173
- execute(ast); // → 4
174
- execute(ast); // → 4 (no re-parsing)
167
+ // Evaluate source string directly
168
+ evaluate("2 + 2"); // → 4
169
+ evaluate("ABS(-5)", { functions: { ABS: Math.abs } }); // → 5
170
+
171
+ // Evaluate pre-parsed AST (useful for parse-once, evaluate-many scenarios)
172
+ const ast = parse("2 + 2");
173
+ evaluate(ast); // → 4
174
+ evaluate(ast); // → 4 (no re-parsing)
175
175
  ```
176
176
 
177
- #### `parseSource(source: string): ASTNode`
177
+ #### `parse(source: string): ASTNode`
178
178
 
179
- Parse source into an Abstract Syntax Tree without executing. Useful for parse-once, execute-many scenarios.
179
+ Parse source into an Abstract Syntax Tree without evaluating. Useful for parse-once, execute-many scenarios.
180
180
 
181
181
  ```typescript
182
- const ast = parseSource("2 + 3 * 4");
182
+ const ast = parse("2 + 3 * 4");
183
183
 
184
- // Execute multiple times with different contexts (no re-parsing)
185
- execute(ast); // → 14
186
- execute(ast, {
184
+ // Evaluate multiple times with different contexts (no re-parsing)
185
+ evaluate(ast); // → 14
186
+ evaluate(ast, {
187
187
  variables: {
188
188
  /* ... */
189
189
  },
190
190
  }); // → 14 (with context)
191
191
 
192
- // Or use with Executor class or optimize() function
192
+ // Or use with optimize() function
193
193
  const optimized = optimize(ast);
194
194
  ```
195
195
 
196
196
  #### `optimize(node: ASTNode): ASTNode`
197
197
 
198
- Optimize an AST by folding constants. Safe for use with external variables.
198
+ Optimize an AST with constant folding and dead code elimination. Safe for use with external variables.
199
199
 
200
200
  ```typescript
201
- const ast = parseSource("2 + 3 * 4");
201
+ const ast = parse("2 + 3 * 4");
202
202
  const optimized = optimize(ast); // → NumberLiteral(14)
203
203
 
204
204
  // Variables are NOT folded (can be overridden by context)
205
- const ast2 = parseSource("x = 5; x + 10");
205
+ const ast2 = parse("x = 5; x + 10");
206
206
  const opt2 = optimize(ast2); // Still has variable reference
207
+
208
+ // Dead code elimination removes unused variables
209
+ const ast3 = parse("x = 10; y = 20; z = x * 20");
210
+ const opt3 = optimize(ast3); // Removes unused y assignment
207
211
  ```
208
212
 
209
213
  #### `generate(node: ASTNode): string`
@@ -211,10 +215,36 @@ const opt2 = optimize(ast2); // Still has variable reference
211
215
  Convert AST back to source code.
212
216
 
213
217
  ```typescript
214
- const ast = parseSource("2 + 3 * 4");
218
+ const ast = parse("2 + 3 * 4");
215
219
  generate(ast); // → "2 + 3 * 4"
216
220
  ```
217
221
 
222
+ #### `humanize(node: ASTNode, options?: HumanizeOptions): string`
223
+
224
+ Convert AST to human-readable English text.
225
+
226
+ ```typescript
227
+ const ast = parse("x + 10");
228
+ humanize(ast); // → "x plus 10"
229
+
230
+ // With HTML output
231
+ humanize(ast, { html: true }); // → "<span class='identifier'>x</span> plus <span class='number'>10</span>"
232
+ ```
233
+
234
+ #### `extractInputVariables(ast: ASTNode): string[]`
235
+
236
+ Extract all variable identifiers that are assigned to constant values (literals, constant expressions, or function calls with constant arguments). Variables computed from other variables are excluded.
237
+
238
+ ```typescript
239
+ const ast = parse("price = 100; tax = price * 0.08");
240
+ extractInputVariables(ast); // → ["price"]
241
+ // 'tax' is excluded because it's computed from 'price'
242
+
243
+ const ast2 = parse("x = 10; y = 20; sum = x + y");
244
+ extractInputVariables(ast2); // → ["x", "y"]
245
+ // 'sum' is excluded because it's computed from 'x' and 'y'
246
+ ```
247
+
218
248
  ### AST Visitor Pattern
219
249
 
220
250
  The visitor pattern provides a centralized, type-safe way to traverse and transform ASTs. This is useful for implementing custom analyzers, transformers, or code generators.
@@ -224,49 +254,54 @@ The visitor pattern provides a centralized, type-safe way to traverse and transf
224
254
  Exhaustively visit every node in an AST. All node types must be handled.
225
255
 
226
256
  ```typescript
227
- import { visit, parseSource } from "littlewing";
257
+ import { visit, parse } from "littlewing";
228
258
 
229
- const ast = parseSource("x = 5; x + 10");
259
+ const ast = parse("x = 5; x + 10");
230
260
 
231
261
  // Count all identifiers in an AST
232
262
  const count = visit(ast, {
233
- Program: (n, recurse) => n.body.reduce((sum, stmt) => sum + recurse(stmt), 0),
263
+ Program: (n, recurse) =>
264
+ n.statements.reduce((sum, stmt) => sum + recurse(stmt), 0),
234
265
  NumberLiteral: () => 0,
235
266
  Identifier: () => 1,
236
267
  BinaryOp: (n, recurse) => recurse(n.left) + recurse(n.right),
237
- UnaryOp: (n, recurse) => recurse(n.operand),
268
+ UnaryOp: (n, recurse) => recurse(n.argument),
238
269
  Assignment: (n, recurse) => 1 + recurse(n.value),
239
270
  FunctionCall: (n, recurse) =>
240
271
  1 + n.arguments.reduce((sum, arg) => sum + recurse(arg), 0),
241
- Ternary: (n, recurse) =>
272
+ ConditionalExpression: (n, recurse) =>
242
273
  recurse(n.condition) + recurse(n.consequent) + recurse(n.alternate),
243
274
  }); // → 3 (x appears 3 times)
244
275
  ```
245
276
 
246
- #### `visitPartial<T>(node: ASTNode, visitor: Partial<Visitor<T>>): T | undefined`
277
+ #### `visitPartial<T>(node: ASTNode, visitor: Partial<Visitor<T>>, defaultHandler: (node: ASTNode, recurse: (n: ASTNode) => T) => T): T`
247
278
 
248
- Visit only specific node types. Unhandled nodes return `undefined` and stop recursion.
279
+ Visit only specific node types. The `defaultHandler` is called for unhandled node types.
249
280
 
250
281
  ```typescript
251
- import { visitPartial, parseSource } from "littlewing";
282
+ import { visitPartial, parse } from "littlewing";
252
283
 
253
- const ast = parseSource("x = 5; y = x + 10; y * 2");
284
+ const ast = parse("x = 5; y = x + 10; y * 2");
254
285
 
255
286
  // Find first assignment to a specific variable
256
- const result = visitPartial(ast, {
257
- Program: (n, recurse) => {
258
- for (const stmt of n.body) {
259
- const found = recurse(stmt);
260
- if (found !== undefined) return found;
261
- }
262
- return undefined;
287
+ const result = visitPartial(
288
+ ast,
289
+ {
290
+ Program: (n, recurse) => {
291
+ for (const stmt of n.statements) {
292
+ const found = recurse(stmt);
293
+ if (found !== undefined) return found;
294
+ }
295
+ return undefined;
296
+ },
297
+ Assignment: (n, recurse) => {
298
+ if (n.name === "y") return n; // Found it!
299
+ return recurse(n.value); // Keep searching
300
+ },
301
+ BinaryOp: (n, recurse) => recurse(n.left) ?? recurse(n.right),
263
302
  },
264
- Assignment: (n, recurse) => {
265
- if (n.name === "y") return n; // Found it!
266
- return recurse(n.value); // Keep searching
267
- },
268
- BinaryOp: (n, recurse) => recurse(n.left) ?? recurse(n.right),
269
- }); // → Assignment node for "y = x + 10"
303
+ () => undefined,
304
+ ); // Assignment node for "y = x + 10"
270
305
  ```
271
306
 
272
307
  #### Transform Pattern
@@ -274,21 +309,22 @@ const result = visitPartial(ast, {
274
309
  Use `visit<ASTNode>` to create AST transformers:
275
310
 
276
311
  ```typescript
277
- import { visit, parseSource, ast } from "littlewing";
312
+ import { visit, parse, ast } from "littlewing";
278
313
 
279
314
  // Double all numeric literals
280
- const transformed = visit<ASTNode>(parseSource("2 + 3 * 4"), {
281
- Program: (n, recurse) => ast.program(n.body.map((stmt) => recurse(stmt))),
315
+ const transformed = visit<ASTNode>(parse("2 + 3 * 4"), {
316
+ Program: (n, recurse) =>
317
+ ast.program(n.statements.map((stmt) => recurse(stmt))),
282
318
  NumberLiteral: (n) => ast.number(n.value * 2),
283
319
  Identifier: (n) => n,
284
320
  BinaryOp: (n, recurse) =>
285
321
  ast.binaryOp(recurse(n.left), n.operator, recurse(n.right)),
286
- UnaryOp: (n, recurse) => ast.unaryOp(n.operator, recurse(n.operand)),
322
+ UnaryOp: (n, recurse) => ast.unaryOp(n.operator, recurse(n.argument)),
287
323
  Assignment: (n, recurse) => ast.assignment(n.name, recurse(n.value)),
288
324
  FunctionCall: (n, recurse) =>
289
325
  ast.functionCall(n.name, n.arguments.map(recurse)),
290
- Ternary: (n, recurse) =>
291
- ast.ternary(
326
+ ConditionalExpression: (n, recurse) =>
327
+ ast.conditional(
292
328
  recurse(n.condition),
293
329
  recurse(n.consequent),
294
330
  recurse(n.alternate),
@@ -298,11 +334,34 @@ const transformed = visit<ASTNode>(parseSource("2 + 3 * 4"), {
298
334
  generate(transformed); // → "4 + 6 * 8"
299
335
  ```
300
336
 
337
+ ### AST Builder Functions
338
+
339
+ The `ast` namespace provides builder functions for constructing AST nodes manually:
340
+
341
+ ```typescript
342
+ import { ast, generate } from "littlewing";
343
+
344
+ // Core builders
345
+ const node = ast.binaryOp(ast.number(2), "+", ast.number(3));
346
+ generate(node); // → "2 + 3"
347
+
348
+ // Convenience builders
349
+ const expr = ast.add(ast.identifier("x"), ast.number(10));
350
+ generate(expr); // → "x + 10"
351
+ ```
352
+
353
+ **Available builders:**
354
+
355
+ - Core: `program()`, `number()`, `identifier()`, `binaryOp()`, `unaryOp()`, `functionCall()`, `assignment()`, `conditional()`
356
+ - Arithmetic: `add()`, `subtract()`, `multiply()`, `divide()`, `modulo()`, `exponentiate()`, `negate()`
357
+ - Comparison: `equals()`, `notEquals()`, `lessThan()`, `greaterThan()`, `lessEqual()`, `greaterEqual()`
358
+ - Logical: `logicalAnd()`, `logicalOr()`, `logicalNot()`
359
+
301
360
  ### ExecutionContext
302
361
 
303
362
  ```typescript
304
363
  interface ExecutionContext {
305
- functions?: Record<string, (...args: any[]) => number>;
364
+ functions?: Record<string, (...args: number[]) => number>;
306
365
  variables?: Record<string, number>;
307
366
  }
308
367
  ```
@@ -311,42 +370,44 @@ interface ExecutionContext {
311
370
 
312
371
  The `defaultContext` includes these built-in functions:
313
372
 
314
- **Math:** `ABS`, `CEIL`, `FLOOR`, `ROUND`, `SQRT`, `MIN`, `MAX`, `CLAMP`, `SIN`, `COS`, `TAN`, `LOG`, `LOG10`, `EXP`
373
+ **Math (14 functions):** `ABS`, `CEIL`, `FLOOR`, `ROUND`, `SQRT`, `MIN`, `MAX`, `CLAMP`, `SIN`, `COS`, `TAN`, `LOG`, `LOG10`, `EXP`
374
+
375
+ **Timestamps (2 functions):** `NOW`, `DATE`
376
+
377
+ **Time converters (4 functions, to milliseconds):** `FROM_DAYS`, `FROM_WEEKS`, `FROM_MONTHS`, `FROM_YEARS`
315
378
 
316
- **Timestamps:** `NOW`, `DATE`
379
+ **Date component extractors (10 functions):** `GET_YEAR`, `GET_MONTH`, `GET_DAY`, `GET_HOUR`, `GET_MINUTE`, `GET_SECOND`, `GET_WEEKDAY`, `GET_MILLISECOND`, `GET_DAY_OF_YEAR`, `GET_QUARTER`
317
380
 
318
- **Time converters (to milliseconds):** `FROM_DAYS`, `FROM_WEEKS`, `FROM_MONTHS`, `FROM_YEARS`
381
+ **Time differences (7 functions, always positive):** `DIFFERENCE_IN_SECONDS`, `DIFFERENCE_IN_MINUTES`, `DIFFERENCE_IN_HOURS`, `DIFFERENCE_IN_DAYS`, `DIFFERENCE_IN_WEEKS`, `DIFFERENCE_IN_MONTHS`, `DIFFERENCE_IN_YEARS`
319
382
 
320
- **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`
383
+ **Start/End of period (8 functions):** `START_OF_DAY`, `END_OF_DAY`, `START_OF_WEEK`, `START_OF_MONTH`, `END_OF_MONTH`, `START_OF_YEAR`, `END_OF_YEAR`, `START_OF_QUARTER`
321
384
 
322
- **Time differences (always positive):** `DIFFERENCE_IN_SECONDS`, `DIFFERENCE_IN_MINUTES`, `DIFFERENCE_IN_HOURS`, `DIFFERENCE_IN_DAYS`, `DIFFERENCE_IN_WEEKS`, `DIFFERENCE_IN_MONTHS`, `DIFFERENCE_IN_YEARS`
385
+ **Date arithmetic (3 functions):** `ADD_DAYS`, `ADD_MONTHS`, `ADD_YEARS`
323
386
 
324
- **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`
387
+ **Date comparisons (3 functions):** `IS_SAME_DAY`, `IS_WEEKEND`, `IS_LEAP_YEAR`
325
388
 
326
- **Date arithmetic:** `ADD_DAYS`, `ADD_MONTHS`, `ADD_YEARS`
389
+ **Note:** For before/after comparisons, use the comparison operators directly: `ts1 < ts2`, `ts1 > ts2`, `ts1 <= ts2`, `ts1 >= ts2`
327
390
 
328
- **Date comparisons:** `IS_SAME_DAY`, `IS_WEEKEND`, `IS_LEAP_YEAR` (use `<`, `>`, `<=`, `>=` operators for before/after comparisons)
391
+ **Total: 54 built-in functions**
329
392
 
330
393
  ## Performance Optimization
331
394
 
332
395
  For expressions that are executed multiple times with different contexts, parse once and reuse the AST:
333
396
 
334
397
  ```typescript
335
- import { execute, parseSource } from "littlewing";
398
+ import { evaluate, parse } from "littlewing";
336
399
 
337
400
  // Parse once
338
- const formula = parseSource(
339
- "price * quantity * (1 - discount) * (1 + taxRate)",
340
- );
401
+ const formula = parse("price * quantity * (1 - discount) * (1 + taxRate)");
341
402
 
342
- // Execute many times with different values (no re-parsing)
343
- execute(formula, {
403
+ // Evaluate many times with different values (no re-parsing)
404
+ evaluate(formula, {
344
405
  variables: { price: 10, quantity: 5, discount: 0.1, taxRate: 0.08 },
345
406
  });
346
- execute(formula, {
407
+ evaluate(formula, {
347
408
  variables: { price: 20, quantity: 3, discount: 0.15, taxRate: 0.08 },
348
409
  });
349
- execute(formula, {
410
+ evaluate(formula, {
350
411
  variables: { price: 15, quantity: 10, discount: 0.2, taxRate: 0.08 },
351
412
  });
352
413
 
@@ -371,7 +432,7 @@ Your app needs to evaluate user-provided formulas or dynamic expressions. Using
371
432
 
372
433
  ### The Solution
373
434
 
374
- Littlewing provides just enough: arithmetic expressions with variables and functions. It's safe (no code execution), fast (linear time), and tiny (5KB gzipped).
435
+ Littlewing provides just enough: arithmetic expressions with variables and functions. It's safe (no code execution), fast (linear time), and tiny (7.8 KB gzipped).
375
436
 
376
437
  ### What Makes It Different
377
438