@texuli/core 0.7.0 → 0.8.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 +11 -5
- package/dist/data-source/collection.d.ts +67 -0
- package/dist/data-source/collection.js +247 -0
- package/dist/data-source/errors.d.ts +58 -0
- package/dist/data-source/errors.js +138 -0
- package/dist/data-source/index.d.ts +17 -0
- package/dist/data-source/index.js +17 -0
- package/dist/data-source/loader.d.ts +120 -0
- package/dist/data-source/loader.js +571 -0
- package/dist/data-source/query.d.ts +78 -0
- package/dist/data-source/query.js +339 -0
- package/dist/data-source/registry.d.ts +83 -0
- package/dist/data-source/registry.js +63 -0
- package/dist/data-source/schema.d.ts +122 -0
- package/dist/data-source/schema.js +197 -0
- package/dist/expression/analyze.d.ts +17 -0
- package/dist/expression/analyze.js +151 -0
- package/dist/expression/evaluate.d.ts +55 -0
- package/dist/expression/evaluate.js +365 -0
- package/dist/expression/functions.d.ts +7 -0
- package/dist/expression/functions.js +374 -0
- package/dist/expression/index.d.ts +7 -0
- package/dist/expression/index.js +12 -0
- package/dist/expression/parse.d.ts +19 -0
- package/dist/expression/parse.js +284 -0
- package/dist/expression/text.d.ts +15 -0
- package/dist/expression/text.js +489 -0
- package/dist/expression/types.d.ts +197 -0
- package/dist/expression/types.js +35 -0
- package/dist/expression/values.d.ts +14 -0
- package/dist/expression/values.js +68 -0
- package/dist/i18n/catalog.d.ts +51 -0
- package/dist/i18n/catalog.js +109 -0
- package/dist/i18n/format.d.ts +41 -0
- package/dist/i18n/format.js +117 -0
- package/dist/i18n/i18n.d.ts +70 -0
- package/dist/i18n/i18n.js +185 -0
- package/dist/i18n/index.d.ts +8 -0
- package/dist/i18n/index.js +8 -0
- package/dist/i18n/pseudo.d.ts +4 -0
- package/dist/i18n/pseudo.js +41 -0
- package/package.json +4 -1
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { ERRORS, isExpressionError, LIMIT, VALUE } from "./values.js";
|
|
2
|
+
/*
|
|
3
|
+
* The core function table: small, pure, bounded functions for text, lists,
|
|
4
|
+
* numbers and ISO dates. Forms evaluate with it; a dialect (the planned
|
|
5
|
+
* sheets formula layer) passes its own table, which can spread this one.
|
|
6
|
+
* Functions that scan their input charge one step per 16 code units or one
|
|
7
|
+
* per list item, so a hostile 32 KB string costs what it reads.
|
|
8
|
+
*/
|
|
9
|
+
const isList = (value) => Array.isArray(value);
|
|
10
|
+
function str(value, context) {
|
|
11
|
+
if (value === null)
|
|
12
|
+
return '';
|
|
13
|
+
if (typeof value === 'string')
|
|
14
|
+
return context.charge(value.length / 16) ? value : null;
|
|
15
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
16
|
+
return String(value);
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
function scan(context, values, run) {
|
|
20
|
+
const texts = [];
|
|
21
|
+
for (const value of values) {
|
|
22
|
+
const text = str(value, context);
|
|
23
|
+
if (text === null)
|
|
24
|
+
return isList(value) || isExpressionError(value) ? VALUE : LIMIT;
|
|
25
|
+
texts.push(text);
|
|
26
|
+
}
|
|
27
|
+
return run(...texts);
|
|
28
|
+
}
|
|
29
|
+
const fn = (definition) => Object.freeze(definition);
|
|
30
|
+
// ISO calendar days (proleptic Gregorian), without Date parsing quirks.
|
|
31
|
+
const ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
32
|
+
function daysInMonth(year, month) {
|
|
33
|
+
if (month === 2)
|
|
34
|
+
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0 ? 29 : 28;
|
|
35
|
+
return [4, 6, 9, 11].includes(month) ? 30 : 31;
|
|
36
|
+
}
|
|
37
|
+
/** Days since 1970-01-01 for a valid `YYYY-MM-DD`, else null. */
|
|
38
|
+
export function isoDateToDays(value) {
|
|
39
|
+
if (typeof value !== 'string' || value.length !== 10)
|
|
40
|
+
return null;
|
|
41
|
+
const match = ISO_DATE.exec(value);
|
|
42
|
+
if (!match)
|
|
43
|
+
return null;
|
|
44
|
+
const year = Number(match[1]);
|
|
45
|
+
const month = Number(match[2]);
|
|
46
|
+
const day = Number(match[3]);
|
|
47
|
+
if (month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month))
|
|
48
|
+
return null;
|
|
49
|
+
// Howard Hinnant's days_from_civil.
|
|
50
|
+
const y = month <= 2 ? year - 1 : year;
|
|
51
|
+
const era = Math.floor(y / 400);
|
|
52
|
+
const yoe = y - era * 400;
|
|
53
|
+
const mp = (month + 9) % 12;
|
|
54
|
+
const doy = Math.floor((153 * mp + 2) / 5) + day - 1;
|
|
55
|
+
const doe = yoe * 365 + Math.floor(yoe / 4) - Math.floor(yoe / 100) + doy;
|
|
56
|
+
return era * 146097 + doe - 719468;
|
|
57
|
+
}
|
|
58
|
+
/** The `YYYY-MM-DD` of a day count since 1970-01-01 (years 0000–9999), else null. */
|
|
59
|
+
export function daysToIsoDate(days) {
|
|
60
|
+
if (!Number.isInteger(days))
|
|
61
|
+
return null;
|
|
62
|
+
const z = days + 719468;
|
|
63
|
+
const era = Math.floor(z / 146097);
|
|
64
|
+
const doe = z - era * 146097;
|
|
65
|
+
const yoe = Math.floor((doe - Math.floor(doe / 1460) + Math.floor(doe / 36524) - Math.floor(doe / 146096)) / 365);
|
|
66
|
+
const doy = doe - (365 * yoe + Math.floor(yoe / 4) - Math.floor(yoe / 100));
|
|
67
|
+
const mp = Math.floor((5 * doy + 2) / 153);
|
|
68
|
+
const day = doy - Math.floor((153 * mp + 2) / 5) + 1;
|
|
69
|
+
const month = mp < 10 ? mp + 3 : mp - 9;
|
|
70
|
+
const year = yoe + era * 400 + (month <= 2 ? 1 : 0);
|
|
71
|
+
if (year < 0 || year > 9999)
|
|
72
|
+
return null;
|
|
73
|
+
const pad = (n, width) => String(n).padStart(width, '0');
|
|
74
|
+
return `${pad(year, 4)}-${pad(month, 2)}-${pad(day, 2)}`;
|
|
75
|
+
}
|
|
76
|
+
function dateArg(value) {
|
|
77
|
+
if (value === null)
|
|
78
|
+
return null;
|
|
79
|
+
const days = isoDateToDays(value);
|
|
80
|
+
return days === null ? VALUE : days;
|
|
81
|
+
}
|
|
82
|
+
function numArg(value) {
|
|
83
|
+
if (value === null)
|
|
84
|
+
return null;
|
|
85
|
+
if (typeof value === 'number')
|
|
86
|
+
return value;
|
|
87
|
+
return VALUE;
|
|
88
|
+
}
|
|
89
|
+
function isEmptyValue(value) {
|
|
90
|
+
if (value === null)
|
|
91
|
+
return true;
|
|
92
|
+
if (typeof value === 'string')
|
|
93
|
+
return value.trim().length === 0;
|
|
94
|
+
if (isList(value))
|
|
95
|
+
return value.length === 0;
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
/** The core function table (ADR-015). Names are case-sensitive. */
|
|
99
|
+
export const CORE_FUNCTIONS = Object.freeze({
|
|
100
|
+
len: fn({
|
|
101
|
+
minArgs: 1,
|
|
102
|
+
maxArgs: 1,
|
|
103
|
+
params: ['any'],
|
|
104
|
+
returns: 'number',
|
|
105
|
+
description: 'Characters in a string, or items in a list; 0 for empty.',
|
|
106
|
+
call: ([value = null], context) => {
|
|
107
|
+
if (isList(value))
|
|
108
|
+
return value.length;
|
|
109
|
+
const text = str(value, context);
|
|
110
|
+
return text === null ? LIMIT : text.length;
|
|
111
|
+
},
|
|
112
|
+
}),
|
|
113
|
+
lower: fn({
|
|
114
|
+
minArgs: 1,
|
|
115
|
+
maxArgs: 1,
|
|
116
|
+
params: ['string'],
|
|
117
|
+
returns: 'string',
|
|
118
|
+
description: 'Lowercase.',
|
|
119
|
+
call: (args, context) => scan(context, args, (s) => s.toLowerCase()),
|
|
120
|
+
}),
|
|
121
|
+
upper: fn({
|
|
122
|
+
minArgs: 1,
|
|
123
|
+
maxArgs: 1,
|
|
124
|
+
params: ['string'],
|
|
125
|
+
returns: 'string',
|
|
126
|
+
description: 'Uppercase.',
|
|
127
|
+
call: (args, context) => scan(context, args, (s) => s.toUpperCase()),
|
|
128
|
+
}),
|
|
129
|
+
trim: fn({
|
|
130
|
+
minArgs: 1,
|
|
131
|
+
maxArgs: 1,
|
|
132
|
+
params: ['string'],
|
|
133
|
+
returns: 'string',
|
|
134
|
+
description: 'Removes leading and trailing whitespace.',
|
|
135
|
+
call: (args, context) => scan(context, args, (s) => s.trim()),
|
|
136
|
+
}),
|
|
137
|
+
contains: fn({
|
|
138
|
+
minArgs: 2,
|
|
139
|
+
maxArgs: 2,
|
|
140
|
+
params: ['string', 'string'],
|
|
141
|
+
returns: 'boolean',
|
|
142
|
+
description: 'Whether the first string contains the second (case-sensitive; use lower for both).',
|
|
143
|
+
call: (args, context) => scan(context, args, (s, part) => s.includes(part)),
|
|
144
|
+
}),
|
|
145
|
+
startsWith: fn({
|
|
146
|
+
minArgs: 2,
|
|
147
|
+
maxArgs: 2,
|
|
148
|
+
params: ['string', 'string'],
|
|
149
|
+
returns: 'boolean',
|
|
150
|
+
description: 'Whether the first string starts with the second.',
|
|
151
|
+
call: (args, context) => scan(context, args, (s, part) => s.startsWith(part)),
|
|
152
|
+
}),
|
|
153
|
+
endsWith: fn({
|
|
154
|
+
minArgs: 2,
|
|
155
|
+
maxArgs: 2,
|
|
156
|
+
params: ['string', 'string'],
|
|
157
|
+
returns: 'boolean',
|
|
158
|
+
description: 'Whether the first string ends with the second.',
|
|
159
|
+
call: (args, context) => scan(context, args, (s, part) => s.endsWith(part)),
|
|
160
|
+
}),
|
|
161
|
+
isEmpty: fn({
|
|
162
|
+
minArgs: 1,
|
|
163
|
+
maxArgs: 1,
|
|
164
|
+
params: ['any'],
|
|
165
|
+
returns: 'boolean',
|
|
166
|
+
description: 'True for null, a blank string or an empty list.',
|
|
167
|
+
call: ([value = null]) => isEmptyValue(value),
|
|
168
|
+
}),
|
|
169
|
+
coalesce: fn({
|
|
170
|
+
minArgs: 1,
|
|
171
|
+
maxArgs: 16,
|
|
172
|
+
params: ['any'],
|
|
173
|
+
returns: 'any',
|
|
174
|
+
description: 'The first argument that is not empty, else null.',
|
|
175
|
+
call: (args) => args.find((value) => !isEmptyValue(value)) ?? null,
|
|
176
|
+
}),
|
|
177
|
+
includes: fn({
|
|
178
|
+
minArgs: 2,
|
|
179
|
+
maxArgs: 2,
|
|
180
|
+
params: ['list', 'any'],
|
|
181
|
+
returns: 'boolean',
|
|
182
|
+
description: 'Whether a list (a multiple choice or tags answer) holds a value.',
|
|
183
|
+
call: ([list = null, item = null], context) => {
|
|
184
|
+
if (list === null)
|
|
185
|
+
return false;
|
|
186
|
+
if (!isList(list) || isList(item))
|
|
187
|
+
return VALUE;
|
|
188
|
+
if (!context.charge(list.length))
|
|
189
|
+
return LIMIT;
|
|
190
|
+
return list.includes(item);
|
|
191
|
+
},
|
|
192
|
+
}),
|
|
193
|
+
count: fn({
|
|
194
|
+
minArgs: 1,
|
|
195
|
+
maxArgs: 1,
|
|
196
|
+
params: ['list'],
|
|
197
|
+
returns: 'number',
|
|
198
|
+
description: 'Items in a list that are not null.',
|
|
199
|
+
call: ([list = null], context) => {
|
|
200
|
+
if (list === null)
|
|
201
|
+
return 0;
|
|
202
|
+
if (!isList(list))
|
|
203
|
+
return VALUE;
|
|
204
|
+
if (!context.charge(list.length))
|
|
205
|
+
return LIMIT;
|
|
206
|
+
return list.filter((item) => item !== null).length;
|
|
207
|
+
},
|
|
208
|
+
}),
|
|
209
|
+
sum: fn({
|
|
210
|
+
minArgs: 1,
|
|
211
|
+
maxArgs: 1,
|
|
212
|
+
params: ['list'],
|
|
213
|
+
returns: 'number',
|
|
214
|
+
description: 'The sum of the numbers in a list (other items are skipped).',
|
|
215
|
+
call: ([list = null], context) => {
|
|
216
|
+
if (list === null)
|
|
217
|
+
return 0;
|
|
218
|
+
if (!isList(list))
|
|
219
|
+
return typeof list === 'number' ? list : VALUE;
|
|
220
|
+
if (!context.charge(list.length))
|
|
221
|
+
return LIMIT;
|
|
222
|
+
let total = 0;
|
|
223
|
+
for (const item of list)
|
|
224
|
+
if (typeof item === 'number')
|
|
225
|
+
total += item;
|
|
226
|
+
return Number.isFinite(total) ? total : ERRORS['#NUM!'];
|
|
227
|
+
},
|
|
228
|
+
}),
|
|
229
|
+
abs: fn({
|
|
230
|
+
minArgs: 1,
|
|
231
|
+
maxArgs: 1,
|
|
232
|
+
params: ['number'],
|
|
233
|
+
returns: 'number',
|
|
234
|
+
description: 'Absolute value.',
|
|
235
|
+
call: ([value = null]) => {
|
|
236
|
+
const n = numArg(value);
|
|
237
|
+
return n === null || n === VALUE ? n : Math.abs(n);
|
|
238
|
+
},
|
|
239
|
+
}),
|
|
240
|
+
floor: fn({
|
|
241
|
+
minArgs: 1,
|
|
242
|
+
maxArgs: 1,
|
|
243
|
+
params: ['number'],
|
|
244
|
+
returns: 'number',
|
|
245
|
+
description: 'Rounds down to an integer.',
|
|
246
|
+
call: ([value = null]) => {
|
|
247
|
+
const n = numArg(value);
|
|
248
|
+
return n === null || n === VALUE ? n : Math.floor(n);
|
|
249
|
+
},
|
|
250
|
+
}),
|
|
251
|
+
ceil: fn({
|
|
252
|
+
minArgs: 1,
|
|
253
|
+
maxArgs: 1,
|
|
254
|
+
params: ['number'],
|
|
255
|
+
returns: 'number',
|
|
256
|
+
description: 'Rounds up to an integer.',
|
|
257
|
+
call: ([value = null]) => {
|
|
258
|
+
const n = numArg(value);
|
|
259
|
+
return n === null || n === VALUE ? n : Math.ceil(n);
|
|
260
|
+
},
|
|
261
|
+
}),
|
|
262
|
+
number: fn({
|
|
263
|
+
minArgs: 1,
|
|
264
|
+
maxArgs: 1,
|
|
265
|
+
params: ['any'],
|
|
266
|
+
returns: 'number',
|
|
267
|
+
description: 'A number from a number or a plain decimal string ("12.5"); null when blank.',
|
|
268
|
+
call: ([value = null]) => {
|
|
269
|
+
if (value === null || typeof value === 'number')
|
|
270
|
+
return value;
|
|
271
|
+
if (typeof value !== 'string')
|
|
272
|
+
return VALUE;
|
|
273
|
+
const text = value.trim();
|
|
274
|
+
if (text === '')
|
|
275
|
+
return null;
|
|
276
|
+
if (text.length > 64 || !/^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d{1,3})?$/.test(text))
|
|
277
|
+
return VALUE;
|
|
278
|
+
const n = Number(text);
|
|
279
|
+
return Number.isFinite(n) ? n : ERRORS['#NUM!'];
|
|
280
|
+
},
|
|
281
|
+
}),
|
|
282
|
+
today: fn({
|
|
283
|
+
minArgs: 0,
|
|
284
|
+
maxArgs: 0,
|
|
285
|
+
params: [],
|
|
286
|
+
returns: 'date',
|
|
287
|
+
description: "The host clock's UTC day as YYYY-MM-DD; #N/A when the host gave no clock.",
|
|
288
|
+
call: (_args, context) => {
|
|
289
|
+
const now = context.now();
|
|
290
|
+
if (now === undefined)
|
|
291
|
+
return ERRORS['#N/A'];
|
|
292
|
+
return daysToIsoDate(Math.floor(now / 86_400_000)) ?? ERRORS['#NUM!'];
|
|
293
|
+
},
|
|
294
|
+
}),
|
|
295
|
+
date: fn({
|
|
296
|
+
minArgs: 3,
|
|
297
|
+
maxArgs: 3,
|
|
298
|
+
params: ['number', 'number', 'number'],
|
|
299
|
+
returns: 'date',
|
|
300
|
+
description: 'The YYYY-MM-DD for a year, month (1–12) and day; #NUM! when not a real day.',
|
|
301
|
+
call: (args) => {
|
|
302
|
+
const [y, m, d] = args;
|
|
303
|
+
if (![y, m, d].every((n) => typeof n === 'number' && Number.isInteger(n)))
|
|
304
|
+
return VALUE;
|
|
305
|
+
const text = `${String(y).padStart(4, '0')}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
|
|
306
|
+
return isoDateToDays(text) === null ? ERRORS['#NUM!'] : text;
|
|
307
|
+
},
|
|
308
|
+
}),
|
|
309
|
+
isDate: fn({
|
|
310
|
+
minArgs: 1,
|
|
311
|
+
maxArgs: 1,
|
|
312
|
+
params: ['any'],
|
|
313
|
+
returns: 'boolean',
|
|
314
|
+
description: 'Whether a value is a real calendar day written YYYY-MM-DD.',
|
|
315
|
+
call: ([value = null]) => isoDateToDays(value) !== null,
|
|
316
|
+
}),
|
|
317
|
+
daysBetween: fn({
|
|
318
|
+
minArgs: 2,
|
|
319
|
+
maxArgs: 2,
|
|
320
|
+
params: ['date', 'date'],
|
|
321
|
+
returns: 'number',
|
|
322
|
+
description: 'Whole days from the first date to the second (negative when earlier).',
|
|
323
|
+
call: ([a = null, b = null]) => {
|
|
324
|
+
const x = dateArg(a);
|
|
325
|
+
const y = dateArg(b);
|
|
326
|
+
if (x === VALUE || y === VALUE)
|
|
327
|
+
return VALUE;
|
|
328
|
+
if (x === null || y === null)
|
|
329
|
+
return null;
|
|
330
|
+
return y - x;
|
|
331
|
+
},
|
|
332
|
+
}),
|
|
333
|
+
addDays: fn({
|
|
334
|
+
minArgs: 2,
|
|
335
|
+
maxArgs: 2,
|
|
336
|
+
params: ['date', 'number'],
|
|
337
|
+
returns: 'date',
|
|
338
|
+
description: 'The date a number of days after (or before, when negative) a date.',
|
|
339
|
+
call: ([a = null, n = null]) => {
|
|
340
|
+
const x = dateArg(a);
|
|
341
|
+
if (x === VALUE)
|
|
342
|
+
return VALUE;
|
|
343
|
+
if (x === null || n === null)
|
|
344
|
+
return null;
|
|
345
|
+
if (typeof n !== 'number' || !Number.isInteger(n))
|
|
346
|
+
return VALUE;
|
|
347
|
+
return daysToIsoDate(x + n) ?? ERRORS['#NUM!'];
|
|
348
|
+
},
|
|
349
|
+
}),
|
|
350
|
+
year: fn({
|
|
351
|
+
minArgs: 1,
|
|
352
|
+
maxArgs: 1,
|
|
353
|
+
params: ['date'],
|
|
354
|
+
returns: 'number',
|
|
355
|
+
description: 'The year of a date.',
|
|
356
|
+
call: ([a = null]) => dateArg(a) === VALUE ? VALUE : a === null ? null : Number(a.slice(0, 4)),
|
|
357
|
+
}),
|
|
358
|
+
month: fn({
|
|
359
|
+
minArgs: 1,
|
|
360
|
+
maxArgs: 1,
|
|
361
|
+
params: ['date'],
|
|
362
|
+
returns: 'number',
|
|
363
|
+
description: 'The month (1–12) of a date.',
|
|
364
|
+
call: ([a = null]) => dateArg(a) === VALUE ? VALUE : a === null ? null : Number(a.slice(5, 7)),
|
|
365
|
+
}),
|
|
366
|
+
day: fn({
|
|
367
|
+
minArgs: 1,
|
|
368
|
+
maxArgs: 1,
|
|
369
|
+
params: ['date'],
|
|
370
|
+
returns: 'number',
|
|
371
|
+
description: 'The day of the month of a date.',
|
|
372
|
+
call: ([a = null]) => dateArg(a) === VALUE ? VALUE : a === null ? null : Number(a.slice(8, 10)),
|
|
373
|
+
}),
|
|
374
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { analyzeExpression, type ExpressionAnalysis, type ExpressionAnalysisOptions, isAssignable, } from './analyze.js';
|
|
2
|
+
export { createExpressionBudget, type ExpressionBudget, type ExpressionEnv, type ExpressionEvaluateOptions, evaluateExpression, } from './evaluate.js';
|
|
3
|
+
export { CORE_FUNCTIONS, daysToIsoDate, isoDateToDays } from './functions.js';
|
|
4
|
+
export { children as expressionChildren, EXPRESSION_OPS, type ExpressionParseOptions, parseExpression, } from './parse.js';
|
|
5
|
+
export { formatExpression, parseExpressionText } from './text.js';
|
|
6
|
+
export { EXPRESSION_LIMITS, EXPRESSION_NAME, type Expression, type ExpressionArithmeticOp, type ExpressionComparisonOp, type ExpressionDiagnostic, type ExpressionDiagnosticCode, type ExpressionError, type ExpressionErrorCode, type ExpressionFunction, type ExpressionFunctionContext, type ExpressionFunctionTable, type ExpressionNode, type ExpressionOp, type ExpressionParseResult, type ExpressionReferenceDialect, type ExpressionScalar, type ExpressionType, type ExpressionValue, } from './types.js';
|
|
7
|
+
export { expressionError, isExpressionError, isTrue, roundHalfAway, toExpressionValue, } from './values.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// @texuli/core/expression: the closed expression language of ADR-015 — the
|
|
2
|
+
// JSON AST, its text form, static analysis and the bounded evaluator. Forms
|
|
3
|
+
// evaluate their conditions and rules with it; the planned sheets formula
|
|
4
|
+
// layer extends it through the reference dialect, its own function table and
|
|
5
|
+
// a shared recalculation budget (core-expression.md, "Extending").
|
|
6
|
+
export { analyzeExpression, isAssignable, } from "./analyze.js";
|
|
7
|
+
export { createExpressionBudget, evaluateExpression, } from "./evaluate.js";
|
|
8
|
+
export { CORE_FUNCTIONS, daysToIsoDate, isoDateToDays } from "./functions.js";
|
|
9
|
+
export { children as expressionChildren, EXPRESSION_OPS, parseExpression, } from "./parse.js";
|
|
10
|
+
export { formatExpression, parseExpressionText } from "./text.js";
|
|
11
|
+
export { EXPRESSION_LIMITS, EXPRESSION_NAME, } from "./types.js";
|
|
12
|
+
export { expressionError, isExpressionError, isTrue, roundHalfAway, toExpressionValue, } from "./values.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Expression, type ExpressionFunctionTable, type ExpressionNode, type ExpressionOp, type ExpressionParseResult, type ExpressionReferenceDialect } from './types.js';
|
|
2
|
+
/** Every op of the AST, in a stable order. */
|
|
3
|
+
export declare const EXPRESSION_OPS: readonly ExpressionOp[];
|
|
4
|
+
/** Options for `parseExpression` and `parseExpressionText`. */
|
|
5
|
+
export interface ExpressionParseOptions {
|
|
6
|
+
/** The closed function table calls may name (default `CORE_FUNCTIONS`). */
|
|
7
|
+
readonly functions?: ExpressionFunctionTable;
|
|
8
|
+
/** Enables `ref` and `range` nodes; without it they are refused (`refs-disabled`). */
|
|
9
|
+
readonly references?: ExpressionReferenceDialect;
|
|
10
|
+
}
|
|
11
|
+
/** Turns a validated AST into an `Expression` with its dependencies. */
|
|
12
|
+
export declare function summarize(ast: ExpressionNode, nodes: number, depth: number): Expression;
|
|
13
|
+
/** The child nodes of a node, in member order. */
|
|
14
|
+
export declare function children(node: ExpressionNode): readonly ExpressionNode[];
|
|
15
|
+
/**
|
|
16
|
+
* Parse an untrusted value as an expression AST (the JSON form). Never
|
|
17
|
+
* throws; on success the AST is a frozen copy holding only known members.
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseExpression(input: unknown, options?: ExpressionParseOptions): ExpressionParseResult;
|