littlewing 0.9.5 → 1.1.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 +213 -129
  2. package/dist/index.d.ts +237 -399
  3. package/dist/index.js +998 -975
  4. package/package.json +5 -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,70 @@ 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
+ // Tuple: [kind, statements]
264
+ Program: (n, recurse) => {
265
+ const statements = n[1];
266
+ return statements.reduce((sum, stmt) => sum + recurse(stmt), 0);
267
+ },
234
268
  NumberLiteral: () => 0,
235
269
  Identifier: () => 1,
236
- BinaryOp: (n, recurse) => recurse(n.left) + recurse(n.right),
237
- UnaryOp: (n, recurse) => recurse(n.operand),
238
- Assignment: (n, recurse) => 1 + recurse(n.value),
239
- FunctionCall: (n, recurse) =>
240
- 1 + n.arguments.reduce((sum, arg) => sum + recurse(arg), 0),
241
- Ternary: (n, recurse) =>
242
- recurse(n.condition) + recurse(n.consequent) + recurse(n.alternate),
270
+ // Tuple: [kind, left, operator, right]
271
+ BinaryOp: (n, recurse) => recurse(n[1]) + recurse(n[3]),
272
+ // Tuple: [kind, operator, argument]
273
+ UnaryOp: (n, recurse) => recurse(n[2]),
274
+ // Tuple: [kind, name, value]
275
+ Assignment: (n, recurse) => 1 + recurse(n[2]),
276
+ // Tuple: [kind, name, arguments]
277
+ FunctionCall: (n, recurse) => {
278
+ const args = n[2];
279
+ return 1 + args.reduce((sum, arg) => sum + recurse(arg), 0);
280
+ },
281
+ // Tuple: [kind, condition, consequent, alternate]
282
+ ConditionalExpression: (n, recurse) =>
283
+ recurse(n[1]) + recurse(n[2]) + recurse(n[3]),
243
284
  }); // → 3 (x appears 3 times)
244
285
  ```
245
286
 
246
- #### `visitPartial<T>(node: ASTNode, visitor: Partial<Visitor<T>>): T | undefined`
287
+ #### `visitPartial<T>(node: ASTNode, visitor: Partial<Visitor<T>>, defaultHandler: (node: ASTNode, recurse: (n: ASTNode) => T) => T): T`
247
288
 
248
- Visit only specific node types. Unhandled nodes return `undefined` and stop recursion.
289
+ Visit only specific node types. The `defaultHandler` is called for unhandled node types.
249
290
 
250
291
  ```typescript
251
- import { visitPartial, parseSource } from "littlewing";
292
+ import { visitPartial, parse } from "littlewing";
252
293
 
253
- const ast = parseSource("x = 5; y = x + 10; y * 2");
294
+ const ast = parse("x = 5; y = x + 10; y * 2");
254
295
 
255
296
  // 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;
263
- },
264
- Assignment: (n, recurse) => {
265
- if (n.name === "y") return n; // Found it!
266
- return recurse(n.value); // Keep searching
297
+ const result = visitPartial(
298
+ ast,
299
+ {
300
+ // Tuple: [kind, statements]
301
+ Program: (n, recurse) => {
302
+ const statements = n[1];
303
+ for (const stmt of statements) {
304
+ const found = recurse(stmt);
305
+ if (found !== undefined) return found;
306
+ }
307
+ return undefined;
308
+ },
309
+ // Tuple: [kind, name, value]
310
+ Assignment: (n, recurse) => {
311
+ const name = n[1];
312
+ const value = n[2];
313
+ if (name === "y") return n; // Found it!
314
+ return recurse(value); // Keep searching
315
+ },
316
+ // Tuple: [kind, left, operator, right]
317
+ BinaryOp: (n, recurse) => recurse(n[1]) ?? recurse(n[3]),
267
318
  },
268
- BinaryOp: (n, recurse) => recurse(n.left) ?? recurse(n.right),
269
- }); // → Assignment node for "y = x + 10"
319
+ () => undefined,
320
+ ); // → Assignment node for "y = x + 10"
270
321
  ```
271
322
 
272
323
  #### Transform Pattern
@@ -274,35 +325,66 @@ const result = visitPartial(ast, {
274
325
  Use `visit<ASTNode>` to create AST transformers:
275
326
 
276
327
  ```typescript
