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.
- package/README.md +175 -114
- package/dist/index.d.ts +10 -342
- package/dist/index.js +185 -193
- 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 {
|
|
6
|
+
import { evaluate, defaultContext } from "littlewing";
|
|
7
7
|
|
|
8
8
|
// Simple arithmetic
|
|
9
|
-
|
|
9
|
+
evaluate("2 + 3 * 4"); // → 14
|
|
10
10
|
|
|
11
11
|
// Variables and functions
|
|
12
|
-
|
|
12
|
+
evaluate("radius = 5; area = 3.14159 * radius ^ 2", defaultContext); // → 78.54
|
|
13
13
|
|
|
14
14
|
// Date arithmetic with timestamps
|
|
15
|
-
|
|
15
|
+
evaluate("deadline = NOW() + FROM_DAYS(7)", defaultContext); // → timestamp 7 days from now
|
|
16
16
|
|
|
17
17
|
// Conditional logic
|
|
18
|
-
|
|
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** -
|
|
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 {
|
|
45
|
+
import { evaluate } from "littlewing";
|
|
46
46
|
|
|
47
47
|
// Arithmetic expressions
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
evaluate("2 + 3 * 4"); // → 14
|
|
49
|
+
evaluate("10 ^ 2"); // → 100
|
|
50
|
+
evaluate("17 % 5"); // → 2
|
|
51
51
|
|
|
52
52
|
// Variables
|
|
53
|
-
|
|
53
|
+
evaluate("x = 10; y = 20; x + y"); // → 30
|
|
54
54
|
|
|
55
55
|
// Comparisons (return 1 for true, 0 for false)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
evaluate("5 > 3"); // → 1
|
|
57
|
+
evaluate("10 == 10"); // → 1
|
|
58
|
+
evaluate("2 != 2"); // → 0
|
|
59
59
|
|
|
60
60
|
// Logical operators
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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 {
|
|
74
|
+
import { evaluate, defaultContext } from "littlewing";
|
|
75
75
|
|
|
76
76
|
// Math functions
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
82
|
+
evaluate("NOW()", defaultContext); // → 1704067200000
|
|
83
83
|
|
|
84
84
|
// Date arithmetic
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
98
|
+
evaluate("DIFFERENCE_IN_HOURS(ts1, ts2)", context); // → 5
|
|
99
99
|
|
|
100
100
|
// Date arithmetic and comparisons
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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 {
|
|
109
|
+
import { evaluate } from "littlewing";
|
|
110
110
|
|
|
111
111
|
const context = {
|
|
112
112
|
functions: {
|
|
113
|
-
// Custom functions
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
135
|
+
evaluate(formula); // → 200
|
|
136
136
|
|
|
137
137
|
// External variables override script assignments
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
#### `
|
|
162
|
+
#### `evaluate(input: string | ASTNode, context?: ExecutionContext): number`
|
|
163
163
|
|
|
164
|
-
|
|
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
|
-
//
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
//
|
|
172
|
-
const ast =
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
#### `
|
|
177
|
+
#### `parse(source: string): ASTNode`
|
|
178
178
|
|
|
179
|
-
Parse source into an Abstract Syntax Tree without
|
|
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 =
|
|
182
|
+
const ast = parse("2 + 3 * 4");
|
|
183
183
|
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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,
|
|
257
|
+
import { visit, parse } from "littlewing";
|
|
228
258
|
|
|
229
|
-
const ast =
|
|
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) =>
|
|
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.
|
|
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
|
-
|
|
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
|
|
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.
|
|
279
|
+
Visit only specific node types. The `defaultHandler` is called for unhandled node types.
|
|
249
280
|
|
|
250
281
|
```typescript
|
|
251
|
-
import { visitPartial,
|
|
282
|
+
import { visitPartial, parse } from "littlewing";
|
|
252
283
|
|
|
253
|
-
const ast =
|
|
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(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
265
|
-
|
|
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,
|
|
312
|
+
import { visit, parse, ast } from "littlewing";
|
|
278
313
|
|
|
279
314
|
// Double all numeric literals
|
|
280
|
-
const transformed = visit<ASTNode>(
|
|
281
|
-
Program: (n, recurse) =>
|
|
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.
|
|
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
|
-
|
|
291
|
-
ast.
|
|
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:
|
|
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
|
-
**
|
|
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
|
|
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
|
-
**
|
|
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
|
-
**
|
|
385
|
+
**Date arithmetic (3 functions):** `ADD_DAYS`, `ADD_MONTHS`, `ADD_YEARS`
|
|
323
386
|
|
|
324
|
-
**
|
|
387
|
+
**Date comparisons (3 functions):** `IS_SAME_DAY`, `IS_WEEKEND`, `IS_LEAP_YEAR`
|
|
325
388
|
|
|
326
|
-
**
|
|
389
|
+
**Note:** For before/after comparisons, use the comparison operators directly: `ts1 < ts2`, `ts1 > ts2`, `ts1 <= ts2`, `ts1 >= ts2`
|
|
327
390
|
|
|
328
|
-
**
|
|
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 {
|
|
398
|
+
import { evaluate, parse } from "littlewing";
|
|
336
399
|
|
|
337
400
|
// Parse once
|
|
338
|
-
const formula =
|
|
339
|
-
"price * quantity * (1 - discount) * (1 + taxRate)",
|
|
340
|
-
);
|
|
401
|
+
const formula = parse("price * quantity * (1 - discount) * (1 + taxRate)");
|
|
341
402
|
|
|
342
|
-
//
|
|
343
|
-
|
|
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
|
-
|
|
407
|
+
evaluate(formula, {
|
|
347
408
|
variables: { price: 20, quantity: 3, discount: 0.15, taxRate: 0.08 },
|
|
348
409
|
});
|
|
349
|
-
|
|
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 (
|
|
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
|
|