littlewing 2.1.0 → 2.2.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/LICENSE +21 -0
- package/README.md +400 -0
- package/dist/index.d.ts +64 -55
- package/dist/index.js +157 -126
- package/package.json +2 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Gabriel Vaquer <brielov@icloud.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
# littlewing
|
|
2
|
+
|
|
3
|
+
A minimal, high-performance multi-type expression language for JavaScript. Seven types, zero compromise, built for the browser.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { evaluate, defaultContext } from "littlewing";
|
|
7
|
+
|
|
8
|
+
// Arithmetic
|
|
9
|
+
evaluate("2 + 3 * 4"); // → 14
|
|
10
|
+
|
|
11
|
+
// Strings
|
|
12
|
+
evaluate('"hello" + " world"'); // → "hello world"
|
|
13
|
+
|
|
14
|
+
// Variables and conditionals
|
|
15
|
+
evaluate('price = 100; if price > 50 then "expensive" else "cheap"'); // → "expensive"
|
|
16
|
+
|
|
17
|
+
// Date arithmetic
|
|
18
|
+
evaluate("DIFFERENCE_IN_DAYS(TODAY(), DATE(2025, 12, 31))", defaultContext);
|
|
19
|
+
|
|
20
|
+
// Array comprehensions
|
|
21
|
+
evaluate("for x in 1..=5 then x ^ 2"); // → [1, 4, 9, 16, 25]
|
|
22
|
+
|
|
23
|
+
// Reduce with accumulator
|
|
24
|
+
evaluate("for x in [1, 2, 3, 4] into sum = 0 then sum + x"); // → 10
|
|
25
|
+
|
|
26
|
+
// Pipe operator — chain values through functions
|
|
27
|
+
evaluate("-5 |> ABS(?) |> STR(?)", defaultContext); // → "5"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- **Seven types** — Numbers, strings, booleans, dates (`Temporal.PlainDate`), times (`Temporal.PlainTime`), datetimes (`Temporal.PlainDateTime`), and homogeneous arrays
|
|
33
|
+
- **No implicit coercion** — Explicit type conversion via `STR()`, `NUM()`, etc.
|
|
34
|
+
- **Strict boolean logic** — `!`, `&&`, `||`, and `if` conditions require booleans
|
|
35
|
+
- **Control flow** — `if/then/else` expressions and `for/in/then` comprehensions with optional `when` guard and `into` accumulator
|
|
36
|
+
- **Bracket indexing** — `arr[0]`, `str[-1]`, with chaining (`matrix[0][1]`)
|
|
37
|
+
- **Pipe operator** — `x |> FUN(?) |> OTHER(?, 1)` chains values through function calls
|
|
38
|
+
- **Range expressions** — `1..5` (exclusive), `1..=5` (inclusive)
|
|
39
|
+
- **Deep equality** — `[1, 2] == [1, 2]` → `true`; cross-type `==` → `false`
|
|
40
|
+
- **85 built-in functions** — Math, string, array, date, time, and datetime operations
|
|
41
|
+
- **O(n) performance** — Linear time parsing and execution
|
|
42
|
+
- **Safe evaluation** — Tree-walk interpreter, no code generation
|
|
43
|
+
- **Extensible** — Add custom functions and variables via context
|
|
44
|
+
- **Type-safe** — Full TypeScript support with strict types
|
|
45
|
+
- **Zero runtime dependencies** — Requires global `Temporal` API (native or polyfill)
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install littlewing
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
### Basic Usage
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { evaluate } from "littlewing";
|
|
59
|
+
|
|
60
|
+
// Arithmetic
|
|
61
|
+
evaluate("2 + 3 * 4"); // → 14
|
|
62
|
+
evaluate("2 ^ 10"); // → 1024
|
|
63
|
+
|
|
64
|
+
// Strings
|
|
65
|
+
evaluate('"hello" + " world"'); // → "hello world"
|
|
66
|
+
|
|
67
|
+
// Booleans (comparisons return boolean, not 1/0)
|
|
68
|
+
evaluate("5 > 3"); // → true
|
|
69
|
+
evaluate("!(5 > 10)"); // → true
|
|
70
|
+
|
|
71
|
+
// Variables
|
|
72
|
+
evaluate("x = 10; y = 20; x + y"); // → 30
|
|
73
|
+
|
|
74
|
+
// Conditionals (condition must be boolean, else is required)
|
|
75
|
+
evaluate('age = 21; if age >= 18 then "adult" else "minor"'); // → "adult"
|
|
76
|
+
|
|
77
|
+
// Arrays and indexing
|
|
78
|
+
evaluate("[10, 20, 30][-1]"); // → 30
|
|
79
|
+
evaluate("[1, 2] + [3, 4]"); // → [1, 2, 3, 4]
|
|
80
|
+
|
|
81
|
+
// Ranges
|
|
82
|
+
evaluate("1..=5"); // → [1, 2, 3, 4, 5]
|
|
83
|
+
|
|
84
|
+
// For comprehensions (map, filter, reduce)
|
|
85
|
+
evaluate("for x in 1..=5 then x * 2"); // → [2, 4, 6, 8, 10]
|
|
86
|
+
evaluate("for x in 1..=10 when x % 2 == 0 then x"); // → [2, 4, 6, 8, 10]
|
|
87
|
+
evaluate("for x in [1, 2, 3] into sum = 0 then sum + x"); // → 6
|
|
88
|
+
|
|
89
|
+
// Pipe operator
|
|
90
|
+
evaluate("-42 |> ABS(?)", defaultContext); // → 42
|
|
91
|
+
evaluate("150 |> CLAMP(?, 0, 100)", defaultContext); // → 100
|
|
92
|
+
evaluate("-3 |> ABS(?) |> STR(?)", defaultContext); // → "3"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### With Built-in Functions
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import { evaluate, defaultContext } from "littlewing";
|
|
99
|
+
|
|
100
|
+
// Math
|
|
101
|
+
evaluate("ABS(-42)", defaultContext); // → 42
|
|
102
|
+
evaluate("ROUND(3.7)", defaultContext); // → 4
|
|
103
|
+
|
|
104
|
+
// Type conversion
|
|
105
|
+
evaluate('NUM("42")', defaultContext); // → 42
|
|
106
|
+
evaluate("STR(42)", defaultContext); // → "42"
|
|
107
|
+
|
|
108
|
+
// String functions
|
|
109
|
+
evaluate('STR_UPPER("hello")', defaultContext); // → "HELLO"
|
|
110
|
+
evaluate('STR_SPLIT("a,b,c", ",")', defaultContext); // → ["a", "b", "c"]
|
|
111
|
+
|
|
112
|
+
// Array functions
|
|
113
|
+
evaluate("ARR_SORT([3, 1, 2])", defaultContext); // → [1, 2, 3]
|
|
114
|
+
evaluate("ARR_SUM([10, 20, 30])", defaultContext); // → 60
|
|
115
|
+
evaluate('ARR_JOIN(["a", "b", "c"], "-")', defaultContext); // → "a-b-c"
|
|
116
|
+
|
|
117
|
+
// Date functions
|
|
118
|
+
evaluate("TODAY()", defaultContext); // → Temporal.PlainDate
|
|
119
|
+
evaluate("ADD_DAYS(TODAY(), 7)", defaultContext); // → 7 days from now
|
|
120
|
+
evaluate("IS_WEEKEND(TODAY())", defaultContext); // → true or false
|
|
121
|
+
|
|
122
|
+
// Time functions
|
|
123
|
+
evaluate("TIME(14, 30, 0)", defaultContext); // → Temporal.PlainTime
|
|
124
|
+
evaluate("ADD_HOURS(TIME(10, 0, 0), 3)", defaultContext); // → 13:00:00
|
|
125
|
+
|
|
126
|
+
// DateTime functions
|
|
127
|
+
evaluate("NOW()", defaultContext); // → Temporal.PlainDateTime
|
|
128
|
+
evaluate("TO_DATE(NOW())", defaultContext); // → today's date
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Custom Functions and Variables
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { evaluate, assertNumber, assertString } from "littlewing";
|
|
135
|
+
|
|
136
|
+
const context = {
|
|
137
|
+
functions: {
|
|
138
|
+
FAHRENHEIT: (celsius) => {
|
|
139
|
+
assertNumber(celsius, "FAHRENHEIT");
|
|
140
|
+
return (celsius * 9) / 5 + 32;
|
|
141
|
+
},
|
|
142
|
+
DISCOUNT: (price, percent) => {
|
|
143
|
+
assertNumber(price, "DISCOUNT", "price");
|
|
144
|
+
assertNumber(percent, "DISCOUNT", "percent");
|
|
145
|
+
return price * (1 - percent / 100);
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
variables: {
|
|
149
|
+
pi: 3.14159,
|
|
150
|
+
taxRate: 0.08,
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
evaluate("FAHRENHEIT(20)", context); // → 68
|
|
155
|
+
evaluate("DISCOUNT(100, 15)", context); // → 85
|
|
156
|
+
evaluate("100 * (1 + taxRate)", context); // → 108
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The assertion helpers (`assertNumber`, `assertString`, `assertBoolean`, `assertArray`, `assertDate`, `assertTime`, `assertDateTime`, `assertDateOrDateTime`, `assertTimeOrDateTime`) are the same ones used by the built-in standard library. They throw `TypeError` with consistent messages on type mismatch.
|
|
160
|
+
|
|
161
|
+
### External Variables Override Script Defaults
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
const formula = "multiplier = 2; value = 100; value * multiplier";
|
|
165
|
+
|
|
166
|
+
evaluate(formula); // → 200
|
|
167
|
+
evaluate(formula, { variables: { multiplier: 3 } }); // → 300
|
|
168
|
+
evaluate(formula, { variables: { value: 50 } }); // → 100
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Language Reference
|
|
172
|
+
|
|
173
|
+
For complete language documentation including all operators, control flow, and built-in functions, see [LANGUAGE.md](./LANGUAGE.md).
|
|
174
|
+
|
|
175
|
+
## API
|
|
176
|
+
|
|
177
|
+
### Main Functions
|
|
178
|
+
|
|
179
|
+
#### `evaluate(input: string | ASTNode, context?: ExecutionContext): RuntimeValue`
|
|
180
|
+
|
|
181
|
+
Evaluate an expression or AST and return the result.
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
evaluate("2 + 2"); // → 4
|
|
185
|
+
|
|
186
|
+
// Evaluate pre-parsed AST (parse once, evaluate many)
|
|
187
|
+
const ast = parse("price * quantity");
|
|
188
|
+
evaluate(ast, { variables: { price: 10, quantity: 5 } }); // → 50
|
|
189
|
+
evaluate(ast, { variables: { price: 20, quantity: 3 } }); // → 60
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
#### `evaluateScope(input: string | ASTNode, context?: ExecutionContext): Record<string, RuntimeValue>`
|
|
193
|
+
|
|
194
|
+
Evaluate and return all assigned variables as a record.
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
evaluateScope("x = 10; y = x * 2"); // → { x: 10, y: 20 }
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
#### `parse(source: string): ASTNode`
|
|
201
|
+
|
|
202
|
+
Parse source into an Abstract Syntax Tree without evaluating.
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
const ast = parse("2 + 3 * 4");
|
|
206
|
+
evaluate(ast); // → 14
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### `generate(node: ASTNode): string`
|
|
210
|
+
|
|
211
|
+
Convert AST back to source code (preserves comments).
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
generate(parse("2 + 3 * 4")); // → "2 + 3 * 4"
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
#### `optimize(node: ASTNode, externalVariables?: ReadonlySet<string>): ASTNode`
|
|
218
|
+
|
|
219
|
+
Optimize an AST with constant folding, constant propagation, and dead code elimination.
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
const ast = parse("2 + 3 * 4");
|
|
223
|
+
optimize(ast); // → NumberLiteral(14)
|
|
224
|
+
|
|
225
|
+
// With external variables: propagates internal constants while preserving external ones
|
|
226
|
+
const ast2 = parse("x = 5; y = 10; x + y");
|
|
227
|
+
optimize(ast2, new Set(["x"])); // Propagates y=10, keeps x as external
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
#### `extractInputVariables(ast: ASTNode): string[]`
|
|
231
|
+
|
|
232
|
+
Extract variable names assigned to constant values (useful for building UIs with input controls).
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
const ast = parse("price = 100; tax = price * 0.08");
|
|
236
|
+
extractInputVariables(ast); // → ["price"]
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### AST Visitor Pattern
|
|
240
|
+
|
|
241
|
+
#### `visit<T>(node: ASTNode, visitor: Visitor<T>): T`
|
|
242
|
+
|
|
243
|
+
Exhaustively visit every node in an AST. All 16 node types must be handled.
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
import { visit, parse } from "littlewing";
|
|
247
|
+
|
|
248
|
+
const count = visit(parse("2 + 3"), {
|
|
249
|
+
Program: (n, recurse) => n.statements.reduce((s, stmt) => s + recurse(stmt), 0),
|
|
250
|
+
NumberLiteral: () => 1,
|
|
251
|
+
StringLiteral: () => 1,
|
|
252
|
+
BooleanLiteral: () => 1,
|
|
253
|
+
ArrayLiteral: (n, recurse) => 1 + n.elements.reduce((s, el) => s + recurse(el), 0),
|
|
254
|
+
Identifier: () => 1,
|
|
255
|
+
BinaryOp: (n, recurse) => 1 + recurse(n.left) + recurse(n.right),
|
|
256
|
+
UnaryOp: (n, recurse) => 1 + recurse(n.argument),
|
|
257
|
+
Assignment: (n, recurse) => 1 + recurse(n.value),
|
|
258
|
+
FunctionCall: (n, recurse) => 1 + n.args.reduce((s, arg) => s + recurse(arg), 0),
|
|
259
|
+
IfExpression: (n, recurse) =>
|
|
260
|
+
1 + recurse(n.condition) + recurse(n.consequent) + recurse(n.alternate),
|
|
261
|
+
ForExpression: (n, recurse) =>
|
|
262
|
+
1 + recurse(n.iterable) + (n.guard ? recurse(n.guard) : 0) + recurse(n.body),
|
|
263
|
+
IndexAccess: (n, recurse) => 1 + recurse(n.object) + recurse(n.index),
|
|
264
|
+
RangeExpression: (n, recurse) => 1 + recurse(n.start) + recurse(n.end),
|
|
265
|
+
PipeExpression: (n, recurse) =>
|
|
266
|
+
1 + recurse(n.value) + n.args.reduce((s, arg) => s + recurse(arg), 0),
|
|
267
|
+
Placeholder: () => 1,
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
#### `visitPartial<T>(node, visitor, defaultHandler): T`
|
|
272
|
+
|
|
273
|
+
Visit only specific node types with a fallback for unhandled types.
|
|
274
|
+
|
|
275
|
+
### AST Builder Functions
|
|
276
|
+
|
|
277
|
+
The `ast` namespace provides builder functions for constructing AST nodes:
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import { ast, generate } from "littlewing";
|
|
281
|
+
|
|
282
|
+
generate(ast.add(ast.number(2), ast.number(3))); // → "2 + 3"
|
|
283
|
+
generate(ast.ifExpr(ast.boolean(true), ast.number(1), ast.number(0))); // → "if true then 1 else 0"
|
|
284
|
+
generate(
|
|
285
|
+
ast.forExpr(
|
|
286
|
+
"x",
|
|
287
|
+
ast.identifier("arr"),
|
|
288
|
+
null,
|
|
289
|
+
null,
|
|
290
|
+
ast.multiply(ast.identifier("x"), ast.number(2)),
|
|
291
|
+
),
|
|
292
|
+
);
|
|
293
|
+
// → "for x in arr then x * 2"
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Available builders:**
|
|
297
|
+
|
|
298
|
+
- Core: `program()`, `number()`, `string()`, `boolean()`, `array()`, `identifier()`, `binaryOp()`, `unaryOp()`, `functionCall()`, `assign()`, `ifExpr()`, `forExpr()`, `indexAccess()`, `rangeExpr()`, `pipeExpr()`, `placeholder()`
|
|
299
|
+
- Arithmetic: `add()`, `subtract()`, `multiply()`, `divide()`, `modulo()`, `exponentiate()`, `negate()`
|
|
300
|
+
- Comparison: `equals()`, `notEquals()`, `lessThan()`, `greaterThan()`, `lessEqual()`, `greaterEqual()`
|
|
301
|
+
- Logical: `logicalAnd()`, `logicalOr()`, `logicalNot()`
|
|
302
|
+
|
|
303
|
+
### ExecutionContext
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
interface ExecutionContext {
|
|
307
|
+
functions?: Record<string, (...args: RuntimeValue[]) => RuntimeValue>;
|
|
308
|
+
variables?: Record<string, RuntimeValue>;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
type RuntimeValue =
|
|
312
|
+
| number
|
|
313
|
+
| string
|
|
314
|
+
| boolean
|
|
315
|
+
| Temporal.PlainDate
|
|
316
|
+
| Temporal.PlainTime
|
|
317
|
+
| Temporal.PlainDateTime
|
|
318
|
+
| readonly RuntimeValue[];
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Default Context Functions
|
|
322
|
+
|
|
323
|
+
The `defaultContext` includes **82 built-in functions**:
|
|
324
|
+
|
|
325
|
+
**Core (8):** `STR`, `NUM`, `TYPE`, `LEN`, `SLICE`, `CONTAINS`, `REVERSE`, `INDEX_OF`
|
|
326
|
+
|
|
327
|
+
**Math (14):** `ABS`, `CEIL`, `FLOOR`, `ROUND`, `SQRT`, `MIN`, `MAX`, `CLAMP`, `SIN`, `COS`, `TAN`, `LOG`, `LOG10`, `EXP`
|
|
328
|
+
|
|
329
|
+
**String (8):** `STR_UPPER`, `STR_LOWER`, `STR_TRIM`, `STR_SPLIT`, `STR_REPLACE`, `STR_STARTS_WITH`, `STR_ENDS_WITH`, `STR_REPEAT`
|
|
330
|
+
|
|
331
|
+
**Array (7):** `ARR_SORT`, `ARR_UNIQUE`, `ARR_FLAT`, `ARR_JOIN`, `ARR_SUM`, `ARR_MIN`, `ARR_MAX`
|
|
332
|
+
|
|
333
|
+
**Date (25):** `TODAY`, `DATE`, `YEAR`, `MONTH`, `DAY`, `WEEKDAY`, `DAY_OF_YEAR`, `QUARTER`, `ADD_DAYS`, `ADD_MONTHS`, `ADD_YEARS`, `DIFFERENCE_IN_DAYS`, `DIFFERENCE_IN_WEEKS`, `DIFFERENCE_IN_MONTHS`, `DIFFERENCE_IN_YEARS`, `START_OF_MONTH`, `END_OF_MONTH`, `START_OF_YEAR`, `END_OF_YEAR`, `START_OF_WEEK`, `START_OF_QUARTER`, `IS_SAME_DAY`, `IS_WEEKEND`, `IS_LEAP_YEAR`, `AGE`
|
|
334
|
+
|
|
335
|
+
**Time (13):** `TIME`, `NOW_TIME`, `HOUR`, `MINUTE`, `SECOND`, `MILLISECOND`, `ADD_HOURS`, `ADD_MINUTES`, `ADD_SECONDS`, `DIFFERENCE_IN_HOURS`, `DIFFERENCE_IN_MINUTES`, `DIFFERENCE_IN_SECONDS`, `IS_SAME_TIME`
|
|
336
|
+
|
|
337
|
+
**DateTime (7):** `DATETIME`, `NOW`, `TO_DATE`, `TO_TIME`, `COMBINE`, `START_OF_DAY`, `END_OF_DAY`
|
|
338
|
+
|
|
339
|
+
**Temporal type support:** Date functions accept both `PlainDate` and `PlainDateTime` (preserving type). Time functions accept both `PlainTime` and `PlainDateTime`. Difference functions require both arguments to be the same type.
|
|
340
|
+
|
|
341
|
+
## Performance Optimization
|
|
342
|
+
|
|
343
|
+
### Parse Once, Evaluate Many
|
|
344
|
+
|
|
345
|
+
For expressions executed multiple times, parse once and reuse the AST:
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
import { evaluate, parse } from "littlewing";
|
|
349
|
+
|
|
350
|
+
const formula = parse("price * quantity * (1 - discount)");
|
|
351
|
+
|
|
352
|
+
evaluate(formula, { variables: { price: 10, quantity: 5, discount: 0.1 } }); // → 45
|
|
353
|
+
evaluate(formula, { variables: { price: 20, quantity: 3, discount: 0.15 } }); // → 51
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## Use Cases
|
|
357
|
+
|
|
358
|
+
- **User-defined formulas** — Let users write safe expressions
|
|
359
|
+
- **Business rules** — Express logic without `eval()` or `new Function()`
|
|
360
|
+
- **Financial calculators** — Compound interest, loan payments, etc.
|
|
361
|
+
- **Date arithmetic** — Deadlines, scheduling, date calculations
|
|
362
|
+
- **Data transformations** — Map, filter, and reduce arrays
|
|
363
|
+
- **Configuration expressions** — Dynamic config values
|
|
364
|
+
|
|
365
|
+
## Why Littlewing?
|
|
366
|
+
|
|
367
|
+
### The Problem
|
|
368
|
+
|
|
369
|
+
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.
|
|
370
|
+
|
|
371
|
+
### The Solution
|
|
372
|
+
|
|
373
|
+
Littlewing provides just enough: expressions with multiple types, variables, and functions. It's safe (no code execution), fast (linear time), and type-safe (no implicit coercion).
|
|
374
|
+
|
|
375
|
+
### What Makes It Different
|
|
376
|
+
|
|
377
|
+
1. **Multi-type with strict semantics** — Seven types, no implicit coercion, no surprises
|
|
378
|
+
2. **External variables override** — Scripts have defaults, runtime provides overrides
|
|
379
|
+
3. **Full Temporal support** — First-class `PlainDate`, `PlainTime`, and `PlainDateTime`
|
|
380
|
+
4. **Deep equality** — Arrays and dates compare by value
|
|
381
|
+
5. **O(n) everything** — Predictable performance at any scale
|
|
382
|
+
|
|
383
|
+
## Development
|
|
384
|
+
|
|
385
|
+
```bash
|
|
386
|
+
bun install # Install dependencies
|
|
387
|
+
bun test # Run tests
|
|
388
|
+
bun run build # Build
|
|
389
|
+
bun run --cwd packages/littlewing dev # Watch mode
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
For detailed development docs, see [CLAUDE.md](./CLAUDE.md).
|
|
393
|
+
|
|
394
|
+
## License
|
|
395
|
+
|
|
396
|
+
MIT
|
|
397
|
+
|
|
398
|
+
## Contributing
|
|
399
|
+
|
|
400
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
package/dist/index.d.ts
CHANGED
|
@@ -320,6 +320,23 @@ declare function extractInputVariables(ast: ASTNode): string[];
|
|
|
320
320
|
*/
|
|
321
321
|
declare function extractAssignedVariables(ast: ASTNode): string[];
|
|
322
322
|
/**
|
|
323
|
+
* Error thrown when parsing fails. Carries byte offsets into the source string
|
|
324
|
+
* so callers can display precise error locations.
|
|
325
|
+
*/
|
|
326
|
+
declare class ParseError extends Error {
|
|
327
|
+
readonly start: number;
|
|
328
|
+
readonly end: number;
|
|
329
|
+
readonly name = "ParseError";
|
|
330
|
+
constructor(message: string, start: number, end: number);
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Convert a byte offset into a source string to a 1-based line and column.
|
|
334
|
+
*/
|
|
335
|
+
declare function toLineColumn(source: string, offset: number): {
|
|
336
|
+
line: number;
|
|
337
|
+
column: number;
|
|
338
|
+
};
|
|
339
|
+
/**
|
|
323
340
|
* Generate source code from an AST node
|
|
324
341
|
*/
|
|
325
342
|
declare function generate(node: ASTNode): string;
|
|
@@ -357,31 +374,9 @@ declare function optimize(node: ASTNode, externalVariables?: ReadonlySet<string>
|
|
|
357
374
|
*/
|
|
358
375
|
declare function parse(source: string): ASTNode;
|
|
359
376
|
declare namespace array {
|
|
360
|
-
export { ARR_UNIQUE, ARR_SUM, ARR_SORT,
|
|
377
|
+
export { ARR_UNIQUE, ARR_SUM, ARR_SORT, ARR_MIN, ARR_MAX, ARR_JOIN, ARR_FLAT };
|
|
361
378
|
}
|
|
362
379
|
/**
|
|
363
|
-
* Get the length of an array
|
|
364
|
-
*/
|
|
365
|
-
declare const ARR_LEN: (a: RuntimeValue) => RuntimeValue;
|
|
366
|
-
/**
|
|
367
|
-
* Append a value to an array, returning a new array
|
|
368
|
-
* Validates homogeneity
|
|
369
|
-
*/
|
|
370
|
-
declare const ARR_PUSH: (a: RuntimeValue, value: RuntimeValue) => RuntimeValue;
|
|
371
|
-
/**
|
|
372
|
-
* Extract a section of an array (0-based indices)
|
|
373
|
-
*/
|
|
374
|
-
declare const ARR_SLICE: (a: RuntimeValue, start: RuntimeValue, end?: RuntimeValue) => RuntimeValue;
|
|
375
|
-
/**
|
|
376
|
-
* Check if an array contains a value (using deep equality)
|
|
377
|
-
* Returns boolean
|
|
378
|
-
*/
|
|
379
|
-
declare const ARR_CONTAINS: (a: RuntimeValue, value: RuntimeValue) => RuntimeValue;
|
|
380
|
-
/**
|
|
381
|
-
* Reverse an array, returning a new array
|
|
382
|
-
*/
|
|
383
|
-
declare const ARR_REVERSE: (a: RuntimeValue) => RuntimeValue;
|
|
384
|
-
/**
|
|
385
380
|
* Sort an array in ascending order, returning a new array.
|
|
386
381
|
* Numbers sort numerically, strings lexicographically,
|
|
387
382
|
* dates/times/datetimes by temporal comparison.
|
|
@@ -419,7 +414,7 @@ declare const ARR_MIN: (a: RuntimeValue) => RuntimeValue;
|
|
|
419
414
|
*/
|
|
420
415
|
declare const ARR_MAX: (a: RuntimeValue) => RuntimeValue;
|
|
421
416
|
declare namespace core {
|
|
422
|
-
export { TYPE, STR, NUM };
|
|
417
|
+
export { TYPE, STR, SLICE, REVERSE, NUM, LEN, INDEX_OF, CONTAINS };
|
|
423
418
|
}
|
|
424
419
|
/**
|
|
425
420
|
* Convert a value to string representation
|
|
@@ -436,8 +431,33 @@ declare const NUM: (v: RuntimeValue) => RuntimeValue;
|
|
|
436
431
|
* Returns the type name of a value as a string
|
|
437
432
|
*/
|
|
438
433
|
declare const TYPE: (v: RuntimeValue) => RuntimeValue;
|
|
434
|
+
/**
|
|
435
|
+
* Get the length of a string or array
|
|
436
|
+
*/
|
|
437
|
+
declare const LEN: (v: RuntimeValue) => RuntimeValue;
|
|
438
|
+
/**
|
|
439
|
+
* Extract a section of a string or array (0-based indices)
|
|
440
|
+
*/
|
|
441
|
+
declare const SLICE: (v: RuntimeValue, start: RuntimeValue, end?: RuntimeValue) => RuntimeValue;
|
|
442
|
+
/**
|
|
443
|
+
* Check if a string contains a substring or an array contains an element.
|
|
444
|
+
* For strings: both arguments must be strings (substring search).
|
|
445
|
+
* For arrays: uses deep equality to find the element.
|
|
446
|
+
*/
|
|
447
|
+
declare const CONTAINS: (v: RuntimeValue, search: RuntimeValue) => RuntimeValue;
|
|
448
|
+
/**
|
|
449
|
+
* Reverse a string or array, returning a new value
|
|
450
|
+
*/
|
|
451
|
+
declare const REVERSE: (v: RuntimeValue) => RuntimeValue;
|
|
452
|
+
/**
|
|
453
|
+
* Find the first index of a substring in a string or an element in an array.
|
|
454
|
+
* Returns -1 if not found.
|
|
455
|
+
* For strings: both arguments must be strings.
|
|
456
|
+
* For arrays: uses deep equality.
|
|
457
|
+
*/
|
|
458
|
+
declare const INDEX_OF: (v: RuntimeValue, search: RuntimeValue) => RuntimeValue;
|
|
439
459
|
declare namespace datetime {
|
|
440
|
-
export { TODAY, START_OF_YEAR, START_OF_WEEK, START_OF_QUARTER, START_OF_MONTH,
|
|
460
|
+
export { YEAR, WEEKDAY, TODAY, START_OF_YEAR, START_OF_WEEK, START_OF_QUARTER, START_OF_MONTH, QUARTER, MONTH, IS_WEEKEND, IS_SAME_DAY, IS_LEAP_YEAR, END_OF_YEAR, END_OF_MONTH, DIFFERENCE_IN_YEARS, DIFFERENCE_IN_WEEKS, DIFFERENCE_IN_MONTHS, DIFFERENCE_IN_DAYS, DAY_OF_YEAR, DAY, DATE, AGE, ADD_YEARS, ADD_MONTHS, ADD_DAYS };
|
|
441
461
|
}
|
|
442
462
|
/**
|
|
443
463
|
* Date utility functions using Temporal.PlainDate and Temporal.PlainDateTime
|
|
@@ -455,27 +475,27 @@ declare const DATE: (year: RuntimeValue, month: RuntimeValue, day: RuntimeValue)
|
|
|
455
475
|
/**
|
|
456
476
|
* Get the year from a date or datetime
|
|
457
477
|
*/
|
|
458
|
-
declare const
|
|
478
|
+
declare const YEAR: (date: RuntimeValue) => RuntimeValue;
|
|
459
479
|
/**
|
|
460
480
|
* Get the month from a date or datetime (1-based: 1 = January, 12 = December)
|
|
461
481
|
*/
|
|
462
|
-
declare const
|
|
482
|
+
declare const MONTH: (date: RuntimeValue) => RuntimeValue;
|
|
463
483
|
/**
|
|
464
484
|
* Get the day of month from a date or datetime (1-31)
|
|
465
485
|
*/
|
|
466
|
-
declare const
|
|
486
|
+
declare const DAY: (date: RuntimeValue) => RuntimeValue;
|
|
467
487
|
/**
|
|
468
488
|
* Get the day of week from a date or datetime (1 = Monday, 7 = Sunday)
|
|
469
489
|
*/
|
|
470
|
-
declare const
|
|
490
|
+
declare const WEEKDAY: (date: RuntimeValue) => RuntimeValue;
|
|
471
491
|
/**
|
|
472
492
|
* Get the day of year (1-366) from a date or datetime
|
|
473
493
|
*/
|
|
474
|
-
declare const
|
|
494
|
+
declare const DAY_OF_YEAR: (date: RuntimeValue) => RuntimeValue;
|
|
475
495
|
/**
|
|
476
496
|
* Get the quarter (1-4) from a date or datetime
|
|
477
497
|
*/
|
|
478
|
-
declare const
|
|
498
|
+
declare const QUARTER: (date: RuntimeValue) => RuntimeValue;
|
|
479
499
|
/**
|
|
480
500
|
* Add days to a date or datetime, returning the same type
|
|
481
501
|
*/
|
|
@@ -542,6 +562,12 @@ declare const IS_WEEKEND: (date: RuntimeValue) => RuntimeValue;
|
|
|
542
562
|
* Check if a date or datetime is in a leap year
|
|
543
563
|
*/
|
|
544
564
|
declare const IS_LEAP_YEAR: (date: RuntimeValue) => RuntimeValue;
|
|
565
|
+
/**
|
|
566
|
+
* Calculate age in complete years from a birth date.
|
|
567
|
+
* Accepts PlainDate or PlainDateTime (only the date portion is used).
|
|
568
|
+
* Optional second argument specifies the reference date (defaults to today).
|
|
569
|
+
*/
|
|
570
|
+
declare const AGE: (birthDate: RuntimeValue, ...rest: RuntimeValue[]) => RuntimeValue;
|
|
545
571
|
declare namespace datetimefull {
|
|
546
572
|
export { TO_TIME, TO_DATE, START_OF_DAY, NOW, END_OF_DAY, DATETIME, COMBINE };
|
|
547
573
|
}
|
|
@@ -601,13 +627,9 @@ declare const EXP: (x: RuntimeValue) => RuntimeValue;
|
|
|
601
627
|
*/
|
|
602
628
|
declare const CLAMP: (value: RuntimeValue, min: RuntimeValue, max: RuntimeValue) => RuntimeValue;
|
|
603
629
|
declare namespace string {
|
|
604
|
-
export { STR_UPPER, STR_TRIM, STR_STARTS_WITH, STR_SPLIT,
|
|
630
|
+
export { STR_UPPER, STR_TRIM, STR_STARTS_WITH, STR_SPLIT, STR_REPLACE, STR_REPEAT, STR_LOWER, STR_ENDS_WITH };
|
|
605
631
|
}
|
|
606
632
|
/**
|
|
607
|
-
* Get the length of a string
|
|
608
|
-
*/
|
|
609
|
-
declare const STR_LEN: (s: RuntimeValue) => RuntimeValue;
|
|
610
|
-
/**
|
|
611
633
|
* Convert string to uppercase
|
|
612
634
|
*/
|
|
613
635
|
declare const STR_UPPER: (s: RuntimeValue) => RuntimeValue;
|
|
@@ -620,19 +642,6 @@ declare const STR_LOWER: (s: RuntimeValue) => RuntimeValue;
|
|
|
620
642
|
*/
|
|
621
643
|
declare const STR_TRIM: (s: RuntimeValue) => RuntimeValue;
|
|
622
644
|
/**
|
|
623
|
-
* Extract a section of a string (0-based indices)
|
|
624
|
-
*/
|
|
625
|
-
declare const STR_SLICE: (s: RuntimeValue, start: RuntimeValue, end?: RuntimeValue) => RuntimeValue;
|
|
626
|
-
/**
|
|
627
|
-
* Check if a string contains another string
|
|
628
|
-
* Returns boolean
|
|
629
|
-
*/
|
|
630
|
-
declare const STR_CONTAINS: (s: RuntimeValue, search: RuntimeValue) => RuntimeValue;
|
|
631
|
-
/**
|
|
632
|
-
* Find the first index of a substring (-1 if not found)
|
|
633
|
-
*/
|
|
634
|
-
declare const STR_INDEX_OF: (s: RuntimeValue, search: RuntimeValue) => RuntimeValue;
|
|
635
|
-
/**
|
|
636
645
|
* Split a string by a separator into a string array
|
|
637
646
|
*/
|
|
638
647
|
declare const STR_SPLIT: (s: RuntimeValue, sep: RuntimeValue) => RuntimeValue;
|
|
@@ -653,7 +662,7 @@ declare const STR_ENDS_WITH: (s: RuntimeValue, suffix: RuntimeValue) => RuntimeV
|
|
|
653
662
|
*/
|
|
654
663
|
declare const STR_REPEAT: (s: RuntimeValue, count: RuntimeValue) => RuntimeValue;
|
|
655
664
|
declare namespace time {
|
|
656
|
-
export { TIME,
|
|
665
|
+
export { TIME, SECOND, NOW_TIME, MINUTE, MILLISECOND, IS_SAME_TIME, HOUR, DIFFERENCE_IN_SECONDS, DIFFERENCE_IN_MINUTES, DIFFERENCE_IN_HOURS, ADD_SECONDS, ADD_MINUTES, ADD_HOURS };
|
|
657
666
|
}
|
|
658
667
|
/**
|
|
659
668
|
* Time utility functions using Temporal.PlainTime and Temporal.PlainDateTime
|
|
@@ -671,19 +680,19 @@ declare const NOW_TIME: () => RuntimeValue;
|
|
|
671
680
|
/**
|
|
672
681
|
* Get the hour (0-23) from a time or datetime
|
|
673
682
|
*/
|
|
674
|
-
declare const
|
|
683
|
+
declare const HOUR: (t: RuntimeValue) => RuntimeValue;
|
|
675
684
|
/**
|
|
676
685
|
* Get the minute (0-59) from a time or datetime
|
|
677
686
|
*/
|
|
678
|
-
declare const
|
|
687
|
+
declare const MINUTE: (t: RuntimeValue) => RuntimeValue;
|
|
679
688
|
/**
|
|
680
689
|
* Get the second (0-59) from a time or datetime
|
|
681
690
|
*/
|
|
682
|
-
declare const
|
|
691
|
+
declare const SECOND: (t: RuntimeValue) => RuntimeValue;
|
|
683
692
|
/**
|
|
684
693
|
* Get the millisecond (0-999) from a time or datetime
|
|
685
694
|
*/
|
|
686
|
-
declare const
|
|
695
|
+
declare const MILLISECOND: (t: RuntimeValue) => RuntimeValue;
|
|
687
696
|
/**
|
|
688
697
|
* Add hours to a time or datetime.
|
|
689
698
|
* PlainTime wraps around at midnight boundaries.
|
|
@@ -825,4 +834,4 @@ declare function visit<T>(node: ASTNode, visitor: Visitor<T>): T;
|
|
|
825
834
|
* @returns The result of visiting the node
|
|
826
835
|
*/
|
|
827
836
|
declare function visitPartial<T>(node: ASTNode, visitor: Partial<Visitor<T>>, defaultHandler: VisitorHandler<ASTNode, T>): T;
|
|
828
|
-
export { visitPartial, visit, typeOf, time, string, parse, optimize, math, isUnaryOp, isStringLiteral, isRangeExpression, isProgram, isPlaceholder, isPipeExpression, isNumberLiteral, isIndexAccess, isIfExpression, isIdentifier, isFunctionCall, isForExpression, isBooleanLiteral, isBinaryOp, isAssignment, isArrayLiteral, generate, extractInputVariables, extractAssignedVariables, evaluateScope, evaluate, defaultContext, datetimefull, datetime, core, exports_ast as ast, assertTimeOrDateTime, assertTime, assertString, assertNumber, assertDateTime, assertDateOrDateTime, assertDate, assertBoolean, assertArray, array, VisitorHandler, Visitor, UnaryOp, StringLiteral, RuntimeValue, RangeExpression, Program, Placeholder, PipeExpression, Operator, NumberLiteral, NodeKind, IndexAccess, IfExpression, Identifier2 as Identifier, FunctionCall, ForExpression, ExecutionContext, BooleanLiteral, BinaryOp, Assignment, ArrayLiteral, ASTNodeBase, ASTNode };
|
|
837
|
+
export { visitPartial, visit, typeOf, toLineColumn, time, string, parse, optimize, math, isUnaryOp, isStringLiteral, isRangeExpression, isProgram, isPlaceholder, isPipeExpression, isNumberLiteral, isIndexAccess, isIfExpression, isIdentifier, isFunctionCall, isForExpression, isBooleanLiteral, isBinaryOp, isAssignment, isArrayLiteral, generate, extractInputVariables, extractAssignedVariables, evaluateScope, evaluate, defaultContext, datetimefull, datetime, core, exports_ast as ast, assertTimeOrDateTime, assertTime, assertString, assertNumber, assertDateTime, assertDateOrDateTime, assertDate, assertBoolean, assertArray, array, VisitorHandler, Visitor, UnaryOp, StringLiteral, RuntimeValue, RangeExpression, Program, Placeholder, PipeExpression, ParseError, Operator, NumberLiteral, NodeKind, IndexAccess, IfExpression, Identifier2 as Identifier, FunctionCall, ForExpression, ExecutionContext, BooleanLiteral, BinaryOp, Assignment, ArrayLiteral, ASTNodeBase, ASTNode };
|
package/dist/index.js
CHANGED
|
@@ -413,6 +413,31 @@ function containsExternalReference(node, boundVars) {
|
|
|
413
413
|
function containsVariableReference(node) {
|
|
414
414
|
return containsExternalReference(node, new Set);
|
|
415
415
|
}
|
|
416
|
+
// src/errors.ts
|
|
417
|
+
class ParseError extends Error {
|
|
418
|
+
start;
|
|
419
|
+
end;
|
|
420
|
+
name = "ParseError";
|
|
421
|
+
constructor(message, start, end) {
|
|
422
|
+
super(message);
|
|
423
|
+
this.start = start;
|
|
424
|
+
this.end = end;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
function toLineColumn(source, offset) {
|
|
428
|
+
let line = 1;
|
|
429
|
+
let column = 1;
|
|
430
|
+
const clamped = Math.min(offset, source.length);
|
|
431
|
+
for (let i = 0;i < clamped; i++) {
|
|
432
|
+
if (source.charCodeAt(i) === 10) {
|
|
433
|
+
line++;
|
|
434
|
+
column = 1;
|
|
435
|
+
} else {
|
|
436
|
+
column++;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return { line, column };
|
|
440
|
+
}
|
|
416
441
|
// src/lexer.ts
|
|
417
442
|
var KEYWORDS = new Map([
|
|
418
443
|
["if", 28 /* If */],
|
|
@@ -594,7 +619,7 @@ function nextToken(cursor) {
|
|
|
594
619
|
}
|
|
595
620
|
return [18 /* DotDot */, start, cursor.pos];
|
|
596
621
|
}
|
|
597
|
-
throw new
|
|
622
|
+
throw new ParseError(`Unexpected character '${String.fromCharCode(ch)}'`, start, cursor.pos);
|
|
598
623
|
case 44:
|
|
599
624
|
advance(cursor);
|
|
600
625
|
return [25 /* Comma */, start, cursor.pos];
|
|
@@ -607,7 +632,7 @@ function nextToken(cursor) {
|
|
|
607
632
|
advance(cursor);
|
|
608
633
|
return [16 /* And */, start, cursor.pos];
|
|
609
634
|
}
|
|
610
|
-
throw new
|
|
635
|
+
throw new ParseError(`Unexpected character '${String.fromCharCode(ch)}'`, start, cursor.pos);
|
|
611
636
|
case 124:
|
|
612
637
|
advance(cursor);
|
|
613
638
|
if (peek(cursor) === 124) {
|
|
@@ -618,12 +643,13 @@ function nextToken(cursor) {
|
|
|
618
643
|
advance(cursor);
|
|
619
644
|
return [26 /* Pipe */, start, cursor.pos];
|
|
620
645
|
}
|
|
621
|
-
throw new
|
|
646
|
+
throw new ParseError(`Unexpected character '${String.fromCharCode(ch)}'`, start, cursor.pos);
|
|
622
647
|
case 63:
|
|
623
648
|
advance(cursor);
|
|
624
649
|
return [27 /* Question */, start, cursor.pos];
|
|
625
650
|
default:
|
|
626
|
-
|
|
651
|
+
advance(cursor);
|
|
652
|
+
throw new ParseError(`Unexpected character '${String.fromCharCode(ch)}'`, start, cursor.pos);
|
|
627
653
|
}
|
|
628
654
|
}
|
|
629
655
|
function lexString(cursor) {
|
|
@@ -640,7 +666,7 @@ function lexString(cursor) {
|
|
|
640
666
|
cursor.pos++;
|
|
641
667
|
}
|
|
642
668
|
}
|
|
643
|
-
throw new
|
|
669
|
+
throw new ParseError("Unterminated string", start, cursor.pos);
|
|
644
670
|
}
|
|
645
671
|
function lexNumber(cursor) {
|
|
646
672
|
const start = cursor.pos;
|
|
@@ -1230,13 +1256,13 @@ function parse(source) {
|
|
|
1230
1256
|
statements.push(parseExpression(state, 0));
|
|
1231
1257
|
}
|
|
1232
1258
|
if (statements.length === 0) {
|
|
1233
|
-
throw new
|
|
1259
|
+
throw new ParseError("Empty program", 0, 0);
|
|
1234
1260
|
}
|
|
1235
1261
|
const annotated = attachComments(source, statements, statementOffsets, cursor.comments);
|
|
1236
1262
|
if (annotated.statements.length === 1 && !annotated.programTrailing) {
|
|
1237
1263
|
const stmt = annotated.statements[0];
|
|
1238
1264
|
if (stmt === undefined) {
|
|
1239
|
-
throw new
|
|
1265
|
+
throw new ParseError("Unexpected empty statements array", 0, 0);
|
|
1240
1266
|
}
|
|
1241
1267
|
return stmt;
|
|
1242
1268
|
}
|
|
@@ -1346,7 +1372,7 @@ function parseExpression(state, minPrecedence) {
|
|
|
1346
1372
|
advance2(state);
|
|
1347
1373
|
const index = parseExpression(state, 0);
|
|
1348
1374
|
if (peekKind(state) !== 23 /* RBracket */) {
|
|
1349
|
-
throw new
|
|
1375
|
+
throw new ParseError("Expected closing bracket", state.currentToken[1], state.currentToken[2]);
|
|
1350
1376
|
}
|
|
1351
1377
|
advance2(state);
|
|
1352
1378
|
left = indexAccess(left, index);
|
|
@@ -1377,7 +1403,7 @@ function parsePrefix(state) {
|
|
|
1377
1403
|
advance2(state);
|
|
1378
1404
|
const expr = parseExpression(state, 0);
|
|
1379
1405
|
if (peekKind(state) !== 21 /* RParen */) {
|
|
1380
|
-
throw new
|
|
1406
|
+
throw new ParseError("Expected closing parenthesis", state.currentToken[1], state.currentToken[2]);
|
|
1381
1407
|
}
|
|
1382
1408
|
advance2(state);
|
|
1383
1409
|
return expr;
|
|
@@ -1393,7 +1419,7 @@ function parsePrefix(state) {
|
|
|
1393
1419
|
}
|
|
1394
1420
|
}
|
|
1395
1421
|
if (peekKind(state) !== 23 /* RBracket */) {
|
|
1396
|
-
throw new
|
|
1422
|
+
throw new ParseError("Expected closing bracket", state.currentToken[1], state.currentToken[2]);
|
|
1397
1423
|
}
|
|
1398
1424
|
advance2(state);
|
|
1399
1425
|
return array(elements);
|
|
@@ -1402,12 +1428,12 @@ function parsePrefix(state) {
|
|
|
1402
1428
|
advance2(state);
|
|
1403
1429
|
const condition = parseExpression(state, 0);
|
|
1404
1430
|
if (peekKind(state) !== 29 /* Then */) {
|
|
1405
|
-
throw new
|
|
1431
|
+
throw new ParseError('Expected "then" in if expression', state.currentToken[1], state.currentToken[2]);
|
|
1406
1432
|
}
|
|
1407
1433
|
advance2(state);
|
|
1408
1434
|
const consequent = parseExpression(state, 0);
|
|
1409
1435
|
if (peekKind(state) !== 30 /* Else */) {
|
|
1410
|
-
throw new
|
|
1436
|
+
throw new ParseError('Expected "else" in if expression', state.currentToken[1], state.currentToken[2]);
|
|
1411
1437
|
}
|
|
1412
1438
|
advance2(state);
|
|
1413
1439
|
const alternate = parseExpression(state, 0);
|
|
@@ -1416,12 +1442,12 @@ function parsePrefix(state) {
|
|
|
1416
1442
|
if (tokenKind === 31 /* For */) {
|
|
1417
1443
|
advance2(state);
|
|
1418
1444
|
if (peekKind(state) !== 1 /* Identifier */) {
|
|
1419
|
-
throw new
|
|
1445
|
+
throw new ParseError('Expected identifier after "for"', state.currentToken[1], state.currentToken[2]);
|
|
1420
1446
|
}
|
|
1421
1447
|
const variableName = readText(state.cursor, state.currentToken);
|
|
1422
1448
|
advance2(state);
|
|
1423
1449
|
if (peekKind(state) !== 32 /* In */) {
|
|
1424
|
-
throw new
|
|
1450
|
+
throw new ParseError('Expected "in" in for expression', state.currentToken[1], state.currentToken[2]);
|
|
1425
1451
|
}
|
|
1426
1452
|
advance2(state);
|
|
1427
1453
|
const iterable = parseExpression(state, 0);
|
|
@@ -1434,19 +1460,19 @@ function parsePrefix(state) {
|
|
|
1434
1460
|
if (peekKind(state) === 34 /* Into */) {
|
|
1435
1461
|
advance2(state);
|
|
1436
1462
|
if (peekKind(state) !== 1 /* Identifier */) {
|
|
1437
|
-
throw new
|
|
1463
|
+
throw new ParseError('Expected identifier after "into"', state.currentToken[1], state.currentToken[2]);
|
|
1438
1464
|
}
|
|
1439
1465
|
const accName = readText(state.cursor, state.currentToken);
|
|
1440
1466
|
advance2(state);
|
|
1441
1467
|
if (peekKind(state) !== 24 /* Eq */) {
|
|
1442
|
-
throw new
|
|
1468
|
+
throw new ParseError('Expected "=" after accumulator name', state.currentToken[1], state.currentToken[2]);
|
|
1443
1469
|
}
|
|
1444
1470
|
advance2(state);
|
|
1445
1471
|
const initial = parseExpression(state, 0);
|
|
1446
1472
|
accumulator = { name: accName, initial };
|
|
1447
1473
|
}
|
|
1448
1474
|
if (peekKind(state) !== 29 /* Then */) {
|
|
1449
|
-
throw new
|
|
1475
|
+
throw new ParseError('Expected "then" in for expression', state.currentToken[1], state.currentToken[2]);
|
|
1450
1476
|
}
|
|
1451
1477
|
advance2(state);
|
|
1452
1478
|
const body = parseExpression(state, 0);
|
|
@@ -1478,7 +1504,7 @@ function parsePrefix(state) {
|
|
|
1478
1504
|
advance2(state);
|
|
1479
1505
|
const args = parseFunctionArguments(state);
|
|
1480
1506
|
if (peekKind(state) !== 21 /* RParen */) {
|
|
1481
|
-
throw new
|
|
1507
|
+
throw new ParseError("Expected closing parenthesis", state.currentToken[1], state.currentToken[2]);
|
|
1482
1508
|
}
|
|
1483
1509
|
advance2(state);
|
|
1484
1510
|
return functionCall(name, args);
|
|
@@ -1487,13 +1513,13 @@ function parsePrefix(state) {
|
|
|
1487
1513
|
}
|
|
1488
1514
|
const token = state.currentToken;
|
|
1489
1515
|
const tokenText = readText(state.cursor, token);
|
|
1490
|
-
throw new
|
|
1516
|
+
throw new ParseError(`Unexpected token: ${tokenText}`, token[1], token[2]);
|
|
1491
1517
|
}
|
|
1492
1518
|
function parseInfix(state, left, precedence) {
|
|
1493
1519
|
const tokenKind = peekKind(state);
|
|
1494
1520
|
if (tokenKind === 24 /* Eq */) {
|
|
1495
1521
|
if (left.kind !== 2 /* Identifier */) {
|
|
1496
|
-
throw new
|
|
1522
|
+
throw new ParseError("Invalid assignment target", state.currentToken[1], state.currentToken[2]);
|
|
1497
1523
|
}
|
|
1498
1524
|
const identName = left.name;
|
|
1499
1525
|
advance2(state);
|
|
@@ -1509,17 +1535,17 @@ function parseInfix(state, left, precedence) {
|
|
|
1509
1535
|
if (tokenKind === 26 /* Pipe */) {
|
|
1510
1536
|
advance2(state);
|
|
1511
1537
|
if (peekKind(state) !== 1 /* Identifier */) {
|
|
1512
|
-
throw new
|
|
1538
|
+
throw new ParseError("Expected function name after |>", state.currentToken[1], state.currentToken[2]);
|
|
1513
1539
|
}
|
|
1514
1540
|
const name = readText(state.cursor, state.currentToken);
|
|
1515
1541
|
advance2(state);
|
|
1516
1542
|
if (peekKind(state) !== 20 /* LParen */) {
|
|
1517
|
-
throw new
|
|
1543
|
+
throw new ParseError("Expected ( after function name in pipe expression", state.currentToken[1], state.currentToken[2]);
|
|
1518
1544
|
}
|
|
1519
1545
|
advance2(state);
|
|
1520
1546
|
const args = parsePipeArguments(state);
|
|
1521
1547
|
if (peekKind(state) !== 21 /* RParen */) {
|
|
1522
|
-
throw new
|
|
1548
|
+
throw new ParseError("Expected closing parenthesis", state.currentToken[1], state.currentToken[2]);
|
|
1523
1549
|
}
|
|
1524
1550
|
advance2(state);
|
|
1525
1551
|
let hasPlaceholder = false;
|
|
@@ -1530,7 +1556,7 @@ function parseInfix(state, left, precedence) {
|
|
|
1530
1556
|
}
|
|
1531
1557
|
}
|
|
1532
1558
|
if (!hasPlaceholder) {
|
|
1533
|
-
throw new
|
|
1559
|
+
throw new ParseError("Pipe expression requires at least one ? placeholder", state.currentToken[1], state.currentToken[2]);
|
|
1534
1560
|
}
|
|
1535
1561
|
return pipeExpr(left, name, args);
|
|
1536
1562
|
}
|
|
@@ -1541,7 +1567,7 @@ function parseInfix(state, left, precedence) {
|
|
|
1541
1567
|
const right = parseExpression(state, isRightAssociative ? precedence : precedence + 1);
|
|
1542
1568
|
return binaryOp(left, operator, right);
|
|
1543
1569
|
}
|
|
1544
|
-
throw new
|
|
1570
|
+
throw new ParseError("Unexpected token in infix position", state.currentToken[1], state.currentToken[2]);
|
|
1545
1571
|
}
|
|
1546
1572
|
function parseFunctionArguments(state) {
|
|
1547
1573
|
if (peekKind(state) === 21 /* RParen */) {
|
|
@@ -2296,52 +2322,11 @@ __export(exports_array, {
|
|
|
2296
2322
|
ARR_UNIQUE: () => ARR_UNIQUE,
|
|
2297
2323
|
ARR_SUM: () => ARR_SUM,
|
|
2298
2324
|
ARR_SORT: () => ARR_SORT,
|
|
2299
|
-
ARR_SLICE: () => ARR_SLICE,
|
|
2300
|
-
ARR_REVERSE: () => ARR_REVERSE,
|
|
2301
|
-
ARR_PUSH: () => ARR_PUSH,
|
|
2302
2325
|
ARR_MIN: () => ARR_MIN,
|
|
2303
2326
|
ARR_MAX: () => ARR_MAX,
|
|
2304
|
-
ARR_LEN: () => ARR_LEN,
|
|
2305
2327
|
ARR_JOIN: () => ARR_JOIN,
|
|
2306
|
-
ARR_FLAT: () => ARR_FLAT
|
|
2307
|
-
ARR_CONTAINS: () => ARR_CONTAINS
|
|
2328
|
+
ARR_FLAT: () => ARR_FLAT
|
|
2308
2329
|
});
|
|
2309
|
-
var ARR_LEN = (a) => {
|
|
2310
|
-
assertArray(a, "ARR_LEN");
|
|
2311
|
-
return a.length;
|
|
2312
|
-
};
|
|
2313
|
-
var ARR_PUSH = (a, value) => {
|
|
2314
|
-
assertArray(a, "ARR_PUSH");
|
|
2315
|
-
if (a.length > 0) {
|
|
2316
|
-
const elemType = typeOf(a[0]);
|
|
2317
|
-
const valType = typeOf(value);
|
|
2318
|
-
if (elemType !== valType) {
|
|
2319
|
-
throw new TypeError(`ARR_PUSH: cannot push ${valType} into array<${elemType}>`);
|
|
2320
|
-
}
|
|
2321
|
-
}
|
|
2322
|
-
return [...a, value];
|
|
2323
|
-
};
|
|
2324
|
-
var ARR_SLICE = (a, start, end) => {
|
|
2325
|
-
assertArray(a, "ARR_SLICE");
|
|
2326
|
-
assertNumber(start, "ARR_SLICE", "start");
|
|
2327
|
-
if (end !== undefined) {
|
|
2328
|
-
assertNumber(end, "ARR_SLICE", "end");
|
|
2329
|
-
return a.slice(start, end);
|
|
2330
|
-
}
|
|
2331
|
-
return a.slice(start);
|
|
2332
|
-
};
|
|
2333
|
-
var ARR_CONTAINS = (a, value) => {
|
|
2334
|
-
assertArray(a, "ARR_CONTAINS");
|
|
2335
|
-
for (const elem of a) {
|
|
2336
|
-
if (deepEquals(elem, value))
|
|
2337
|
-
return true;
|
|
2338
|
-
}
|
|
2339
|
-
return false;
|
|
2340
|
-
};
|
|
2341
|
-
var ARR_REVERSE = (a) => {
|
|
2342
|
-
assertArray(a, "ARR_REVERSE");
|
|
2343
|
-
return [...a].reverse();
|
|
2344
|
-
};
|
|
2345
2330
|
var ARR_SORT = (a) => {
|
|
2346
2331
|
assertArray(a, "ARR_SORT");
|
|
2347
2332
|
if (a.length <= 1)
|
|
@@ -2473,7 +2458,12 @@ var exports_core = {};
|
|
|
2473
2458
|
__export(exports_core, {
|
|
2474
2459
|
TYPE: () => TYPE,
|
|
2475
2460
|
STR: () => STR,
|
|
2476
|
-
|
|
2461
|
+
SLICE: () => SLICE,
|
|
2462
|
+
REVERSE: () => REVERSE,
|
|
2463
|
+
NUM: () => NUM,
|
|
2464
|
+
LEN: () => LEN,
|
|
2465
|
+
INDEX_OF: () => INDEX_OF,
|
|
2466
|
+
CONTAINS: () => CONTAINS
|
|
2477
2467
|
});
|
|
2478
2468
|
var STR = (v) => {
|
|
2479
2469
|
if (typeof v === "string")
|
|
@@ -2504,31 +2494,81 @@ var NUM = (v) => {
|
|
|
2504
2494
|
var TYPE = (v) => {
|
|
2505
2495
|
return typeOf(v);
|
|
2506
2496
|
};
|
|
2497
|
+
function assertStringOrArray(v, context) {
|
|
2498
|
+
if (typeof v !== "string" && !Array.isArray(v)) {
|
|
2499
|
+
throw new TypeError(`${context} expected string or array, got ${typeOf(v)}`);
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
var LEN = (v) => {
|
|
2503
|
+
assertStringOrArray(v, "LEN");
|
|
2504
|
+
return v.length;
|
|
2505
|
+
};
|
|
2506
|
+
var SLICE = (v, start, end) => {
|
|
2507
|
+
assertStringOrArray(v, "SLICE");
|
|
2508
|
+
assertNumber(start, "SLICE", "start");
|
|
2509
|
+
if (end !== undefined) {
|
|
2510
|
+
assertNumber(end, "SLICE", "end");
|
|
2511
|
+
return v.slice(start, end);
|
|
2512
|
+
}
|
|
2513
|
+
return v.slice(start);
|
|
2514
|
+
};
|
|
2515
|
+
var CONTAINS = (v, search) => {
|
|
2516
|
+
assertStringOrArray(v, "CONTAINS");
|
|
2517
|
+
if (typeof v === "string") {
|
|
2518
|
+
assertString(search, "CONTAINS (search)");
|
|
2519
|
+
return v.includes(search);
|
|
2520
|
+
}
|
|
2521
|
+
for (const elem of v) {
|
|
2522
|
+
if (deepEquals(elem, search))
|
|
2523
|
+
return true;
|
|
2524
|
+
}
|
|
2525
|
+
return false;
|
|
2526
|
+
};
|
|
2527
|
+
var REVERSE = (v) => {
|
|
2528
|
+
assertStringOrArray(v, "REVERSE");
|
|
2529
|
+
if (typeof v === "string") {
|
|
2530
|
+
return Array.from(v).reverse().join("");
|
|
2531
|
+
}
|
|
2532
|
+
return [...v].reverse();
|
|
2533
|
+
};
|
|
2534
|
+
var INDEX_OF = (v, search) => {
|
|
2535
|
+
assertStringOrArray(v, "INDEX_OF");
|
|
2536
|
+
if (typeof v === "string") {
|
|
2537
|
+
assertString(search, "INDEX_OF (search)");
|
|
2538
|
+
return v.indexOf(search);
|
|
2539
|
+
}
|
|
2540
|
+
for (let i = 0;i < v.length; i++) {
|
|
2541
|
+
if (deepEquals(v[i], search))
|
|
2542
|
+
return i;
|
|
2543
|
+
}
|
|
2544
|
+
return -1;
|
|
2545
|
+
};
|
|
2507
2546
|
|
|
2508
2547
|
// src/stdlib/datetime.ts
|
|
2509
2548
|
var exports_datetime = {};
|
|
2510
2549
|
__export(exports_datetime, {
|
|
2550
|
+
YEAR: () => YEAR,
|
|
2551
|
+
WEEKDAY: () => WEEKDAY,
|
|
2511
2552
|
TODAY: () => TODAY,
|
|
2512
2553
|
START_OF_YEAR: () => START_OF_YEAR,
|
|
2513
2554
|
START_OF_WEEK: () => START_OF_WEEK,
|
|
2514
2555
|
START_OF_QUARTER: () => START_OF_QUARTER,
|
|
2515
2556
|
START_OF_MONTH: () => START_OF_MONTH,
|
|
2557
|
+
QUARTER: () => QUARTER,
|
|
2558
|
+
MONTH: () => MONTH,
|
|
2516
2559
|
IS_WEEKEND: () => IS_WEEKEND,
|
|
2517
2560
|
IS_SAME_DAY: () => IS_SAME_DAY,
|
|
2518
2561
|
IS_LEAP_YEAR: () => IS_LEAP_YEAR,
|
|
2519
|
-
GET_YEAR: () => GET_YEAR,
|
|
2520
|
-
GET_WEEKDAY: () => GET_WEEKDAY,
|
|
2521
|
-
GET_QUARTER: () => GET_QUARTER,
|
|
2522
|
-
GET_MONTH: () => GET_MONTH,
|
|
2523
|
-
GET_DAY_OF_YEAR: () => GET_DAY_OF_YEAR,
|
|
2524
|
-
GET_DAY: () => GET_DAY,
|
|
2525
2562
|
END_OF_YEAR: () => END_OF_YEAR,
|
|
2526
2563
|
END_OF_MONTH: () => END_OF_MONTH,
|
|
2527
2564
|
DIFFERENCE_IN_YEARS: () => DIFFERENCE_IN_YEARS,
|
|
2528
2565
|
DIFFERENCE_IN_WEEKS: () => DIFFERENCE_IN_WEEKS,
|
|
2529
2566
|
DIFFERENCE_IN_MONTHS: () => DIFFERENCE_IN_MONTHS,
|
|
2530
2567
|
DIFFERENCE_IN_DAYS: () => DIFFERENCE_IN_DAYS,
|
|
2568
|
+
DAY_OF_YEAR: () => DAY_OF_YEAR,
|
|
2569
|
+
DAY: () => DAY,
|
|
2531
2570
|
DATE: () => DATE,
|
|
2571
|
+
AGE: () => AGE,
|
|
2532
2572
|
ADD_YEARS: () => ADD_YEARS,
|
|
2533
2573
|
ADD_MONTHS: () => ADD_MONTHS,
|
|
2534
2574
|
ADD_DAYS: () => ADD_DAYS
|
|
@@ -2540,28 +2580,28 @@ var DATE = (year, month, day) => {
|
|
|
2540
2580
|
assertNumber(day, "DATE", "day");
|
|
2541
2581
|
return new Temporal.PlainDate(year, month, day);
|
|
2542
2582
|
};
|
|
2543
|
-
var
|
|
2544
|
-
assertDateOrDateTime(date, "
|
|
2583
|
+
var YEAR = (date) => {
|
|
2584
|
+
assertDateOrDateTime(date, "YEAR");
|
|
2545
2585
|
return date.year;
|
|
2546
2586
|
};
|
|
2547
|
-
var
|
|
2548
|
-
assertDateOrDateTime(date, "
|
|
2587
|
+
var MONTH = (date) => {
|
|
2588
|
+
assertDateOrDateTime(date, "MONTH");
|
|
2549
2589
|
return date.month;
|
|
2550
2590
|
};
|
|
2551
|
-
var
|
|
2552
|
-
assertDateOrDateTime(date, "
|
|
2591
|
+
var DAY = (date) => {
|
|
2592
|
+
assertDateOrDateTime(date, "DAY");
|
|
2553
2593
|
return date.day;
|
|
2554
2594
|
};
|
|
2555
|
-
var
|
|
2556
|
-
assertDateOrDateTime(date, "
|
|
2595
|
+
var WEEKDAY = (date) => {
|
|
2596
|
+
assertDateOrDateTime(date, "WEEKDAY");
|
|
2557
2597
|
return date.dayOfWeek;
|
|
2558
2598
|
};
|
|
2559
|
-
var
|
|
2560
|
-
assertDateOrDateTime(date, "
|
|
2599
|
+
var DAY_OF_YEAR = (date) => {
|
|
2600
|
+
assertDateOrDateTime(date, "DAY_OF_YEAR");
|
|
2561
2601
|
return date.dayOfYear;
|
|
2562
2602
|
};
|
|
2563
|
-
var
|
|
2564
|
-
assertDateOrDateTime(date, "
|
|
2603
|
+
var QUARTER = (date) => {
|
|
2604
|
+
assertDateOrDateTime(date, "QUARTER");
|
|
2565
2605
|
return Math.ceil(date.month / 3);
|
|
2566
2606
|
};
|
|
2567
2607
|
var ADD_DAYS = (date, days) => {
|
|
@@ -2676,6 +2716,22 @@ var IS_LEAP_YEAR = (date) => {
|
|
|
2676
2716
|
assertDateOrDateTime(date, "IS_LEAP_YEAR");
|
|
2677
2717
|
return date.inLeapYear;
|
|
2678
2718
|
};
|
|
2719
|
+
var AGE = (birthDate, ...rest) => {
|
|
2720
|
+
assertDateOrDateTime(birthDate, "AGE");
|
|
2721
|
+
const birth = birthDate instanceof Temporal.PlainDateTime ? birthDate.toPlainDate() : birthDate;
|
|
2722
|
+
let reference;
|
|
2723
|
+
if (rest.length > 0) {
|
|
2724
|
+
const ref = rest[0];
|
|
2725
|
+
assertDateOrDateTime(ref, "AGE");
|
|
2726
|
+
reference = ref instanceof Temporal.PlainDateTime ? ref.toPlainDate() : ref;
|
|
2727
|
+
} else {
|
|
2728
|
+
reference = Temporal.Now.plainDateISO();
|
|
2729
|
+
}
|
|
2730
|
+
if (Temporal.PlainDate.compare(birth, reference) > 0) {
|
|
2731
|
+
throw new RangeError("AGE requires birth date to be on or before reference date");
|
|
2732
|
+
}
|
|
2733
|
+
return birth.until(reference, { largestUnit: "year" }).years;
|
|
2734
|
+
};
|
|
2679
2735
|
|
|
2680
2736
|
// src/stdlib/datetimefull.ts
|
|
2681
2737
|
var exports_datetimefull = {};
|
|
@@ -2822,19 +2878,11 @@ __export(exports_string, {
|
|
|
2822
2878
|
STR_TRIM: () => STR_TRIM,
|
|
2823
2879
|
STR_STARTS_WITH: () => STR_STARTS_WITH,
|
|
2824
2880
|
STR_SPLIT: () => STR_SPLIT,
|
|
2825
|
-
STR_SLICE: () => STR_SLICE,
|
|
2826
2881
|
STR_REPLACE: () => STR_REPLACE,
|
|
2827
2882
|
STR_REPEAT: () => STR_REPEAT,
|
|
2828
2883
|
STR_LOWER: () => STR_LOWER,
|
|
2829
|
-
|
|
2830
|
-
STR_INDEX_OF: () => STR_INDEX_OF,
|
|
2831
|
-
STR_ENDS_WITH: () => STR_ENDS_WITH,
|
|
2832
|
-
STR_CONTAINS: () => STR_CONTAINS
|
|
2884
|
+
STR_ENDS_WITH: () => STR_ENDS_WITH
|
|
2833
2885
|
});
|
|
2834
|
-
var STR_LEN = (s) => {
|
|
2835
|
-
assertString(s, "STR_LEN");
|
|
2836
|
-
return s.length;
|
|
2837
|
-
};
|
|
2838
2886
|
var STR_UPPER = (s) => {
|
|
2839
2887
|
assertString(s, "STR_UPPER");
|
|
2840
2888
|
return s.toUpperCase();
|
|
@@ -2847,25 +2895,6 @@ var STR_TRIM = (s) => {
|
|
|
2847
2895
|
assertString(s, "STR_TRIM");
|
|
2848
2896
|
return s.trim();
|
|
2849
2897
|
};
|
|
2850
|
-
var STR_SLICE = (s, start, end) => {
|
|
2851
|
-
assertString(s, "STR_SLICE");
|
|
2852
|
-
assertNumber(start, "STR_SLICE", "start");
|
|
2853
|
-
if (end !== undefined) {
|
|
2854
|
-
assertNumber(end, "STR_SLICE", "end");
|
|
2855
|
-
return s.slice(start, end);
|
|
2856
|
-
}
|
|
2857
|
-
return s.slice(start);
|
|
2858
|
-
};
|
|
2859
|
-
var STR_CONTAINS = (s, search) => {
|
|
2860
|
-
assertString(s, "STR_CONTAINS");
|
|
2861
|
-
assertString(search, "STR_CONTAINS");
|
|
2862
|
-
return s.includes(search);
|
|
2863
|
-
};
|
|
2864
|
-
var STR_INDEX_OF = (s, search) => {
|
|
2865
|
-
assertString(s, "STR_INDEX_OF");
|
|
2866
|
-
assertString(search, "STR_INDEX_OF");
|
|
2867
|
-
return s.indexOf(search);
|
|
2868
|
-
};
|
|
2869
2898
|
var STR_SPLIT = (s, sep) => {
|
|
2870
2899
|
assertString(s, "STR_SPLIT");
|
|
2871
2900
|
assertString(sep, "STR_SPLIT (separator)");
|
|
@@ -2900,12 +2929,12 @@ var STR_REPEAT = (s, count) => {
|
|
|
2900
2929
|
var exports_time = {};
|
|
2901
2930
|
__export(exports_time, {
|
|
2902
2931
|
TIME: () => TIME,
|
|
2932
|
+
SECOND: () => SECOND,
|
|
2903
2933
|
NOW_TIME: () => NOW_TIME,
|
|
2934
|
+
MINUTE: () => MINUTE,
|
|
2935
|
+
MILLISECOND: () => MILLISECOND,
|
|
2904
2936
|
IS_SAME_TIME: () => IS_SAME_TIME,
|
|
2905
|
-
|
|
2906
|
-
GET_MINUTE: () => GET_MINUTE,
|
|
2907
|
-
GET_MILLISECOND: () => GET_MILLISECOND,
|
|
2908
|
-
GET_HOUR: () => GET_HOUR,
|
|
2937
|
+
HOUR: () => HOUR,
|
|
2909
2938
|
DIFFERENCE_IN_SECONDS: () => DIFFERENCE_IN_SECONDS,
|
|
2910
2939
|
DIFFERENCE_IN_MINUTES: () => DIFFERENCE_IN_MINUTES,
|
|
2911
2940
|
DIFFERENCE_IN_HOURS: () => DIFFERENCE_IN_HOURS,
|
|
@@ -2920,20 +2949,20 @@ var TIME = (hour, minute, second) => {
|
|
|
2920
2949
|
return new Temporal.PlainTime(hour, minute, second);
|
|
2921
2950
|
};
|
|
2922
2951
|
var NOW_TIME = () => Temporal.Now.plainTimeISO();
|
|
2923
|
-
var
|
|
2924
|
-
assertTimeOrDateTime(t, "
|
|
2952
|
+
var HOUR = (t) => {
|
|
2953
|
+
assertTimeOrDateTime(t, "HOUR");
|
|
2925
2954
|
return t.hour;
|
|
2926
2955
|
};
|
|
2927
|
-
var
|
|
2928
|
-
assertTimeOrDateTime(t, "
|
|
2956
|
+
var MINUTE = (t) => {
|
|
2957
|
+
assertTimeOrDateTime(t, "MINUTE");
|
|
2929
2958
|
return t.minute;
|
|
2930
2959
|
};
|
|
2931
|
-
var
|
|
2932
|
-
assertTimeOrDateTime(t, "
|
|
2960
|
+
var SECOND = (t) => {
|
|
2961
|
+
assertTimeOrDateTime(t, "SECOND");
|
|
2933
2962
|
return t.second;
|
|
2934
2963
|
};
|
|
2935
|
-
var
|
|
2936
|
-
assertTimeOrDateTime(t, "
|
|
2964
|
+
var MILLISECOND = (t) => {
|
|
2965
|
+
assertTimeOrDateTime(t, "MILLISECOND");
|
|
2937
2966
|
return t.millisecond;
|
|
2938
2967
|
};
|
|
2939
2968
|
var ADD_HOURS = (t, hours) => {
|
|
@@ -3019,6 +3048,7 @@ export {
|
|
|
3019
3048
|
visitPartial,
|
|
3020
3049
|
visit,
|
|
3021
3050
|
typeOf,
|
|
3051
|
+
toLineColumn,
|
|
3022
3052
|
exports_time as time,
|
|
3023
3053
|
exports_string as string,
|
|
3024
3054
|
parse,
|
|
@@ -3060,5 +3090,6 @@ export {
|
|
|
3060
3090
|
assertBoolean,
|
|
3061
3091
|
assertArray,
|
|
3062
3092
|
exports_array as array,
|
|
3093
|
+
ParseError,
|
|
3063
3094
|
NodeKind
|
|
3064
3095
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "littlewing",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "A minimal, high-performance multi-type expression language with lexer, parser, and interpreter. Optimized for browsers with type-safe execution.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"calculator",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"test": "bun test",
|
|
52
52
|
"test:coverage": "bun test --coverage",
|
|
53
53
|
"test:watch": "bun test --watch",
|
|
54
|
+
"prepublishOnly": "cp ../../README.md ../../LICENSE .",
|
|
54
55
|
"release": "bumpp --commit --push --tag"
|
|
55
56
|
},
|
|
56
57
|
"devDependencies": {
|