277
- import { visit, parseSource, ast } from "littlewing";
328
+ import { visit, parse, ast } from "littlewing";
278
329
 
279
330
  // 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))),
282
- NumberLiteral: (n) => ast.number(n.value * 2),
331
+ const transformed = visit<ASTNode>(parse("2 + 3 * 4"), {
332
+ // Tuple: [kind, statements]
333
+ Program: (n, recurse) => {
334
+ const statements = n[1];
335
+ return ast.program(statements.map((stmt) => recurse(stmt)));
336
+ },
337
+ // Tuple: [kind, value]
338
+ NumberLiteral: (n) => ast.number(n[1] * 2),
283
339
  Identifier: (n) => n,
284
- BinaryOp: (n, recurse) =>
285
- ast.binaryOp(recurse(n.left), n.operator, recurse(n.right)),
286
- UnaryOp: (n, recurse) => ast.unaryOp(n.operator, recurse(n.operand)),
287
- Assignment: (n, recurse) => ast.assignment(n.name, recurse(n.value)),
288
- FunctionCall: (n, recurse) =>
289
- ast.functionCall(n.name, n.arguments.map(recurse)),
290
- Ternary: (n, recurse) =>
291
- ast.ternary(
292
- recurse(n.condition),
293
- recurse(n.consequent),
294
- recurse(n.alternate),
295
- ),
340
+ // Tuple: [kind, left, operator, right]
341
+ BinaryOp: (n, recurse) => ast.binaryOp(recurse(n[1]), n[2], recurse(n[3])),
342
+ // Tuple: [kind, operator, argument]
343
+ UnaryOp: (n, recurse) => ast.unaryOp(n[1], recurse(n[2])),
344
+ // Tuple: [kind, name, value]
345
+ Assignment: (n, recurse) => ast.assign(n[1], recurse(n[2])),
346
+ // Tuple: [kind, name, arguments]
347
+ FunctionCall: (n, recurse) => {
348
+ const name = n[1];
349
+ const args = n[2];
350
+ return ast.functionCall(name, args.map(recurse));
351
+ },
352
+ // Tuple: [kind, condition, consequent, alternate]
353
+ ConditionalExpression: (n, recurse) =>
354
+ ast.conditional(recurse(n[1]), recurse(n[2]), recurse(n[3])),
296
355
  });
297
356
 
298
357
  generate(transformed); // → "4 + 6 * 8"
299
358
  ```
300
359
 
360
+ ### AST Builder Functions
361
+
362
+ The `ast` namespace provides builder functions for constructing AST nodes manually:
363
+
364
+ ```typescript
365
+ import { ast, generate } from "littlewing";
366
+
367
+ // Core builders
368
+ const node = ast.binaryOp(ast.number(2), "+", ast.number(3));
369
+ generate(node); // → "2 + 3"
370
+
371
+ // Convenience builders
372
+ const expr = ast.add(ast.identifier("x"), ast.number(10));
373
+ generate(expr); // → "x + 10"
374
+ ```
375
+
376
+ **Available builders:**
377
+
378
+ - Core: `program()`, `number()`, `identifier()`, `binaryOp()`, `unaryOp()`, `functionCall()`, `assignment()`, `conditional()`
379
+ - Arithmetic: `add()`, `subtract()`, `multiply()`, `divide()`, `modulo()`, `exponentiate()`, `negate()`
380
+ - Comparison: `equals()`, `notEquals()`, `lessThan()`, `greaterThan()`, `lessEqual()`, `greaterEqual()`
381
+ - Logical: `logicalAnd()`, `logicalOr()`, `logicalNot()`
382
+
301
383
  ### ExecutionContext
302
384
 
303
385
  ```typescript
304
386
  interface ExecutionContext {
305
- functions?: Record<string, (...args: any[]) => number>;
387
+ functions?: Record<string, (...args: number[]) => number>;
306
388
  variables?: Record<string, number>;
307
389
  }
308
390
  ```
@@ -311,42 +393,44 @@ interface ExecutionContext {
311
393
 
312
394
  The `defaultContext` includes these built-in functions:
313
395
 
314
- **Math:** `ABS`, `CEIL`, `FLOOR`, `ROUND`, `SQRT`, `MIN`, `MAX`, `CLAMP`, `SIN`, `COS`, `TAN`, `LOG`, `LOG10`, `EXP`
396
+ **Math (14 functions):** `ABS`, `CEIL`, `FLOOR`, `ROUND`, `SQRT`, `MIN`, `MAX`, `CLAMP`, `SIN`, `COS`, `TAN`, `LOG`, `LOG10`, `EXP`
397
+
398
+ **Timestamps (2 functions):** `NOW`, `DATE`
399
+
400
+ **Time converters (4 functions, to milliseconds):** `FROM_DAYS`, `FROM_WEEKS`, `FROM_MONTHS`, `FROM_YEARS`
315
401
 
316
- **Timestamps:** `NOW`, `DATE`
402
+ **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
403
 
318
- **Time converters (to milliseconds):** `FROM_DAYS`, `FROM_WEEKS`, `FROM_MONTHS`, `FROM_YEARS`
404
+ **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
405
 
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`
406
+ **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
407
 
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`
408
+ **Date arithmetic (3 functions):** `ADD_DAYS`, `ADD_MONTHS`, `ADD_YEARS`
323
409
 
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`
410
+ **Date comparisons (3 functions):** `IS_SAME_DAY`, `IS_WEEKEND`, `IS_LEAP_YEAR`
325
411
 
326
- **Date arithmetic:** `ADD_DAYS`, `ADD_MONTHS`, `ADD_YEARS`
412
+ **Note:** For before/after comparisons, use the comparison operators directly: `ts1 < ts2`, `ts1 > ts2`, `ts1 <= ts2`, `ts1 >= ts2`
327
413
 
328
- **Date comparisons:** `IS_SAME_DAY`, `IS_WEEKEND`, `IS_LEAP_YEAR` (use `<`, `>`, `<=`, `>=` operators for before/after comparisons)
414
+ **Total: 54 built-in functions**
329
415
 
330
416
  ## Performance Optimization
331
417
 
332
418
  For expressions that are executed multiple times with different contexts, parse once and reuse the AST:
333
419
 
334
420
  ```typescript
335
- import { execute, parseSource } from "littlewing";
421
+ import { evaluate, parse } from "littlewing";
336
422
 
337
423
  // Parse once
338
- const formula = parseSource(
339
- "price * quantity * (1 - discount) * (1 + taxRate)",
340
- );
424
+ const formula = parse("price * quantity * (1 - discount) * (1 + taxRate)");
341
425
 
342
- // Execute many times with different values (no re-parsing)
343
- execute(formula, {
426
+ // Evaluate many times with different values (no re-parsing)
427
+ evaluate(formula, {
344
428
  variables: { price: 10, quantity: 5, discount: 0.1, taxRate: 0.08 },
345
429
  });
346
- execute(formula, {
430
+ evaluate(formula, {
347
431
  variables: { price: 20, quantity: 3, discount: 0.15, taxRate: 0.08 },
348
432
  });
349
- execute(formula, {
433
+ evaluate(formula, {
350
434
  variables: { price: 15, quantity: 10, discount: 0.2, taxRate: 0.08 },
351
435
  });
352
436
 
@@ -371,7 +455,7 @@ Your app needs to evaluate user-provided formulas or dynamic expressions. Using
371
455
 
372
456
  ### The Solution
373
457
 
374
- Littlewing provides just enough: arithmetic expressions with variables and functions. It's safe (no code execution), fast (linear time), and tiny (5KB gzipped).
458
+ 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
459
 
376
460
  ### What Makes It Different
377
461