feelin 4.3.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.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +10 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +43 -0
- data/feelin.gemspec +22 -0
- data/lib/feelin/js/node_modules/.package-lock.json +67 -0
- data/lib/feelin/js/node_modules/@lezer/common/LICENSE +21 -0
- data/lib/feelin/js/node_modules/@lezer/common/README.md +14 -0
- data/lib/feelin/js/node_modules/@lezer/common/dist/index.cjs +2181 -0
- data/lib/feelin/js/node_modules/@lezer/common/dist/index.d.cts +1137 -0
- data/lib/feelin/js/node_modules/@lezer/common/dist/index.d.ts +1137 -0
- data/lib/feelin/js/node_modules/@lezer/common/dist/index.js +2168 -0
- data/lib/feelin/js/node_modules/@lezer/common/package.json +32 -0
- data/lib/feelin/js/node_modules/@lezer/highlight/LICENSE +21 -0
- data/lib/feelin/js/node_modules/@lezer/highlight/README.md +14 -0
- data/lib/feelin/js/node_modules/@lezer/highlight/dist/index.cjs +915 -0
- data/lib/feelin/js/node_modules/@lezer/highlight/dist/index.d.cts +621 -0
- data/lib/feelin/js/node_modules/@lezer/highlight/dist/index.d.ts +623 -0
- data/lib/feelin/js/node_modules/@lezer/highlight/dist/index.js +904 -0
- data/lib/feelin/js/node_modules/@lezer/highlight/package.json +31 -0
- data/lib/feelin/js/node_modules/@lezer/lr/LICENSE +21 -0
- data/lib/feelin/js/node_modules/@lezer/lr/README.md +25 -0
- data/lib/feelin/js/node_modules/@lezer/lr/dist/constants.d.ts +45 -0
- data/lib/feelin/js/node_modules/@lezer/lr/dist/constants.js +5 -0
- data/lib/feelin/js/node_modules/@lezer/lr/dist/index.cjs +1890 -0
- data/lib/feelin/js/node_modules/@lezer/lr/dist/index.d.cts +303 -0
- data/lib/feelin/js/node_modules/@lezer/lr/dist/index.d.ts +303 -0
- data/lib/feelin/js/node_modules/@lezer/lr/dist/index.js +1883 -0
- data/lib/feelin/js/node_modules/@lezer/lr/package.json +32 -0
- data/lib/feelin/js/node_modules/feelin/LICENSE +21 -0
- data/lib/feelin/js/node_modules/feelin/README.md +65 -0
- data/lib/feelin/js/node_modules/feelin/dist/builtins.d.ts +355 -0
- data/lib/feelin/js/node_modules/feelin/dist/index.cjs +2072 -0
- data/lib/feelin/js/node_modules/feelin/dist/index.cjs.map +1 -0
- data/lib/feelin/js/node_modules/feelin/dist/index.d.ts +3 -0
- data/lib/feelin/js/node_modules/feelin/dist/index.esm.js +2063 -0
- data/lib/feelin/js/node_modules/feelin/dist/index.esm.js.map +1 -0
- data/lib/feelin/js/node_modules/feelin/dist/interpreter.d.ts +26 -0
- data/lib/feelin/js/node_modules/feelin/dist/parser.d.ts +4 -0
- data/lib/feelin/js/node_modules/feelin/dist/temporal.d.ts +6 -0
- data/lib/feelin/js/node_modules/feelin/dist/types.d.ts +35 -0
- data/lib/feelin/js/node_modules/feelin/dist/utils.d.ts +12 -0
- data/lib/feelin/js/node_modules/feelin/package.json +63 -0
- data/lib/feelin/js/node_modules/lezer-feel/LICENSE +21 -0
- data/lib/feelin/js/node_modules/lezer-feel/README.md +94 -0
- data/lib/feelin/js/node_modules/lezer-feel/dist/index.cjs +1328 -0
- data/lib/feelin/js/node_modules/lezer-feel/dist/index.cjs.map +1 -0
- data/lib/feelin/js/node_modules/lezer-feel/dist/index.d.ts +32 -0
- data/lib/feelin/js/node_modules/lezer-feel/dist/index.js +1323 -0
- data/lib/feelin/js/node_modules/lezer-feel/dist/index.js.map +1 -0
- data/lib/feelin/js/node_modules/lezer-feel/package.json +61 -0
- data/lib/feelin/js/node_modules/luxon/LICENSE.md +7 -0
- data/lib/feelin/js/node_modules/luxon/README.md +55 -0
- data/lib/feelin/js/node_modules/luxon/build/amd/luxon.js +8623 -0
- data/lib/feelin/js/node_modules/luxon/build/amd/luxon.js.map +1 -0
- data/lib/feelin/js/node_modules/luxon/build/cjs-browser/luxon.js +8621 -0
- data/lib/feelin/js/node_modules/luxon/build/cjs-browser/luxon.js.map +1 -0
- data/lib/feelin/js/node_modules/luxon/build/es6/luxon.js +8011 -0
- data/lib/feelin/js/node_modules/luxon/build/es6/luxon.js.map +1 -0
- data/lib/feelin/js/node_modules/luxon/build/global/luxon.js +8626 -0
- data/lib/feelin/js/node_modules/luxon/build/global/luxon.js.map +1 -0
- data/lib/feelin/js/node_modules/luxon/build/global/luxon.min.js +1 -0
- data/lib/feelin/js/node_modules/luxon/build/global/luxon.min.js.map +1 -0
- data/lib/feelin/js/node_modules/luxon/build/node/luxon.js +7679 -0
- data/lib/feelin/js/node_modules/luxon/build/node/luxon.js.map +1 -0
- data/lib/feelin/js/node_modules/luxon/package.json +87 -0
- data/lib/feelin/js/node_modules/luxon/src/datetime.js +2566 -0
- data/lib/feelin/js/node_modules/luxon/src/duration.js +990 -0
- data/lib/feelin/js/node_modules/luxon/src/errors.js +61 -0
- data/lib/feelin/js/node_modules/luxon/src/impl/conversions.js +206 -0
- data/lib/feelin/js/node_modules/luxon/src/impl/diff.js +95 -0
- data/lib/feelin/js/node_modules/luxon/src/impl/digits.js +90 -0
- data/lib/feelin/js/node_modules/luxon/src/impl/english.js +233 -0
- data/lib/feelin/js/node_modules/luxon/src/impl/formats.js +176 -0
- data/lib/feelin/js/node_modules/luxon/src/impl/formatter.js +409 -0
- data/lib/feelin/js/node_modules/luxon/src/impl/invalid.js +14 -0
- data/lib/feelin/js/node_modules/luxon/src/impl/locale.js +554 -0
- data/lib/feelin/js/node_modules/luxon/src/impl/regexParser.js +335 -0
- data/lib/feelin/js/node_modules/luxon/src/impl/tokenParser.js +505 -0
- data/lib/feelin/js/node_modules/luxon/src/impl/util.js +316 -0
- data/lib/feelin/js/node_modules/luxon/src/impl/zoneUtil.js +34 -0
- data/lib/feelin/js/node_modules/luxon/src/info.js +205 -0
- data/lib/feelin/js/node_modules/luxon/src/interval.js +665 -0
- data/lib/feelin/js/node_modules/luxon/src/luxon.js +26 -0
- data/lib/feelin/js/node_modules/luxon/src/package.json +4 -0
- data/lib/feelin/js/node_modules/luxon/src/settings.js +180 -0
- data/lib/feelin/js/node_modules/luxon/src/zone.js +97 -0
- data/lib/feelin/js/node_modules/luxon/src/zones/IANAZone.js +231 -0
- data/lib/feelin/js/node_modules/luxon/src/zones/fixedOffsetZone.js +150 -0
- data/lib/feelin/js/node_modules/luxon/src/zones/invalidZone.js +53 -0
- data/lib/feelin/js/node_modules/luxon/src/zones/systemZone.js +61 -0
- data/lib/feelin/js/node_modules/min-dash/LICENSE +21 -0
- data/lib/feelin/js/node_modules/min-dash/README.md +38 -0
- data/lib/feelin/js/node_modules/min-dash/dist/array.d.ts +12 -0
- data/lib/feelin/js/node_modules/min-dash/dist/collection.d.ts +174 -0
- data/lib/feelin/js/node_modules/min-dash/dist/fn.d.ts +37 -0
- data/lib/feelin/js/node_modules/min-dash/dist/index.cjs +910 -0
- data/lib/feelin/js/node_modules/min-dash/dist/index.d.ts +5 -0
- data/lib/feelin/js/node_modules/min-dash/dist/index.esm.js +872 -0
- data/lib/feelin/js/node_modules/min-dash/dist/lang.d.ts +29 -0
- data/lib/feelin/js/node_modules/min-dash/dist/min-dash.js +916 -0
- data/lib/feelin/js/node_modules/min-dash/dist/min-dash.min.js +1 -0
- data/lib/feelin/js/node_modules/min-dash/dist/object.d.ts +112 -0
- data/lib/feelin/js/node_modules/min-dash/package.json +72 -0
- data/lib/feelin/js/package-lock.json +72 -0
- data/lib/feelin/js/package.json +5 -0
- data/lib/feelin/version.rb +3 -0
- data/lib/feelin.rb +63 -0
- data/spec/feelin/feelin_spec.rb +38 -0
- data/spec/spec_helper.rb +2 -0
- metadata +198 -0
@@ -0,0 +1,2063 @@
|
|
1
|
+
import { DateTime, FixedOffsetZone, Duration, SystemZone, Info } from 'luxon';
|
2
|
+
import { normalizeContextKey, parser, trackVariables } from 'lezer-feel';
|
3
|
+
|
4
|
+
function isContext(e) {
|
5
|
+
return Object.getPrototypeOf(e) === Object.prototype;
|
6
|
+
}
|
7
|
+
function isDateTime(obj) {
|
8
|
+
return DateTime.isDateTime(obj);
|
9
|
+
}
|
10
|
+
function isDuration(obj) {
|
11
|
+
return Duration.isDuration(obj);
|
12
|
+
}
|
13
|
+
function isArray(e) {
|
14
|
+
return Array.isArray(e);
|
15
|
+
}
|
16
|
+
function isBoolean(e) {
|
17
|
+
return typeof e === 'boolean';
|
18
|
+
}
|
19
|
+
function getType(e) {
|
20
|
+
if (e === null || e === undefined) {
|
21
|
+
return 'nil';
|
22
|
+
}
|
23
|
+
if (isBoolean(e)) {
|
24
|
+
return 'boolean';
|
25
|
+
}
|
26
|
+
if (isNumber(e)) {
|
27
|
+
return 'number';
|
28
|
+
}
|
29
|
+
if (isString(e)) {
|
30
|
+
return 'string';
|
31
|
+
}
|
32
|
+
if (isContext(e)) {
|
33
|
+
return 'context';
|
34
|
+
}
|
35
|
+
if (isArray(e)) {
|
36
|
+
return 'list';
|
37
|
+
}
|
38
|
+
if (isDuration(e)) {
|
39
|
+
return 'duration';
|
40
|
+
}
|
41
|
+
if (isDateTime(e)) {
|
42
|
+
if (e.year === 1900 &&
|
43
|
+
e.month === 1 &&
|
44
|
+
e.day === 1) {
|
45
|
+
return 'time';
|
46
|
+
}
|
47
|
+
if (e.hour === 0 &&
|
48
|
+
e.minute === 0 &&
|
49
|
+
e.second === 0 &&
|
50
|
+
e.millisecond === 0 &&
|
51
|
+
e.zone === FixedOffsetZone.utcInstance) {
|
52
|
+
return 'date';
|
53
|
+
}
|
54
|
+
return 'date time';
|
55
|
+
}
|
56
|
+
if (e instanceof Range) {
|
57
|
+
return 'range';
|
58
|
+
}
|
59
|
+
if (e instanceof FunctionWrapper) {
|
60
|
+
return 'function';
|
61
|
+
}
|
62
|
+
return 'literal';
|
63
|
+
}
|
64
|
+
function isType(el, type) {
|
65
|
+
return getType(el) === type;
|
66
|
+
}
|
67
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
68
|
+
function typeCast(obj, type) {
|
69
|
+
if (isDateTime(obj)) {
|
70
|
+
if (type === 'time') {
|
71
|
+
return obj.set({
|
72
|
+
year: 1900,
|
73
|
+
month: 1,
|
74
|
+
day: 1
|
75
|
+
});
|
76
|
+
}
|
77
|
+
if (type === 'date') {
|
78
|
+
return obj.setZone('utc', { keepLocalTime: true }).startOf('day');
|
79
|
+
}
|
80
|
+
if (type === 'date time') {
|
81
|
+
return obj;
|
82
|
+
}
|
83
|
+
}
|
84
|
+
return null;
|
85
|
+
}
|
86
|
+
class Range {
|
87
|
+
constructor(props) {
|
88
|
+
Object.assign(this, props);
|
89
|
+
}
|
90
|
+
}
|
91
|
+
function isNumber(obj) {
|
92
|
+
return typeof obj === 'number';
|
93
|
+
}
|
94
|
+
function isString(obj) {
|
95
|
+
return typeof obj === 'string';
|
96
|
+
}
|
97
|
+
function equals(a, b, strict = false) {
|
98
|
+
if (a === null && b !== null ||
|
99
|
+
a !== null && b === null) {
|
100
|
+
return false;
|
101
|
+
}
|
102
|
+
if (isArray(a) && a.length < 2) {
|
103
|
+
a = a[0];
|
104
|
+
}
|
105
|
+
if (isArray(b) && b.length < 2) {
|
106
|
+
b = b[0];
|
107
|
+
}
|
108
|
+
const aType = getType(a);
|
109
|
+
const bType = getType(b);
|
110
|
+
const temporalTypes = ['date time', 'time', 'date'];
|
111
|
+
if (temporalTypes.includes(aType)) {
|
112
|
+
if (!temporalTypes.includes(bType)) {
|
113
|
+
return null;
|
114
|
+
}
|
115
|
+
if (aType === 'time' && bType !== 'time') {
|
116
|
+
return null;
|
117
|
+
}
|
118
|
+
if (bType === 'time' && aType !== 'time') {
|
119
|
+
return null;
|
120
|
+
}
|
121
|
+
if (strict || a.zone === SystemZone.instance || b.zone === SystemZone.instance) {
|
122
|
+
return a.equals(b);
|
123
|
+
}
|
124
|
+
else {
|
125
|
+
return a.toUTC().valueOf() === b.toUTC().valueOf();
|
126
|
+
}
|
127
|
+
}
|
128
|
+
if (aType !== bType) {
|
129
|
+
return null;
|
130
|
+
}
|
131
|
+
if (aType === 'nil') {
|
132
|
+
return true;
|
133
|
+
}
|
134
|
+
if (aType === 'list') {
|
135
|
+
if (a.length !== b.length) {
|
136
|
+
return false;
|
137
|
+
}
|
138
|
+
return a.every((element, idx) => equals(element, b[idx]));
|
139
|
+
}
|
140
|
+
if (aType === 'duration') {
|
141
|
+
// years and months duration -> months
|
142
|
+
if (Math.abs(a.as('days')) > 180) {
|
143
|
+
return Math.trunc(a.minus(b).as('months')) === 0;
|
144
|
+
}
|
145
|
+
// days and time duration -> seconds
|
146
|
+
else {
|
147
|
+
return Math.trunc(a.minus(b).as('seconds')) === 0;
|
148
|
+
}
|
149
|
+
}
|
150
|
+
if (aType === 'context') {
|
151
|
+
const aEntries = Object.entries(a);
|
152
|
+
const bEntries = Object.entries(b);
|
153
|
+
if (aEntries.length !== bEntries.length) {
|
154
|
+
return false;
|
155
|
+
}
|
156
|
+
return aEntries.every(([key, value]) => key in b && equals(value, b[key]));
|
157
|
+
}
|
158
|
+
if (aType === 'range') {
|
159
|
+
return [
|
160
|
+
[a.start, b.start],
|
161
|
+
[a.end, b.end],
|
162
|
+
[a['start included'], b['start included']],
|
163
|
+
[a['end included'], b['end included']]
|
164
|
+
].every(([a, b]) => a === b);
|
165
|
+
}
|
166
|
+
if (a == b) {
|
167
|
+
return true;
|
168
|
+
}
|
169
|
+
return aType === bType ? false : null;
|
170
|
+
}
|
171
|
+
class FunctionWrapper {
|
172
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
173
|
+
constructor(fn, parameterNames) {
|
174
|
+
this.fn = fn;
|
175
|
+
this.parameterNames = parameterNames;
|
176
|
+
}
|
177
|
+
invoke(contextOrArgs) {
|
178
|
+
let params;
|
179
|
+
if (isArray(contextOrArgs)) {
|
180
|
+
params = contextOrArgs;
|
181
|
+
// reject
|
182
|
+
if (params.length > this.parameterNames.length) {
|
183
|
+
const lastParam = this.parameterNames[this.parameterNames.length - 1];
|
184
|
+
// strictly check for parameter count provided
|
185
|
+
// for non var-args functions
|
186
|
+
if (!lastParam || !lastParam.startsWith('...')) {
|
187
|
+
return null;
|
188
|
+
}
|
189
|
+
}
|
190
|
+
}
|
191
|
+
else {
|
192
|
+
// strictly check for required parameter names,
|
193
|
+
// and fail on wrong parameter name
|
194
|
+
if (Object.keys(contextOrArgs).some(key => !this.parameterNames.includes(key) && !this.parameterNames.includes(`...${key}`))) {
|
195
|
+
return null;
|
196
|
+
}
|
197
|
+
params = this.parameterNames.reduce((params, name) => {
|
198
|
+
if (name.startsWith('...')) {
|
199
|
+
name = name.slice(3);
|
200
|
+
const value = contextOrArgs[name];
|
201
|
+
if (!value) {
|
202
|
+
return params;
|
203
|
+
}
|
204
|
+
else {
|
205
|
+
// ensure that single arg provided for var args named
|
206
|
+
// parameter is wrapped in a list
|
207
|
+
return [...params, ...(isArray(value) ? value : [value])];
|
208
|
+
}
|
209
|
+
}
|
210
|
+
return [...params, contextOrArgs[name]];
|
211
|
+
}, []);
|
212
|
+
}
|
213
|
+
return this.fn.call(null, ...params);
|
214
|
+
}
|
215
|
+
}
|
216
|
+
|
217
|
+
function parseParameterNames(fn) {
|
218
|
+
if (Array.isArray(fn.$args)) {
|
219
|
+
return fn.$args;
|
220
|
+
}
|
221
|
+
const code = fn.toString();
|
222
|
+
const match = /^(?:[^(]*\s*)?\(([^)]+)?\)/.exec(code);
|
223
|
+
if (!match) {
|
224
|
+
throw new Error('failed to parse params: ' + code);
|
225
|
+
}
|
226
|
+
const [_, params] = match;
|
227
|
+
if (!params) {
|
228
|
+
return [];
|
229
|
+
}
|
230
|
+
return params.split(',').map(p => p.trim());
|
231
|
+
}
|
232
|
+
function notImplemented(thing) {
|
233
|
+
return new Error(`not implemented: ${thing}`);
|
234
|
+
}
|
235
|
+
function isNotImplemented(err) {
|
236
|
+
return /^not implemented/.test(err.message);
|
237
|
+
}
|
238
|
+
/**
|
239
|
+
* Returns a name from context or undefined if it does not exist.
|
240
|
+
*
|
241
|
+
* @param {string} name
|
242
|
+
* @param {Record<string, any>} context
|
243
|
+
*
|
244
|
+
* @return {any|undefined}
|
245
|
+
*/
|
246
|
+
function getFromContext(name, context) {
|
247
|
+
if (['nil', 'boolean', 'number', 'string'].includes(getType(context))) {
|
248
|
+
return undefined;
|
249
|
+
}
|
250
|
+
if (name in context) {
|
251
|
+
return context[name];
|
252
|
+
}
|
253
|
+
const normalizedName = normalizeContextKey(name);
|
254
|
+
if (normalizedName in context) {
|
255
|
+
return context[normalizedName];
|
256
|
+
}
|
257
|
+
const entry = Object.entries(context).find(([key]) => normalizedName === normalizeContextKey(key));
|
258
|
+
if (entry) {
|
259
|
+
return entry[1];
|
260
|
+
}
|
261
|
+
return undefined;
|
262
|
+
}
|
263
|
+
|
264
|
+
function duration(opts) {
|
265
|
+
if (typeof opts === 'number') {
|
266
|
+
return Duration.fromMillis(opts);
|
267
|
+
}
|
268
|
+
return Duration.fromISO(opts);
|
269
|
+
}
|
270
|
+
function date(str = null, time = null, zone = null) {
|
271
|
+
if (time) {
|
272
|
+
if (str) {
|
273
|
+
throw new Error('<str> and <time> provided');
|
274
|
+
}
|
275
|
+
return date(`1900-01-01T${time}`, null);
|
276
|
+
}
|
277
|
+
if (typeof str === 'string') {
|
278
|
+
if (str.startsWith('-')) {
|
279
|
+
throw notImplemented('negative date');
|
280
|
+
}
|
281
|
+
if (!str.includes('T')) {
|
282
|
+
// raw dates are in UTC time zone
|
283
|
+
return date(str + 'T00:00:00', null, zone || FixedOffsetZone.utcInstance);
|
284
|
+
}
|
285
|
+
if (str.includes('@')) {
|
286
|
+
if (zone) {
|
287
|
+
throw new Error('<zone> already provided');
|
288
|
+
}
|
289
|
+
const [datePart, zonePart] = str.split('@');
|
290
|
+
return date(datePart, null, Info.normalizeZone(zonePart));
|
291
|
+
}
|
292
|
+
return DateTime.fromISO(str.toUpperCase(), {
|
293
|
+
setZone: true,
|
294
|
+
zone
|
295
|
+
});
|
296
|
+
}
|
297
|
+
return DateTime.now();
|
298
|
+
}
|
299
|
+
|
300
|
+
// 10.3.4 Built-in functions
|
301
|
+
const builtins = {
|
302
|
+
// 10.3.4.1 Conversion functions
|
303
|
+
'number': fn(function (from, groupingSeparator, decimalSeparator) {
|
304
|
+
// must always provide three arguments
|
305
|
+
if (arguments.length !== 3) {
|
306
|
+
return null;
|
307
|
+
}
|
308
|
+
if (groupingSeparator) {
|
309
|
+
from = from.split(groupingSeparator).join('');
|
310
|
+
}
|
311
|
+
if (decimalSeparator && decimalSeparator !== '.') {
|
312
|
+
from = from.split('.').join('#').split(decimalSeparator).join('.');
|
313
|
+
}
|
314
|
+
const number = +from;
|
315
|
+
if (isNaN(number)) {
|
316
|
+
return null;
|
317
|
+
}
|
318
|
+
return number;
|
319
|
+
}, ['string', 'string?', 'string?'], ['from', 'grouping separator', 'decimal separator']),
|
320
|
+
'string': fn(function (from) {
|
321
|
+
if (from === null) {
|
322
|
+
return null;
|
323
|
+
}
|
324
|
+
return toString(from);
|
325
|
+
}, ['any']),
|
326
|
+
// date(from) => date string
|
327
|
+
// date(from) => date and time
|
328
|
+
// date(year, month, day)
|
329
|
+
'date': fn(function (year, month, day, from) {
|
330
|
+
if (!from && !isNumber(year)) {
|
331
|
+
from = year;
|
332
|
+
year = null;
|
333
|
+
}
|
334
|
+
let d;
|
335
|
+
if (isString(from)) {
|
336
|
+
d = date(from);
|
337
|
+
}
|
338
|
+
if (isDateTime(from)) {
|
339
|
+
d = from;
|
340
|
+
}
|
341
|
+
if (year) {
|
342
|
+
if (!isNumber(month) || !isNumber(day)) {
|
343
|
+
return null;
|
344
|
+
}
|
345
|
+
d = date().setZone('utc').set({
|
346
|
+
year,
|
347
|
+
month,
|
348
|
+
day
|
349
|
+
});
|
350
|
+
}
|
351
|
+
return d && ifValid(d.setZone('utc').startOf('day')) || null;
|
352
|
+
}, ['any?', 'number?', 'number?', 'any?']),
|
353
|
+
// date and time(from) => date time string
|
354
|
+
// date and time(date, time)
|
355
|
+
'date and time': fn(function (d, time, from) {
|
356
|
+
let dt;
|
357
|
+
if (isDateTime(d) && isDateTime(time)) {
|
358
|
+
const dLocal = d.toLocal();
|
359
|
+
dt = time.set({
|
360
|
+
year: dLocal.year,
|
361
|
+
month: dLocal.month,
|
362
|
+
day: dLocal.day
|
363
|
+
});
|
364
|
+
}
|
365
|
+
if (isString(d)) {
|
366
|
+
from = d;
|
367
|
+
d = null;
|
368
|
+
}
|
369
|
+
if (isString(from)) {
|
370
|
+
dt = date(from, null, from.includes('@') ? null : SystemZone.instance);
|
371
|
+
}
|
372
|
+
return dt && ifValid(dt) || null;
|
373
|
+
}, ['any?', 'time?', 'string?'], ['date', 'time', 'from']),
|
374
|
+
// time(from) => time string
|
375
|
+
// time(from) => time, date and time
|
376
|
+
// time(hour, minute, second, offset?) => ...
|
377
|
+
'time': fn(function (hour, minute, second, offset, from) {
|
378
|
+
let t;
|
379
|
+
if (offset) {
|
380
|
+
throw notImplemented('time(..., offset)');
|
381
|
+
}
|
382
|
+
if (isString(hour) || isDateTime(hour)) {
|
383
|
+
from = hour;
|
384
|
+
hour = null;
|
385
|
+
}
|
386
|
+
if (isString(from) && from) {
|
387
|
+
t = date(null, from);
|
388
|
+
}
|
389
|
+
if (isDateTime(from)) {
|
390
|
+
t = from.set({
|
391
|
+
year: 1900,
|
392
|
+
month: 1,
|
393
|
+
day: 1
|
394
|
+
});
|
395
|
+
}
|
396
|
+
if (isNumber(hour)) {
|
397
|
+
if (!isNumber(minute) || !isNumber(second)) {
|
398
|
+
return null;
|
399
|
+
}
|
400
|
+
// TODO: support offset = days and time duration
|
401
|
+
t = date().set({
|
402
|
+
hour,
|
403
|
+
minute,
|
404
|
+
second
|
405
|
+
}).set({
|
406
|
+
year: 1900,
|
407
|
+
month: 1,
|
408
|
+
day: 1,
|
409
|
+
millisecond: 0
|
410
|
+
});
|
411
|
+
}
|
412
|
+
return t && ifValid(t) || null;
|
413
|
+
}, ['any?', 'number?', 'number?', 'any?', 'any?']),
|
414
|
+
'duration': fn(function (from) {
|
415
|
+
return ifValid(duration(from));
|
416
|
+
}, ['string']),
|
417
|
+
'years and months duration': fn(function (from, to) {
|
418
|
+
return ifValid(to.diff(from, ['years', 'months']));
|
419
|
+
}, ['date', 'date']),
|
420
|
+
'@': fn(function (string) {
|
421
|
+
let t;
|
422
|
+
if (/^-?P/.test(string)) {
|
423
|
+
t = duration(string);
|
424
|
+
}
|
425
|
+
else if (/^[\d]{1,2}:[\d]{1,2}:[\d]{1,2}/.test(string)) {
|
426
|
+
t = date(null, string);
|
427
|
+
}
|
428
|
+
else {
|
429
|
+
t = date(string);
|
430
|
+
}
|
431
|
+
return t && ifValid(t) || null;
|
432
|
+
}, ['string']),
|
433
|
+
'now': fn(function () {
|
434
|
+
return date();
|
435
|
+
}, []),
|
436
|
+
'today': fn(function () {
|
437
|
+
return date().startOf('day');
|
438
|
+
}, []),
|
439
|
+
// 10.3.4.2 Boolean function
|
440
|
+
'not': fn(function (bool) {
|
441
|
+
return isType(bool, 'boolean') ? !bool : null;
|
442
|
+
}, ['any']),
|
443
|
+
// 10.3.4.3 String functions
|
444
|
+
'substring': fn(function (string, start, length) {
|
445
|
+
const _start = (start < 0 ? string.length + start : start - 1);
|
446
|
+
const arr = Array.from(string);
|
447
|
+
return (typeof length !== 'undefined'
|
448
|
+
? arr.slice(_start, _start + length)
|
449
|
+
: arr.slice(_start)).join('');
|
450
|
+
}, ['string', 'number', 'number?'], ['string', 'start position', 'length']),
|
451
|
+
'string length': fn(function (string) {
|
452
|
+
return countSymbols(string);
|
453
|
+
}, ['string']),
|
454
|
+
'upper case': fn(function (string) {
|
455
|
+
return string.toUpperCase();
|
456
|
+
}, ['string']),
|
457
|
+
'lower case': fn(function (string) {
|
458
|
+
return string.toLowerCase();
|
459
|
+
}, ['string']),
|
460
|
+
'substring before': fn(function (string, match) {
|
461
|
+
const index = string.indexOf(match);
|
462
|
+
if (index === -1) {
|
463
|
+
return '';
|
464
|
+
}
|
465
|
+
return string.substring(0, index);
|
466
|
+
}, ['string', 'string']),
|
467
|
+
'substring after': fn(function (string, match) {
|
468
|
+
const index = string.indexOf(match);
|
469
|
+
if (index === -1) {
|
470
|
+
return '';
|
471
|
+
}
|
472
|
+
return string.substring(index + match.length);
|
473
|
+
}, ['string', 'string']),
|
474
|
+
'replace': fn(function (input, pattern, replacement, flags) {
|
475
|
+
const regexp = createRegexp(pattern, flags || '', 'g');
|
476
|
+
return regexp && input.replace(regexp, replacement.replace(/\$0/g, '$$&'));
|
477
|
+
}, ['string', 'string', 'string', 'string?']),
|
478
|
+
'contains': fn(function (string, match) {
|
479
|
+
return string.includes(match);
|
480
|
+
}, ['string', 'string']),
|
481
|
+
'matches': fn(function (input, pattern, flags) {
|
482
|
+
const regexp = createRegexp(pattern, flags || '', '');
|
483
|
+
return regexp && regexp.test(input);
|
484
|
+
}, ['string', 'string', 'string?']),
|
485
|
+
'starts with': fn(function (string, match) {
|
486
|
+
return string.startsWith(match);
|
487
|
+
}, ['string', 'string']),
|
488
|
+
'ends with': fn(function (string, match) {
|
489
|
+
return string.endsWith(match);
|
490
|
+
}, ['string', 'string']),
|
491
|
+
'split': fn(function (string, delimiter) {
|
492
|
+
const regexp = createRegexp(delimiter, '', '');
|
493
|
+
return regexp && string.split(regexp);
|
494
|
+
}, ['string', 'string']),
|
495
|
+
'string join': fn(function (list, delimiter) {
|
496
|
+
if (list.some(e => !isString(e) && e !== null)) {
|
497
|
+
return null;
|
498
|
+
}
|
499
|
+
return list.filter(l => l !== null).join(delimiter || '');
|
500
|
+
}, ['list', 'string?']),
|
501
|
+
// 10.3.4.4 List functions
|
502
|
+
'list contains': fn(function (list, element) {
|
503
|
+
return list.some(el => matches(el, element));
|
504
|
+
}, ['list', 'any?']),
|
505
|
+
// list replace(list, position, newItem)
|
506
|
+
// list replace(list, match, newItem)
|
507
|
+
'list replace': fn(function (list, position, newItem, match) {
|
508
|
+
const matcher = position || match;
|
509
|
+
if (!['number', 'function'].includes(getType(matcher))) {
|
510
|
+
return null;
|
511
|
+
}
|
512
|
+
return listReplace(list, position || match, newItem);
|
513
|
+
}, ['list', 'any?', 'any', 'function?']),
|
514
|
+
'count': fn(function (list) {
|
515
|
+
return list.length;
|
516
|
+
}, ['list']),
|
517
|
+
'min': listFn(function (...list) {
|
518
|
+
return list.reduce((min, el) => min === null ? el : Math.min(min, el), null);
|
519
|
+
}, 'number'),
|
520
|
+
'max': listFn(function (...list) {
|
521
|
+
return list.reduce((max, el) => max === null ? el : Math.max(max, el), null);
|
522
|
+
}, 'number'),
|
523
|
+
'sum': listFn(function (...list) {
|
524
|
+
return sum(list);
|
525
|
+
}, 'number'),
|
526
|
+
'mean': listFn(function (...list) {
|
527
|
+
const s = sum(list);
|
528
|
+
return s === null ? s : s / list.length;
|
529
|
+
}, 'number'),
|
530
|
+
'all': listFn(function (...list) {
|
531
|
+
let nonBool = false;
|
532
|
+
for (const o of list) {
|
533
|
+
if (o === false) {
|
534
|
+
return false;
|
535
|
+
}
|
536
|
+
if (typeof o !== 'boolean') {
|
537
|
+
nonBool = true;
|
538
|
+
}
|
539
|
+
}
|
540
|
+
return nonBool ? null : true;
|
541
|
+
}, 'any?'),
|
542
|
+
'any': listFn(function (...list) {
|
543
|
+
let nonBool = false;
|
544
|
+
for (const o of list) {
|
545
|
+
if (o === true) {
|
546
|
+
return true;
|
547
|
+
}
|
548
|
+
if (typeof o !== 'boolean') {
|
549
|
+
nonBool = true;
|
550
|
+
}
|
551
|
+
}
|
552
|
+
return nonBool ? null : false;
|
553
|
+
}, 'any?'),
|
554
|
+
'sublist': fn(function (list, start, length) {
|
555
|
+
const _start = (start < 0 ? list.length + start : start - 1);
|
556
|
+
return (typeof length !== 'undefined'
|
557
|
+
? list.slice(_start, _start + length)
|
558
|
+
: list.slice(_start));
|
559
|
+
}, ['list', 'number', 'number?']),
|
560
|
+
'append': fn(function (list, ...items) {
|
561
|
+
return list.concat(items);
|
562
|
+
}, ['list', 'any?']),
|
563
|
+
'concatenate': fn(function (...args) {
|
564
|
+
return args.reduce((result, arg) => {
|
565
|
+
return result.concat(arg);
|
566
|
+
}, []);
|
567
|
+
}, ['any']),
|
568
|
+
'insert before': fn(function (list, position, newItem) {
|
569
|
+
return list.slice(0, position - 1).concat([newItem], list.slice(position - 1));
|
570
|
+
}, ['list', 'number', 'any?']),
|
571
|
+
'remove': fn(function (list, position) {
|
572
|
+
return list.slice(0, position - 1).concat(list.slice(position));
|
573
|
+
}, ['list', 'number']),
|
574
|
+
'reverse': fn(function (list) {
|
575
|
+
return list.slice().reverse();
|
576
|
+
}, ['list']),
|
577
|
+
'index of': fn(function (list, match) {
|
578
|
+
return list.reduce(function (result, element, index) {
|
579
|
+
if (matches(element, match)) {
|
580
|
+
result.push(index + 1);
|
581
|
+
}
|
582
|
+
return result;
|
583
|
+
}, []);
|
584
|
+
}, ['list', 'any']),
|
585
|
+
'union': listFn(function (...lists) {
|
586
|
+
return lists.reduce((result, list) => {
|
587
|
+
return list.reduce((result, e) => {
|
588
|
+
if (!result.some(r => equals(e, r))) {
|
589
|
+
result.push(e);
|
590
|
+
}
|
591
|
+
return result;
|
592
|
+
}, result);
|
593
|
+
}, []);
|
594
|
+
}, 'list'),
|
595
|
+
'distinct values': fn(function (list) {
|
596
|
+
return list.reduce((result, e) => {
|
597
|
+
if (!result.some(r => equals(e, r))) {
|
598
|
+
result.push(e);
|
599
|
+
}
|
600
|
+
return result;
|
601
|
+
}, []);
|
602
|
+
}, ['list']),
|
603
|
+
'flatten': fn(function (list) {
|
604
|
+
return flatten(list);
|
605
|
+
}, ['list']),
|
606
|
+
'product': listFn(function (...list) {
|
607
|
+
if (list.length === 0) {
|
608
|
+
return null;
|
609
|
+
}
|
610
|
+
return list.reduce((result, n) => {
|
611
|
+
return result * n;
|
612
|
+
}, 1);
|
613
|
+
}, 'number'),
|
614
|
+
'median': listFn(function (...list) {
|
615
|
+
if (list.length === 0) {
|
616
|
+
return null;
|
617
|
+
}
|
618
|
+
return median(list);
|
619
|
+
}, 'number'),
|
620
|
+
'stddev': listFn(function (...list) {
|
621
|
+
if (list.length < 2) {
|
622
|
+
return null;
|
623
|
+
}
|
624
|
+
return stddev(list);
|
625
|
+
}, 'number'),
|
626
|
+
'mode': listFn(function (...list) {
|
627
|
+
return mode(list);
|
628
|
+
}, 'number'),
|
629
|
+
// 10.3.4.5 Numeric functions
|
630
|
+
'decimal': fn(function (n, scale) {
|
631
|
+
if (!scale) {
|
632
|
+
return bankersRound(n);
|
633
|
+
}
|
634
|
+
const offset = Math.pow(10, scale);
|
635
|
+
return bankersRound(n * offset) / offset;
|
636
|
+
}, ['number', 'number']),
|
637
|
+
'floor': fn(function (n, scale = 0) {
|
638
|
+
if (scale === null) {
|
639
|
+
return null;
|
640
|
+
}
|
641
|
+
const adjust = Math.pow(10, scale);
|
642
|
+
return Math.floor(n * adjust) / adjust;
|
643
|
+
}, ['number', 'number?']),
|
644
|
+
'ceiling': fn(function (n, scale = 0) {
|
645
|
+
if (scale === null) {
|
646
|
+
return null;
|
647
|
+
}
|
648
|
+
const adjust = Math.pow(10, scale);
|
649
|
+
return Math.ceil(n * adjust) / adjust;
|
650
|
+
}, ['number', 'number?']),
|
651
|
+
'abs': fn(function (n) {
|
652
|
+
if (typeof n !== 'number') {
|
653
|
+
return null;
|
654
|
+
}
|
655
|
+
return Math.abs(n);
|
656
|
+
}, ['number']),
|
657
|
+
// eslint-disable-next-line
|
658
|
+
'round up': fn(function (n, scale) {
|
659
|
+
throw notImplemented('round up');
|
660
|
+
}, ['number', 'number']),
|
661
|
+
// eslint-disable-next-line
|
662
|
+
'round down': fn(function (n, scale) {
|
663
|
+
throw notImplemented('round down');
|
664
|
+
}, ['number', 'number']),
|
665
|
+
// eslint-disable-next-line
|
666
|
+
'round half up': fn(function (n, scale) {
|
667
|
+
throw notImplemented('round half up');
|
668
|
+
}, ['number', 'number']),
|
669
|
+
// eslint-disable-next-line
|
670
|
+
'round half down': fn(function (n, scale) {
|
671
|
+
throw notImplemented('round half down');
|
672
|
+
}, ['number', 'number']),
|
673
|
+
'modulo': fn(function (dividend, divisor) {
|
674
|
+
if (!divisor) {
|
675
|
+
return null;
|
676
|
+
}
|
677
|
+
const adjust = 1000000000;
|
678
|
+
// cf. https://dustinpfister.github.io/2017/09/02/js-whats-wrong-with-modulo/
|
679
|
+
//
|
680
|
+
// need to round here as using this custom modulo
|
681
|
+
// variant is prone to rounding errors
|
682
|
+
return Math.round((dividend % divisor + divisor) % divisor * adjust) / adjust;
|
683
|
+
}, ['number', 'number']),
|
684
|
+
'sqrt': fn(function (number) {
|
685
|
+
if (number < 0) {
|
686
|
+
return null;
|
687
|
+
}
|
688
|
+
return Math.sqrt(number);
|
689
|
+
}, ['number']),
|
690
|
+
'log': fn(function (number) {
|
691
|
+
if (number <= 0) {
|
692
|
+
return null;
|
693
|
+
}
|
694
|
+
return Math.log(number);
|
695
|
+
}, ['number']),
|
696
|
+
'exp': fn(function (number) {
|
697
|
+
return Math.exp(number);
|
698
|
+
}, ['number']),
|
699
|
+
'odd': fn(function (number) {
|
700
|
+
return Math.abs(number) % 2 === 1;
|
701
|
+
}, ['number']),
|
702
|
+
'even': fn(function (number) {
|
703
|
+
return Math.abs(number) % 2 === 0;
|
704
|
+
}, ['number']),
|
705
|
+
// 10.3.4.6 Date and time functions
|
706
|
+
'is': fn(function (value1, value2) {
|
707
|
+
if (typeof value1 === 'undefined' || typeof value2 === 'undefined') {
|
708
|
+
return false;
|
709
|
+
}
|
710
|
+
return equals(value1, value2, true);
|
711
|
+
}, ['any?', 'any?']),
|
712
|
+
// 10.3.4.7 Range Functions
|
713
|
+
'before': fn(function (a, b) {
|
714
|
+
return before(a, b);
|
715
|
+
}, ['any', 'any']),
|
716
|
+
'after': fn(function (a, b) {
|
717
|
+
return before(b, a);
|
718
|
+
}, ['any', 'any']),
|
719
|
+
'meets': fn(function (a, b) {
|
720
|
+
return meetsRange(a, b);
|
721
|
+
}, ['range', 'range']),
|
722
|
+
'met by': fn(function (a, b) {
|
723
|
+
return meetsRange(b, a);
|
724
|
+
}, ['range', 'range']),
|
725
|
+
'overlaps': fn(function (range1, range2) {
|
726
|
+
return !before(range1, range2) && !before(range2, range1);
|
727
|
+
}, ['range', 'range']),
|
728
|
+
'overlaps before': fn(function () {
|
729
|
+
throw notImplemented('overlaps before');
|
730
|
+
}, ['any?']),
|
731
|
+
'overlaps after': fn(function () {
|
732
|
+
throw notImplemented('overlaps after');
|
733
|
+
}, ['any?']),
|
734
|
+
'finishes': fn(function () {
|
735
|
+
throw notImplemented('finishes');
|
736
|
+
}, ['any?']),
|
737
|
+
'finished by': fn(function () {
|
738
|
+
throw notImplemented('finished by');
|
739
|
+
}, ['any?']),
|
740
|
+
'includes': fn(function () {
|
741
|
+
throw notImplemented('includes');
|
742
|
+
}, ['any?']),
|
743
|
+
'during': fn(function () {
|
744
|
+
throw notImplemented('during');
|
745
|
+
}, ['any?']),
|
746
|
+
'starts': fn(function () {
|
747
|
+
throw notImplemented('starts');
|
748
|
+
}, ['any?']),
|
749
|
+
'started by': fn(function () {
|
750
|
+
throw notImplemented('started by');
|
751
|
+
}, ['any?']),
|
752
|
+
'coincides': fn(function () {
|
753
|
+
throw notImplemented('coincides');
|
754
|
+
}, ['any?']),
|
755
|
+
// 10.3.4.8 Temporal built-in functions
|
756
|
+
'day of year': fn(function (date) {
|
757
|
+
return date.ordinal;
|
758
|
+
}, ['date time']),
|
759
|
+
'day of week': fn(function (date) {
|
760
|
+
return date.weekdayLong;
|
761
|
+
}, ['date time']),
|
762
|
+
'month of year': fn(function (date) {
|
763
|
+
return date.monthLong;
|
764
|
+
}, ['date time']),
|
765
|
+
'week of year': fn(function (date) {
|
766
|
+
return date.weekNumber;
|
767
|
+
}, ['date time']),
|
768
|
+
// 10.3.4.9 Sort
|
769
|
+
'sort': fn(function (list, precedes) {
|
770
|
+
return Array.from(list).sort((a, b) => precedes.invoke([a, b]) ? -1 : 1);
|
771
|
+
}, ['list', 'function']),
|
772
|
+
// 10.3.4.10 Context function
|
773
|
+
'get value': fn(function (m, key) {
|
774
|
+
const value = getFromContext(key, m);
|
775
|
+
return value != undefined ? value : null;
|
776
|
+
}, ['context', 'string']),
|
777
|
+
'get entries': fn(function (m) {
|
778
|
+
if (arguments.length !== 1) {
|
779
|
+
return null;
|
780
|
+
}
|
781
|
+
if (Array.isArray(m)) {
|
782
|
+
return null;
|
783
|
+
}
|
784
|
+
return Object.entries(m).map(([key, value]) => ({ key, value }));
|
785
|
+
}, ['context']),
|
786
|
+
'context': listFn(function (...entries) {
|
787
|
+
const context = entries.reduce((context, entry) => {
|
788
|
+
if (context === FALSE || !['key', 'value'].every(e => e in entry)) {
|
789
|
+
return FALSE;
|
790
|
+
}
|
791
|
+
const key = entry.key;
|
792
|
+
if (key === null) {
|
793
|
+
return FALSE;
|
794
|
+
}
|
795
|
+
if (key in context) {
|
796
|
+
return FALSE;
|
797
|
+
}
|
798
|
+
return Object.assign(Object.assign({}, context), { [entry.key]: entry.value });
|
799
|
+
}, {});
|
800
|
+
if (context === FALSE) {
|
801
|
+
return null;
|
802
|
+
}
|
803
|
+
return context;
|
804
|
+
}, 'context'),
|
805
|
+
'context merge': listFn(function (...contexts) {
|
806
|
+
return Object.assign({}, ...contexts);
|
807
|
+
}, 'context'),
|
808
|
+
'context put': fn(function (context, keys, value, key) {
|
809
|
+
if (typeof keys === 'undefined' && typeof key === 'undefined') {
|
810
|
+
return null;
|
811
|
+
}
|
812
|
+
return contextPut(context, keys || [key], value);
|
813
|
+
}, ['context', 'list?', 'any', 'string?'], ['context', 'keys', 'value', 'key'])
|
814
|
+
};
|
815
|
+
/**
|
816
|
+
* @param {Object} context
|
817
|
+
* @param {string[]} keys
|
818
|
+
* @param {any} value
|
819
|
+
*/
|
820
|
+
function contextPut(context, keys, value) {
|
821
|
+
const [key, ...remainingKeys] = keys;
|
822
|
+
if (getType(key) !== 'string') {
|
823
|
+
return null;
|
824
|
+
}
|
825
|
+
if (getType(context) === 'nil') {
|
826
|
+
return null;
|
827
|
+
}
|
828
|
+
if (remainingKeys.length) {
|
829
|
+
value = contextPut(context[key], remainingKeys, value);
|
830
|
+
if (value === null) {
|
831
|
+
return null;
|
832
|
+
}
|
833
|
+
}
|
834
|
+
return Object.assign(Object.assign({}, context), { [key]: value });
|
835
|
+
}
|
836
|
+
function matches(a, b) {
|
837
|
+
return a === b;
|
838
|
+
}
|
839
|
+
const FALSE = {};
|
840
|
+
function createArgTester(arg) {
|
841
|
+
const optional = arg.endsWith('?');
|
842
|
+
const type = optional ? arg.substring(0, arg.length - 1) : arg;
|
843
|
+
return function (obj) {
|
844
|
+
const arr = Array.isArray(obj);
|
845
|
+
if (type === 'list') {
|
846
|
+
if (arr || optional && typeof obj === 'undefined') {
|
847
|
+
return obj;
|
848
|
+
}
|
849
|
+
else {
|
850
|
+
// implicit conversion obj => [ obj ]
|
851
|
+
return obj === null ? FALSE : [obj];
|
852
|
+
}
|
853
|
+
}
|
854
|
+
if (type !== 'any' && arr && obj.length === 1) {
|
855
|
+
// implicit conversion [ obj ] => obj
|
856
|
+
obj = obj[0];
|
857
|
+
}
|
858
|
+
const objType = getType(obj);
|
859
|
+
if (type === 'any' || type === objType) {
|
860
|
+
return optional ? obj : typeof obj !== 'undefined' ? obj : FALSE;
|
861
|
+
}
|
862
|
+
if (objType === 'nil') {
|
863
|
+
return (optional ? obj : FALSE);
|
864
|
+
}
|
865
|
+
return typeCast(obj, type) || FALSE;
|
866
|
+
};
|
867
|
+
}
|
868
|
+
function createArgsValidator(argDefinitions) {
|
869
|
+
const tests = argDefinitions.map(createArgTester);
|
870
|
+
return function (args) {
|
871
|
+
while (args.length < argDefinitions.length) {
|
872
|
+
args.push(undefined);
|
873
|
+
}
|
874
|
+
return args.reduce((result, arg, index) => {
|
875
|
+
if (result === false) {
|
876
|
+
return result;
|
877
|
+
}
|
878
|
+
const test = tests[index];
|
879
|
+
const conversion = test ? test(arg) : arg;
|
880
|
+
if (conversion === FALSE) {
|
881
|
+
return false;
|
882
|
+
}
|
883
|
+
result.push(conversion);
|
884
|
+
return result;
|
885
|
+
}, []);
|
886
|
+
};
|
887
|
+
}
|
888
|
+
/**
|
889
|
+
* @param {Function} fnDefinition
|
890
|
+
* @param {string} type
|
891
|
+
* @param {string[]} [parameterNames]
|
892
|
+
*
|
893
|
+
* @return {Function}
|
894
|
+
*/
|
895
|
+
function listFn(fnDefinition, type, parameterNames = null) {
|
896
|
+
const tester = createArgTester(type);
|
897
|
+
const wrappedFn = function (...args) {
|
898
|
+
if (args.length === 0) {
|
899
|
+
return null;
|
900
|
+
}
|
901
|
+
// unwrap first arg
|
902
|
+
if (Array.isArray(args[0]) && args.length === 1) {
|
903
|
+
args = args[0];
|
904
|
+
}
|
905
|
+
if (!args.every(arg => tester(arg) !== FALSE)) {
|
906
|
+
return null;
|
907
|
+
}
|
908
|
+
return fnDefinition(...args);
|
909
|
+
};
|
910
|
+
wrappedFn.$args = parameterNames || parseParameterNames(fnDefinition);
|
911
|
+
return wrappedFn;
|
912
|
+
}
|
913
|
+
/**
|
914
|
+
* @param {Function} fnDefinition
|
915
|
+
* @param {string[]} argDefinitions
|
916
|
+
* @param {string[]} [parameterNames]
|
917
|
+
*
|
918
|
+
* @return {Function}
|
919
|
+
*/
|
920
|
+
function fn(fnDefinition, argDefinitions, parameterNames = null) {
|
921
|
+
const checkArgs = createArgsValidator(argDefinitions);
|
922
|
+
parameterNames = parameterNames || parseParameterNames(fnDefinition);
|
923
|
+
const wrappedFn = function (...args) {
|
924
|
+
const convertedArgs = checkArgs(args);
|
925
|
+
if (!convertedArgs) {
|
926
|
+
return null;
|
927
|
+
}
|
928
|
+
return fnDefinition(...convertedArgs);
|
929
|
+
};
|
930
|
+
wrappedFn.$args = parameterNames;
|
931
|
+
return wrappedFn;
|
932
|
+
}
|
933
|
+
/**
|
934
|
+
* @param {Range} a
|
935
|
+
* @param {Range} b
|
936
|
+
*/
|
937
|
+
function meetsRange(a, b) {
|
938
|
+
return [
|
939
|
+
(a.end === b.start),
|
940
|
+
(a['end included'] === true),
|
941
|
+
(b['start included'] === true)
|
942
|
+
].every(v => v);
|
943
|
+
}
|
944
|
+
/**
|
945
|
+
* @param {Range|number} a
|
946
|
+
* @param {Range|number} b
|
947
|
+
*/
|
948
|
+
function before(a, b) {
|
949
|
+
if (a instanceof Range && b instanceof Range) {
|
950
|
+
return (a.end < b.start || (!a['end included'] || !b['start included']) && a.end == b.start);
|
951
|
+
}
|
952
|
+
if (a instanceof Range) {
|
953
|
+
return (a.end < b || (!a['end included'] && a.end === b));
|
954
|
+
}
|
955
|
+
if (b instanceof Range) {
|
956
|
+
return (b.start > a || (!b['start included'] && b.start === a));
|
957
|
+
}
|
958
|
+
return a < b;
|
959
|
+
}
|
960
|
+
function sum(list) {
|
961
|
+
return list.reduce((sum, el) => sum === null ? el : sum + el, null);
|
962
|
+
}
|
963
|
+
function flatten([x, ...xs]) {
|
964
|
+
return (x !== undefined
|
965
|
+
? [...Array.isArray(x) ? flatten(x) : [x], ...flatten(xs)]
|
966
|
+
: []);
|
967
|
+
}
|
968
|
+
function toKeyString(key) {
|
969
|
+
if (typeof key === 'string' && /\W/.test(key)) {
|
970
|
+
return toString(key, true);
|
971
|
+
}
|
972
|
+
return key;
|
973
|
+
}
|
974
|
+
function toDeepString(obj) {
|
975
|
+
return toString(obj, true);
|
976
|
+
}
|
977
|
+
function escapeStr(str) {
|
978
|
+
return str.replace(/("|\\)/g, '\\$1');
|
979
|
+
}
|
980
|
+
function toString(obj, wrap = false) {
|
981
|
+
var _a, _b, _c, _d;
|
982
|
+
const type = getType(obj);
|
983
|
+
if (type === 'nil') {
|
984
|
+
return 'null';
|
985
|
+
}
|
986
|
+
if (type === 'string') {
|
987
|
+
return wrap ? `"${escapeStr(obj)}"` : obj;
|
988
|
+
}
|
989
|
+
if (type === 'boolean' || type === 'number') {
|
990
|
+
return String(obj);
|
991
|
+
}
|
992
|
+
if (type === 'list') {
|
993
|
+
return '[' + obj.map(toDeepString).join(', ') + ']';
|
994
|
+
}
|
995
|
+
if (type === 'context') {
|
996
|
+
return '{' + Object.entries(obj).map(([key, value]) => {
|
997
|
+
return toKeyString(key) + ': ' + toDeepString(value);
|
998
|
+
}).join(', ') + '}';
|
999
|
+
}
|
1000
|
+
if (type === 'duration') {
|
1001
|
+
return obj.shiftTo('years', 'months', 'days', 'hours', 'minutes', 'seconds').normalize().toISO();
|
1002
|
+
}
|
1003
|
+
if (type === 'date time') {
|
1004
|
+
if (obj.zone === SystemZone.instance) {
|
1005
|
+
return obj.toISO({ suppressMilliseconds: true, includeOffset: false });
|
1006
|
+
}
|
1007
|
+
if ((_a = obj.zone) === null || _a === void 0 ? void 0 : _a.zoneName) {
|
1008
|
+
return obj.toISO({ suppressMilliseconds: true, includeOffset: false }) + '@' + ((_b = obj.zone) === null || _b === void 0 ? void 0 : _b.zoneName);
|
1009
|
+
}
|
1010
|
+
return obj.toISO({ suppressMilliseconds: true });
|
1011
|
+
}
|
1012
|
+
if (type === 'date') {
|
1013
|
+
return obj.toISODate();
|
1014
|
+
}
|
1015
|
+
if (type === 'range') {
|
1016
|
+
return '<range>';
|
1017
|
+
}
|
1018
|
+
if (type === 'time') {
|
1019
|
+
if (obj.zone === SystemZone.instance) {
|
1020
|
+
return obj.toISOTime({ suppressMilliseconds: true, includeOffset: false });
|
1021
|
+
}
|
1022
|
+
if ((_c = obj.zone) === null || _c === void 0 ? void 0 : _c.zoneName) {
|
1023
|
+
return obj.toISOTime({ suppressMilliseconds: true, includeOffset: false }) + '@' + ((_d = obj.zone) === null || _d === void 0 ? void 0 : _d.zoneName);
|
1024
|
+
}
|
1025
|
+
return obj.toISOTime({ suppressMilliseconds: true });
|
1026
|
+
}
|
1027
|
+
if (type === 'function') {
|
1028
|
+
return '<function>';
|
1029
|
+
}
|
1030
|
+
throw notImplemented('string(' + type + ')');
|
1031
|
+
}
|
1032
|
+
function countSymbols(str) {
|
1033
|
+
// cf. https://mathiasbynens.be/notes/javascript-unicode
|
1034
|
+
return str.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '_').length;
|
1035
|
+
}
|
1036
|
+
function bankersRound(n) {
|
1037
|
+
const floored = Math.floor(n);
|
1038
|
+
const decimalPart = n - floored;
|
1039
|
+
if (decimalPart === 0.5) {
|
1040
|
+
return (floored % 2 === 0) ? floored : floored + 1;
|
1041
|
+
}
|
1042
|
+
return Math.round(n);
|
1043
|
+
}
|
1044
|
+
// adapted from https://stackoverflow.com/a/53577159
|
1045
|
+
function stddev(array) {
|
1046
|
+
const n = array.length;
|
1047
|
+
const mean = array.reduce((a, b) => a + b) / n;
|
1048
|
+
return Math.sqrt(array.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / (n - 1));
|
1049
|
+
}
|
1050
|
+
function listReplace(list, matcher, newItem) {
|
1051
|
+
if (isNumber(matcher)) {
|
1052
|
+
return [...list.slice(0, matcher - 1), newItem, ...list.slice(matcher)];
|
1053
|
+
}
|
1054
|
+
return list.map((item, _idx) => {
|
1055
|
+
if (matcher.invoke([item, newItem])) {
|
1056
|
+
return newItem;
|
1057
|
+
}
|
1058
|
+
else {
|
1059
|
+
return item;
|
1060
|
+
}
|
1061
|
+
});
|
1062
|
+
}
|
1063
|
+
function median(array) {
|
1064
|
+
const n = array.length;
|
1065
|
+
const sorted = array.slice().sort();
|
1066
|
+
const mid = n / 2 - 1;
|
1067
|
+
const index = Math.ceil(mid);
|
1068
|
+
// even
|
1069
|
+
if (mid === index) {
|
1070
|
+
return (sorted[index] + sorted[index + 1]) / 2;
|
1071
|
+
}
|
1072
|
+
// uneven
|
1073
|
+
return sorted[index];
|
1074
|
+
}
|
1075
|
+
function mode(array) {
|
1076
|
+
if (array.length < 2) {
|
1077
|
+
return array;
|
1078
|
+
}
|
1079
|
+
const buckets = {};
|
1080
|
+
for (const n of array) {
|
1081
|
+
buckets[n] = (buckets[n] || 0) + 1;
|
1082
|
+
}
|
1083
|
+
const sorted = Object.entries(buckets).sort((a, b) => b[1] - a[1]);
|
1084
|
+
return sorted.filter(s => s[1] === sorted[0][1]).map(e => +e[0]);
|
1085
|
+
}
|
1086
|
+
function ifValid(o) {
|
1087
|
+
return o.isValid ? o : null;
|
1088
|
+
}
|
1089
|
+
/**
|
1090
|
+
* Concatenates flags for a regular expression.
|
1091
|
+
*
|
1092
|
+
* Ensures that default flags are included without duplication, even if
|
1093
|
+
* user-specified flags overlap with the defaults.
|
1094
|
+
*/
|
1095
|
+
function buildFlags(flags, defaultFlags) {
|
1096
|
+
const unsupportedFlags = flags.replace(/[smix]/g, '');
|
1097
|
+
if (unsupportedFlags) {
|
1098
|
+
throw new Error('illegal flags: ' + unsupportedFlags);
|
1099
|
+
}
|
1100
|
+
// we don't implement the <x> flag
|
1101
|
+
if (/x/.test(flags)) {
|
1102
|
+
throw notImplemented('matches <x> flag');
|
1103
|
+
}
|
1104
|
+
return flags + defaultFlags;
|
1105
|
+
}
|
1106
|
+
/**
|
1107
|
+
* Creates a regular expression from a given pattern
|
1108
|
+
*/
|
1109
|
+
function createRegexp(pattern, flags, defaultFlags = '') {
|
1110
|
+
try {
|
1111
|
+
return new RegExp(pattern, 'u' + buildFlags(flags, defaultFlags));
|
1112
|
+
}
|
1113
|
+
catch (_err) {
|
1114
|
+
if (isNotImplemented(_err)) {
|
1115
|
+
throw _err;
|
1116
|
+
}
|
1117
|
+
}
|
1118
|
+
return null;
|
1119
|
+
}
|
1120
|
+
|
1121
|
+
function parseExpression(expression, context = {}, dialect) {
|
1122
|
+
return parser.configure({
|
1123
|
+
top: 'Expression',
|
1124
|
+
contextTracker: trackVariables(context),
|
1125
|
+
dialect
|
1126
|
+
}).parse(expression);
|
1127
|
+
}
|
1128
|
+
function parseUnaryTests(expression, context = {}, dialect) {
|
1129
|
+
return parser.configure({
|
1130
|
+
top: 'UnaryTests',
|
1131
|
+
contextTracker: trackVariables(context),
|
1132
|
+
dialect
|
1133
|
+
}).parse(expression);
|
1134
|
+
}
|
1135
|
+
|
1136
|
+
class SyntaxError extends Error {
|
1137
|
+
constructor(message, details) {
|
1138
|
+
super(message);
|
1139
|
+
Object.assign(this, details);
|
1140
|
+
}
|
1141
|
+
}
|
1142
|
+
class Interpreter {
|
1143
|
+
_buildExecutionTree(tree, input) {
|
1144
|
+
const root = { args: [], nodeInput: input };
|
1145
|
+
const stack = [root];
|
1146
|
+
tree.iterate({
|
1147
|
+
enter(nodeRef) {
|
1148
|
+
const { isError, isSkipped } = nodeRef.type;
|
1149
|
+
const { from, to } = nodeRef;
|
1150
|
+
if (isError) {
|
1151
|
+
const { from, to, message } = lintError(nodeRef);
|
1152
|
+
throw new SyntaxError(message, {
|
1153
|
+
input: input.slice(from, to),
|
1154
|
+
position: {
|
1155
|
+
from,
|
1156
|
+
to
|
1157
|
+
}
|
1158
|
+
});
|
1159
|
+
}
|
1160
|
+
if (isSkipped) {
|
1161
|
+
return false;
|
1162
|
+
}
|
1163
|
+
const nodeInput = input.slice(from, to);
|
1164
|
+
stack.push({
|
1165
|
+
nodeInput,
|
1166
|
+
args: []
|
1167
|
+
});
|
1168
|
+
},
|
1169
|
+
leave(nodeRef) {
|
1170
|
+
if (nodeRef.type.isSkipped) {
|
1171
|
+
return;
|
1172
|
+
}
|
1173
|
+
const { nodeInput, args } = stack.pop();
|
1174
|
+
const parent = stack[stack.length - 1];
|
1175
|
+
const expr = evalNode(nodeRef, nodeInput, args);
|
1176
|
+
parent.args.push(expr);
|
1177
|
+
}
|
1178
|
+
});
|
1179
|
+
return root.args[root.args.length - 1];
|
1180
|
+
}
|
1181
|
+
evaluate(expression, context = {}, dialect) {
|
1182
|
+
const parseTree = parseExpression(expression, context, dialect);
|
1183
|
+
const root = this._buildExecutionTree(parseTree, expression);
|
1184
|
+
return {
|
1185
|
+
parseTree,
|
1186
|
+
root
|
1187
|
+
};
|
1188
|
+
}
|
1189
|
+
unaryTest(expression, context = {}, dialect) {
|
1190
|
+
const parseTree = parseUnaryTests(expression, context, dialect);
|
1191
|
+
const root = this._buildExecutionTree(parseTree, expression);
|
1192
|
+
return {
|
1193
|
+
parseTree,
|
1194
|
+
root
|
1195
|
+
};
|
1196
|
+
}
|
1197
|
+
}
|
1198
|
+
const interpreter = new Interpreter();
|
1199
|
+
function unaryTest(expression, context = {}, dialect) {
|
1200
|
+
const value = context['?'] !== undefined ? context['?'] : null;
|
1201
|
+
const { root } = interpreter.unaryTest(expression, context, dialect);
|
1202
|
+
// root = fn(ctx) => test(val)
|
1203
|
+
const test = root(context);
|
1204
|
+
return test(value);
|
1205
|
+
}
|
1206
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
1207
|
+
function evaluate(expression, context = {}, dialect) {
|
1208
|
+
const { root } = interpreter.evaluate(expression, context, dialect);
|
1209
|
+
// root = Expression :: fn(ctx)
|
1210
|
+
return root(context);
|
1211
|
+
}
|
1212
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
1213
|
+
function evalNode(node, input, args) {
|
1214
|
+
switch (node.name) {
|
1215
|
+
case 'ArithOp': return (context) => {
|
1216
|
+
const nullable = (op, types = ['number']) => (a, b) => {
|
1217
|
+
const left = a(context);
|
1218
|
+
const right = b(context);
|
1219
|
+
if (isArray(left)) {
|
1220
|
+
return null;
|
1221
|
+
}
|
1222
|
+
if (isArray(right)) {
|
1223
|
+
return null;
|
1224
|
+
}
|
1225
|
+
const leftType = getType(left);
|
1226
|
+
const rightType = getType(right);
|
1227
|
+
const temporal = ['date', 'time', 'date time', 'duration'];
|
1228
|
+
if (temporal.includes(leftType)) {
|
1229
|
+
if (!temporal.includes(rightType)) {
|
1230
|
+
return null;
|
1231
|
+
}
|
1232
|
+
}
|
1233
|
+
else if (leftType !== rightType || !types.includes(leftType)) {
|
1234
|
+
return null;
|
1235
|
+
}
|
1236
|
+
return op(left, right);
|
1237
|
+
};
|
1238
|
+
switch (input) {
|
1239
|
+
case '+': return nullable((a, b) => {
|
1240
|
+
// flip these as luxon operations with durations aren't commutative
|
1241
|
+
if (isDuration(a) && !isDuration(b)) {
|
1242
|
+
const tmp = a;
|
1243
|
+
a = b;
|
1244
|
+
b = tmp;
|
1245
|
+
}
|
1246
|
+
if (isType(a, 'time') && isDuration(b)) {
|
1247
|
+
return a.plus(b).set({
|
1248
|
+
year: 1900,
|
1249
|
+
month: 1,
|
1250
|
+
day: 1
|
1251
|
+
});
|
1252
|
+
}
|
1253
|
+
else if (isDateTime(a) && isDateTime(b)) {
|
1254
|
+
return null;
|
1255
|
+
}
|
1256
|
+
else if (isDateTime(a) && isDuration(b)) {
|
1257
|
+
return a.plus(b);
|
1258
|
+
}
|
1259
|
+
else if (isDuration(a) && isDuration(b)) {
|
1260
|
+
return a.plus(b);
|
1261
|
+
}
|
1262
|
+
return a + b;
|
1263
|
+
}, ['string', 'number', 'date', 'time', 'duration', 'date time']);
|
1264
|
+
case '-': return nullable((a, b) => {
|
1265
|
+
if (isType(a, 'time') && isDuration(b)) {
|
1266
|
+
return a.minus(b).set({
|
1267
|
+
year: 1900,
|
1268
|
+
month: 1,
|
1269
|
+
day: 1
|
1270
|
+
});
|
1271
|
+
}
|
1272
|
+
else if (isDateTime(a) && isDateTime(b)) {
|
1273
|
+
return a.diff(b);
|
1274
|
+
}
|
1275
|
+
else if (isDateTime(a) && isDuration(b)) {
|
1276
|
+
return a.minus(b);
|
1277
|
+
}
|
1278
|
+
else if (isDuration(a) && isDuration(b)) {
|
1279
|
+
return a.minus(b);
|
1280
|
+
}
|
1281
|
+
return a - b;
|
1282
|
+
}, ['number', 'date', 'time', 'duration', 'date time']);
|
1283
|
+
case '*': return nullable((a, b) => a * b);
|
1284
|
+
case '/': return nullable((a, b) => !b ? null : a / b);
|
1285
|
+
case '**':
|
1286
|
+
case '^': return nullable((a, b) => Math.pow(a, b));
|
1287
|
+
}
|
1288
|
+
};
|
1289
|
+
case 'CompareOp': return tag(() => {
|
1290
|
+
switch (input) {
|
1291
|
+
case '>': return (b) => createRange(b, null, false, false);
|
1292
|
+
case '>=': return (b) => createRange(b, null, true, false);
|
1293
|
+
case '<': return (b) => createRange(null, b, false, false);
|
1294
|
+
case '<=': return (b) => createRange(null, b, false, true);
|
1295
|
+
case '=': return (b) => (a) => equals(a, b);
|
1296
|
+
case '!=': return (b) => (a) => !equals(a, b);
|
1297
|
+
}
|
1298
|
+
}, Test('boolean'));
|
1299
|
+
case 'BacktickIdentifier': return input.replace(/`/g, '');
|
1300
|
+
case 'Wildcard': return (_context) => true;
|
1301
|
+
case 'null': return (_context) => {
|
1302
|
+
return null;
|
1303
|
+
};
|
1304
|
+
case 'Disjunction': return tag((context) => {
|
1305
|
+
const left = args[0](context);
|
1306
|
+
const right = args[2](context);
|
1307
|
+
const matrix = [
|
1308
|
+
[true, true, true],
|
1309
|
+
[true, false, true],
|
1310
|
+
[true, null, true],
|
1311
|
+
[false, true, true],
|
1312
|
+
[false, false, false],
|
1313
|
+
[false, null, null],
|
1314
|
+
[null, true, true],
|
1315
|
+
[null, false, null],
|
1316
|
+
[null, null, null],
|
1317
|
+
];
|
1318
|
+
const a = typeof left === 'boolean' ? left : null;
|
1319
|
+
const b = typeof right === 'boolean' ? right : null;
|
1320
|
+
return matrix.find(el => el[0] === a && el[1] === b)[2];
|
1321
|
+
}, Test('boolean'));
|
1322
|
+
case 'Conjunction': return tag((context) => {
|
1323
|
+
const left = args[0](context);
|
1324
|
+
const right = args[2](context);
|
1325
|
+
const matrix = [
|
1326
|
+
[true, true, true],
|
1327
|
+
[true, false, false],
|
1328
|
+
[true, null, null],
|
1329
|
+
[false, true, false],
|
1330
|
+
[false, false, false],
|
1331
|
+
[false, null, false],
|
1332
|
+
[null, true, null],
|
1333
|
+
[null, false, false],
|
1334
|
+
[null, null, null],
|
1335
|
+
];
|
1336
|
+
const a = typeof left === 'boolean' ? left : null;
|
1337
|
+
const b = typeof right === 'boolean' ? right : null;
|
1338
|
+
return matrix.find(el => el[0] === a && el[1] === b)[2];
|
1339
|
+
}, Test('boolean'));
|
1340
|
+
case 'Context': return (context) => {
|
1341
|
+
return args.slice(1, -1).reduce((obj, arg) => {
|
1342
|
+
const [key, value] = arg(Object.assign(Object.assign({}, context), obj));
|
1343
|
+
return Object.assign(Object.assign({}, obj), { [key]: value });
|
1344
|
+
}, {});
|
1345
|
+
};
|
1346
|
+
case 'FunctionBody': return args[0];
|
1347
|
+
case 'FormalParameters': return args;
|
1348
|
+
case 'FormalParameter': return args[0];
|
1349
|
+
case 'ParameterName': return args.join(' ');
|
1350
|
+
case 'FunctionDefinition': return (context) => {
|
1351
|
+
const parameterNames = args[2];
|
1352
|
+
const fnBody = args[4];
|
1353
|
+
return wrapFunction((...args) => {
|
1354
|
+
const fnContext = parameterNames.reduce((context, name, idx) => {
|
1355
|
+
// support positional parameters
|
1356
|
+
context[name] = args[idx];
|
1357
|
+
return context;
|
1358
|
+
}, Object.assign({}, context));
|
1359
|
+
return fnBody(fnContext);
|
1360
|
+
}, parameterNames);
|
1361
|
+
};
|
1362
|
+
case 'ContextEntry': return (context) => {
|
1363
|
+
const key = typeof args[0] === 'function' ? args[0](context) : args[0];
|
1364
|
+
const value = args[1](context);
|
1365
|
+
return [key, value];
|
1366
|
+
};
|
1367
|
+
case 'Key': return args[0];
|
1368
|
+
case 'Identifier': return input;
|
1369
|
+
case 'SpecialFunctionName': return (context) => getBuiltin(input);
|
1370
|
+
// preserve spaces in name, but compact multiple
|
1371
|
+
// spaces into one (token)
|
1372
|
+
case 'Name': return input.replace(/\s{2,}/g, ' ');
|
1373
|
+
case 'VariableName': return (context) => {
|
1374
|
+
const name = args.join(' ');
|
1375
|
+
const contextValue = getFromContext(name, context);
|
1376
|
+
return (typeof contextValue !== 'undefined'
|
1377
|
+
? contextValue
|
1378
|
+
: getBuiltin(name) || null);
|
1379
|
+
};
|
1380
|
+
case 'QualifiedName': return (context) => {
|
1381
|
+
return args.reduce((context, arg) => arg(context), context);
|
1382
|
+
};
|
1383
|
+
case '?': return (context) => getFromContext('?', context);
|
1384
|
+
// expression
|
1385
|
+
// expression ".." expression
|
1386
|
+
case 'IterationContext': return (context) => {
|
1387
|
+
const a = args[0](context);
|
1388
|
+
const b = args[1] && args[1](context);
|
1389
|
+
return b ? createRange(a, b) : a;
|
1390
|
+
};
|
1391
|
+
case 'Type': return args[0];
|
1392
|
+
// (x in [ [1,2], [3,4] ]), (y in x)
|
1393
|
+
case 'InExpressions': return (context) => {
|
1394
|
+
// we build up evaluation contexts from left to right,
|
1395
|
+
// ending up with the cartesian product over all available contexts
|
1396
|
+
//
|
1397
|
+
// previous context is provided to later context providers
|
1398
|
+
// producing <null> as a context during evaluation causes the
|
1399
|
+
// whole result to turn <null>
|
1400
|
+
const isValidContexts = (contexts) => {
|
1401
|
+
if (contexts === null || contexts.some(arr => getType(arr) === 'nil')) {
|
1402
|
+
return false;
|
1403
|
+
}
|
1404
|
+
return true;
|
1405
|
+
};
|
1406
|
+
const join = (aContexts, bContextProducer) => {
|
1407
|
+
return [].concat(...aContexts.map(aContext => {
|
1408
|
+
const bContexts = bContextProducer(Object.assign(Object.assign({}, context), aContext));
|
1409
|
+
if (!isValidContexts(bContexts)) {
|
1410
|
+
return null;
|
1411
|
+
}
|
1412
|
+
return bContexts.map(bContext => {
|
1413
|
+
return Object.assign(Object.assign({}, aContext), bContext);
|
1414
|
+
});
|
1415
|
+
}));
|
1416
|
+
};
|
1417
|
+
const cartesian = (aContexts, bContextProducer, ...otherContextProducers) => {
|
1418
|
+
if (!isValidContexts(aContexts)) {
|
1419
|
+
return null;
|
1420
|
+
}
|
1421
|
+
if (!bContextProducer) {
|
1422
|
+
return aContexts;
|
1423
|
+
}
|
1424
|
+
return cartesian(join(aContexts, bContextProducer), ...otherContextProducers);
|
1425
|
+
};
|
1426
|
+
const cartesianProduct = (contextProducers) => {
|
1427
|
+
const [aContextProducer, ...otherContextProducers] = contextProducers;
|
1428
|
+
const aContexts = aContextProducer(context);
|
1429
|
+
return cartesian(aContexts, ...otherContextProducers);
|
1430
|
+
};
|
1431
|
+
const product = cartesianProduct(args);
|
1432
|
+
return product && product.map(p => {
|
1433
|
+
return Object.assign(Object.assign({}, context), p);
|
1434
|
+
});
|
1435
|
+
};
|
1436
|
+
// Name kw<"in"> Expr
|
1437
|
+
case 'InExpression': return (context) => {
|
1438
|
+
return extractValue(context, args[0], args[2]);
|
1439
|
+
};
|
1440
|
+
case 'SpecialType': throw notImplemented('SpecialType');
|
1441
|
+
case 'InstanceOfExpression': return tag((context) => {
|
1442
|
+
const a = args[0](context);
|
1443
|
+
const b = args[3](context);
|
1444
|
+
return a instanceof b;
|
1445
|
+
}, Test('boolean'));
|
1446
|
+
case 'every': return tag((context) => {
|
1447
|
+
return (_contexts, _condition) => {
|
1448
|
+
const contexts = _contexts(context);
|
1449
|
+
if (getType(contexts) !== 'list') {
|
1450
|
+
return contexts;
|
1451
|
+
}
|
1452
|
+
return contexts.every(ctx => isTruthy(_condition(ctx)));
|
1453
|
+
};
|
1454
|
+
}, Test('boolean'));
|
1455
|
+
case 'some': return tag((context) => {
|
1456
|
+
return (_contexts, _condition) => {
|
1457
|
+
const contexts = _contexts(context);
|
1458
|
+
if (getType(contexts) !== 'list') {
|
1459
|
+
return contexts;
|
1460
|
+
}
|
1461
|
+
return contexts.some(ctx => isTruthy(_condition(ctx)));
|
1462
|
+
};
|
1463
|
+
}, Test('boolean'));
|
1464
|
+
case 'NumericLiteral': return tag((_context) => input.includes('.') ? parseFloat(input) : parseInt(input), 'number');
|
1465
|
+
case 'BooleanLiteral': return tag((_context) => input === 'true' ? true : false, 'boolean');
|
1466
|
+
case 'StringLiteral': return tag((_context) => parseString(input), 'string');
|
1467
|
+
case 'PositionalParameters': return (context) => args.map(arg => arg(context));
|
1468
|
+
case 'NamedParameter': return (context) => {
|
1469
|
+
const name = args[0];
|
1470
|
+
const value = args[1](context);
|
1471
|
+
return [name, value];
|
1472
|
+
};
|
1473
|
+
case 'NamedParameters': return (context) => args.reduce((args, arg) => {
|
1474
|
+
const [name, value] = arg(context);
|
1475
|
+
args[name] = value;
|
1476
|
+
return args;
|
1477
|
+
}, {});
|
1478
|
+
case 'DateTimeConstructor': return (context) => {
|
1479
|
+
return getBuiltin(input);
|
1480
|
+
};
|
1481
|
+
case 'DateTimeLiteral': return (context) => {
|
1482
|
+
// AtLiteral
|
1483
|
+
if (args.length === 1) {
|
1484
|
+
return args[0](context);
|
1485
|
+
}
|
1486
|
+
// FunctionInvocation
|
1487
|
+
else {
|
1488
|
+
const wrappedFn = wrapFunction(args[0](context));
|
1489
|
+
// TODO(nikku): indicate as error
|
1490
|
+
// throw new Error(`Failed to evaluate ${input}: Target is not a function`);
|
1491
|
+
if (!wrappedFn) {
|
1492
|
+
return null;
|
1493
|
+
}
|
1494
|
+
const contextOrArgs = args[2](context);
|
1495
|
+
return wrappedFn.invoke(contextOrArgs);
|
1496
|
+
}
|
1497
|
+
};
|
1498
|
+
case 'AtLiteral': return (context) => {
|
1499
|
+
const wrappedFn = wrapFunction(getBuiltin('@'));
|
1500
|
+
// TODO(nikku): indicate as error
|
1501
|
+
// throw new Error(`Failed to evaluate ${input}: Target is not a function`);
|
1502
|
+
if (!wrappedFn) {
|
1503
|
+
return null;
|
1504
|
+
}
|
1505
|
+
return wrappedFn.invoke([args[0](context)]);
|
1506
|
+
};
|
1507
|
+
case 'FunctionInvocation': return (context) => {
|
1508
|
+
const wrappedFn = wrapFunction(args[0](context));
|
1509
|
+
// TODO(nikku): indicate error at node
|
1510
|
+
// throw new Error(`Failed to evaluate ${input}: Target is not a function`);
|
1511
|
+
if (!wrappedFn) {
|
1512
|
+
return null;
|
1513
|
+
}
|
1514
|
+
const contextOrArgs = args[2](context);
|
1515
|
+
return wrappedFn.invoke(contextOrArgs);
|
1516
|
+
};
|
1517
|
+
case 'IfExpression': return (function () {
|
1518
|
+
const ifCondition = args[1];
|
1519
|
+
const thenValue = args[3];
|
1520
|
+
const elseValue = args[5];
|
1521
|
+
const type = coalecenseTypes(thenValue, elseValue);
|
1522
|
+
return tag((context) => {
|
1523
|
+
if (isTruthy(ifCondition(context))) {
|
1524
|
+
return thenValue(context);
|
1525
|
+
}
|
1526
|
+
else {
|
1527
|
+
return elseValue ? elseValue(context) : null;
|
1528
|
+
}
|
1529
|
+
}, type);
|
1530
|
+
})();
|
1531
|
+
case 'Parameters': return args.length === 3 ? args[1] : (_context) => [];
|
1532
|
+
case 'Comparison': return (context) => {
|
1533
|
+
const operator = args[1];
|
1534
|
+
// expression !compare kw<"in"> PositiveUnaryTest |
|
1535
|
+
// expression !compare kw<"in"> !unaryTest "(" PositiveUnaryTests ")"
|
1536
|
+
if (operator === 'in') {
|
1537
|
+
return compareIn(args[0](context), (args[3] || args[2])(context));
|
1538
|
+
}
|
1539
|
+
// expression !compare kw<"between"> expression kw<"and"> expression
|
1540
|
+
if (operator === 'between') {
|
1541
|
+
const start = args[2](context);
|
1542
|
+
const end = args[4](context);
|
1543
|
+
if (start === null || end === null) {
|
1544
|
+
return null;
|
1545
|
+
}
|
1546
|
+
return createRange(start, end).includes(args[0](context));
|
1547
|
+
}
|
1548
|
+
// expression !compare CompareOp<"=" | "!="> expression |
|
1549
|
+
// expression !compare CompareOp<Gt | Gte | Lt | Lte> expression |
|
1550
|
+
const left = args[0](context);
|
1551
|
+
const right = args[2](context);
|
1552
|
+
const test = operator()(right);
|
1553
|
+
return compareValue(test, left);
|
1554
|
+
};
|
1555
|
+
case 'QuantifiedExpression': return (context) => {
|
1556
|
+
const testFn = args[0](context);
|
1557
|
+
const contexts = args[1];
|
1558
|
+
const condition = args[3];
|
1559
|
+
return testFn(contexts, condition);
|
1560
|
+
};
|
1561
|
+
// DMN 1.2 - 10.3.2.14
|
1562
|
+
// kw<"for"> commaSep1<InExpression<IterationContext>> kw<"return"> expression
|
1563
|
+
case 'ForExpression': return (context) => {
|
1564
|
+
const extractor = args[args.length - 1];
|
1565
|
+
const iterationContexts = args[1](context);
|
1566
|
+
if (getType(iterationContexts) !== 'list') {
|
1567
|
+
return iterationContexts;
|
1568
|
+
}
|
1569
|
+
const partial = [];
|
1570
|
+
for (const ctx of iterationContexts) {
|
1571
|
+
partial.push(extractor(Object.assign(Object.assign({}, ctx), { partial })));
|
1572
|
+
}
|
1573
|
+
return partial;
|
1574
|
+
};
|
1575
|
+
case 'ArithmeticExpression': return (function () {
|
1576
|
+
// binary expression (a + b)
|
1577
|
+
if (args.length === 3) {
|
1578
|
+
const [a, op, b] = args;
|
1579
|
+
return tag((context) => {
|
1580
|
+
return op(context)(a, b);
|
1581
|
+
}, coalecenseTypes(a, b));
|
1582
|
+
}
|
1583
|
+
// unary expression (-b)
|
1584
|
+
if (args.length === 2) {
|
1585
|
+
const [op, value] = args;
|
1586
|
+
return tag((context) => {
|
1587
|
+
return op(context)(() => 0, value);
|
1588
|
+
}, value.type);
|
1589
|
+
}
|
1590
|
+
})();
|
1591
|
+
case 'PositiveUnaryTest': return args[0];
|
1592
|
+
case 'ParenthesizedExpression': return args[1];
|
1593
|
+
case 'PathExpression': return (context) => {
|
1594
|
+
const pathTarget = args[0](context);
|
1595
|
+
const pathProp = args[1];
|
1596
|
+
if (isArray(pathTarget)) {
|
1597
|
+
return pathTarget.map(pathProp);
|
1598
|
+
}
|
1599
|
+
else {
|
1600
|
+
return pathProp(pathTarget);
|
1601
|
+
}
|
1602
|
+
};
|
1603
|
+
// expression !filter "[" expression "]"
|
1604
|
+
case 'FilterExpression': return (context) => {
|
1605
|
+
const target = args[0](context);
|
1606
|
+
const filterFn = args[2];
|
1607
|
+
const filterTarget = isArray(target) ? target : [target];
|
1608
|
+
// null[..]
|
1609
|
+
if (target === null) {
|
1610
|
+
return null;
|
1611
|
+
}
|
1612
|
+
// a[variable=number]
|
1613
|
+
if (typeof filterFn.type === 'undefined') {
|
1614
|
+
try {
|
1615
|
+
const value = filterFn(context);
|
1616
|
+
if (isNumber(value)) {
|
1617
|
+
filterFn.type = 'number';
|
1618
|
+
}
|
1619
|
+
}
|
1620
|
+
catch (_err) {
|
1621
|
+
// ignore
|
1622
|
+
}
|
1623
|
+
}
|
1624
|
+
// a[1]
|
1625
|
+
if (filterFn.type === 'number') {
|
1626
|
+
const idx = filterFn(context);
|
1627
|
+
const value = filterTarget[idx < 0 ? filterTarget.length + idx : idx - 1];
|
1628
|
+
if (typeof value === 'undefined') {
|
1629
|
+
return null;
|
1630
|
+
}
|
1631
|
+
else {
|
1632
|
+
return value;
|
1633
|
+
}
|
1634
|
+
}
|
1635
|
+
// a[true]
|
1636
|
+
if (filterFn.type === 'boolean') {
|
1637
|
+
if (filterFn(context)) {
|
1638
|
+
return filterTarget;
|
1639
|
+
}
|
1640
|
+
else {
|
1641
|
+
return [];
|
1642
|
+
}
|
1643
|
+
}
|
1644
|
+
if (filterFn.type === 'string') {
|
1645
|
+
const value = filterFn(context);
|
1646
|
+
return filterTarget.filter(el => el === value);
|
1647
|
+
}
|
1648
|
+
// a[test]
|
1649
|
+
return filterTarget.map(el => {
|
1650
|
+
const iterationContext = Object.assign(Object.assign(Object.assign({}, context), { item: el }), el);
|
1651
|
+
let result = filterFn(iterationContext);
|
1652
|
+
// test is fn(val) => boolean SimpleUnaryTest
|
1653
|
+
if (typeof result === 'function') {
|
1654
|
+
result = result(el);
|
1655
|
+
}
|
1656
|
+
if (result instanceof Range) {
|
1657
|
+
result = result.includes(el);
|
1658
|
+
}
|
1659
|
+
if (result === true) {
|
1660
|
+
return el;
|
1661
|
+
}
|
1662
|
+
return result;
|
1663
|
+
}).filter(isTruthy);
|
1664
|
+
};
|
1665
|
+
case 'SimplePositiveUnaryTest': return tag((context) => {
|
1666
|
+
// <Interval>
|
1667
|
+
if (args.length === 1) {
|
1668
|
+
return args[0](context);
|
1669
|
+
}
|
1670
|
+
// <CompareOp> <Expr>
|
1671
|
+
return args[0](context)(args[1](context));
|
1672
|
+
}, 'test');
|
1673
|
+
case 'List': return (context) => {
|
1674
|
+
return args.slice(1, -1).map(arg => arg(context));
|
1675
|
+
};
|
1676
|
+
case 'Interval': return tag((context) => {
|
1677
|
+
const left = args[1](context);
|
1678
|
+
const right = args[2](context);
|
1679
|
+
const startIncluded = left !== null && args[0] === '[';
|
1680
|
+
const endIncluded = right !== null && args[3] === ']';
|
1681
|
+
return createRange(left, right, startIncluded, endIncluded);
|
1682
|
+
}, Test('boolean'));
|
1683
|
+
case 'PositiveUnaryTests':
|
1684
|
+
case 'Expressions': return (context) => {
|
1685
|
+
return args.map(a => a(context));
|
1686
|
+
};
|
1687
|
+
case 'Expression': return (context) => {
|
1688
|
+
return args[0](context);
|
1689
|
+
};
|
1690
|
+
case 'UnaryTests': return (context) => {
|
1691
|
+
return (value = null) => {
|
1692
|
+
const negate = args[0] === 'not';
|
1693
|
+
const tests = negate ? args.slice(2, -1) : args;
|
1694
|
+
const matches = tests.map(test => test(context)).flat(1).map(test => {
|
1695
|
+
if (isArray(test)) {
|
1696
|
+
return test.includes(value);
|
1697
|
+
}
|
1698
|
+
if (test === null) {
|
1699
|
+
return null;
|
1700
|
+
}
|
1701
|
+
if (typeof test === 'boolean') {
|
1702
|
+
return test;
|
1703
|
+
}
|
1704
|
+
return compareValue(test, value);
|
1705
|
+
}).reduce(combineResult, undefined);
|
1706
|
+
return matches === null ? null : (negate ? !matches : matches);
|
1707
|
+
};
|
1708
|
+
};
|
1709
|
+
default: return node.name;
|
1710
|
+
}
|
1711
|
+
}
|
1712
|
+
function getBuiltin(name, _context) {
|
1713
|
+
return getFromContext(name, builtins);
|
1714
|
+
}
|
1715
|
+
function extractValue(context, prop, _target) {
|
1716
|
+
const target = _target(context);
|
1717
|
+
if (['list', 'range'].includes(getType(target))) {
|
1718
|
+
return target.map(t => ({ [prop]: t }));
|
1719
|
+
}
|
1720
|
+
return null;
|
1721
|
+
}
|
1722
|
+
function compareIn(value, tests) {
|
1723
|
+
if (!isArray(tests)) {
|
1724
|
+
if (getType(tests) === 'nil') {
|
1725
|
+
return null;
|
1726
|
+
}
|
1727
|
+
tests = [tests];
|
1728
|
+
}
|
1729
|
+
return tests.some(test => compareValue(test, value));
|
1730
|
+
}
|
1731
|
+
function compareValue(test, value) {
|
1732
|
+
if (typeof test === 'function') {
|
1733
|
+
return test(value);
|
1734
|
+
}
|
1735
|
+
if (test instanceof Range) {
|
1736
|
+
return test.includes(value);
|
1737
|
+
}
|
1738
|
+
return equals(test, value);
|
1739
|
+
}
|
1740
|
+
const chars = Array.from('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
|
1741
|
+
function isTyped(type, values) {
|
1742
|
+
return (values.some(e => getType(e) === type) &&
|
1743
|
+
values.every(e => e === null || getType(e) === type));
|
1744
|
+
}
|
1745
|
+
const nullRange = new Range({
|
1746
|
+
start: null,
|
1747
|
+
end: null,
|
1748
|
+
'start included': false,
|
1749
|
+
'end included': false,
|
1750
|
+
map() {
|
1751
|
+
return [];
|
1752
|
+
},
|
1753
|
+
includes() {
|
1754
|
+
return null;
|
1755
|
+
}
|
1756
|
+
});
|
1757
|
+
function createRange(start, end, startIncluded = true, endIncluded = true) {
|
1758
|
+
if (isTyped('string', [start, end])) {
|
1759
|
+
return createStringRange(start, end, startIncluded, endIncluded);
|
1760
|
+
}
|
1761
|
+
if (isTyped('number', [start, end])) {
|
1762
|
+
return createNumberRange(start, end, startIncluded, endIncluded);
|
1763
|
+
}
|
1764
|
+
if (isTyped('duration', [start, end])) {
|
1765
|
+
return createDurationRange(start, end, startIncluded, endIncluded);
|
1766
|
+
}
|
1767
|
+
if (isTyped('time', [start, end])) {
|
1768
|
+
return createDateTimeRange(start, end, startIncluded, endIncluded);
|
1769
|
+
}
|
1770
|
+
if (isTyped('date time', [start, end])) {
|
1771
|
+
return createDateTimeRange(start, end, startIncluded, endIncluded);
|
1772
|
+
}
|
1773
|
+
if (isTyped('date', [start, end])) {
|
1774
|
+
return createDateTimeRange(start, end, startIncluded, endIncluded);
|
1775
|
+
}
|
1776
|
+
if (start === null && end === null) {
|
1777
|
+
return nullRange;
|
1778
|
+
}
|
1779
|
+
throw new Error(`unsupported range: ${start}..${end}`);
|
1780
|
+
}
|
1781
|
+
function noopMap() {
|
1782
|
+
return () => {
|
1783
|
+
throw new Error('unsupported range operation: map');
|
1784
|
+
};
|
1785
|
+
}
|
1786
|
+
function valuesMap(values) {
|
1787
|
+
return (fn) => values.map(fn);
|
1788
|
+
}
|
1789
|
+
function valuesIncludes(values) {
|
1790
|
+
return (value) => values.includes(value);
|
1791
|
+
}
|
1792
|
+
function numberMap(start, end, startIncluded, endIncluded) {
|
1793
|
+
const direction = start > end ? -1 : 1;
|
1794
|
+
return (fn) => {
|
1795
|
+
const result = [];
|
1796
|
+
for (let i = start;; i += direction) {
|
1797
|
+
if (i === 0 && !startIncluded) {
|
1798
|
+
continue;
|
1799
|
+
}
|
1800
|
+
if (i === end && !endIncluded) {
|
1801
|
+
break;
|
1802
|
+
}
|
1803
|
+
result.push(fn(i));
|
1804
|
+
if (i === end) {
|
1805
|
+
break;
|
1806
|
+
}
|
1807
|
+
}
|
1808
|
+
return result;
|
1809
|
+
};
|
1810
|
+
}
|
1811
|
+
function includesStart(n, inclusive) {
|
1812
|
+
if (inclusive) {
|
1813
|
+
return (value) => n <= value;
|
1814
|
+
}
|
1815
|
+
else {
|
1816
|
+
return (value) => n < value;
|
1817
|
+
}
|
1818
|
+
}
|
1819
|
+
function includesEnd(n, inclusive) {
|
1820
|
+
if (inclusive) {
|
1821
|
+
return (value) => n >= value;
|
1822
|
+
}
|
1823
|
+
else {
|
1824
|
+
return (value) => n > value;
|
1825
|
+
}
|
1826
|
+
}
|
1827
|
+
function anyIncludes(start, end, startIncluded, endIncluded, conversion = (v) => v) {
|
1828
|
+
let tests = [];
|
1829
|
+
if (start === null && end === null) {
|
1830
|
+
return () => null;
|
1831
|
+
}
|
1832
|
+
if (start !== null && end !== null) {
|
1833
|
+
if (start > end) {
|
1834
|
+
tests = [
|
1835
|
+
includesStart(end, endIncluded),
|
1836
|
+
includesEnd(start, startIncluded)
|
1837
|
+
];
|
1838
|
+
}
|
1839
|
+
else {
|
1840
|
+
tests = [
|
1841
|
+
includesStart(start, startIncluded),
|
1842
|
+
includesEnd(end, endIncluded)
|
1843
|
+
];
|
1844
|
+
}
|
1845
|
+
}
|
1846
|
+
else if (end !== null) {
|
1847
|
+
tests = [
|
1848
|
+
includesEnd(end, endIncluded)
|
1849
|
+
];
|
1850
|
+
}
|
1851
|
+
else if (start !== null) {
|
1852
|
+
tests = [
|
1853
|
+
includesStart(start, startIncluded)
|
1854
|
+
];
|
1855
|
+
}
|
1856
|
+
return (value) => value === null ? null : tests.every(t => t(conversion(value)));
|
1857
|
+
}
|
1858
|
+
function createStringRange(start, end, startIncluded = true, endIncluded = true) {
|
1859
|
+
if (start !== null && !chars.includes(start)) {
|
1860
|
+
throw new Error('illegal range start: ' + start);
|
1861
|
+
}
|
1862
|
+
if (end !== null && !chars.includes(end)) {
|
1863
|
+
throw new Error('illegal range end: ' + end);
|
1864
|
+
}
|
1865
|
+
let values;
|
1866
|
+
if (start !== null && end !== null) {
|
1867
|
+
let startIdx = chars.indexOf(start);
|
1868
|
+
let endIdx = chars.indexOf(end);
|
1869
|
+
const direction = startIdx > endIdx ? -1 : 1;
|
1870
|
+
if (startIncluded === false) {
|
1871
|
+
startIdx += direction;
|
1872
|
+
}
|
1873
|
+
if (endIncluded === false) {
|
1874
|
+
endIdx -= direction;
|
1875
|
+
}
|
1876
|
+
values = chars.slice(startIdx, endIdx + 1);
|
1877
|
+
}
|
1878
|
+
const map = values ? valuesMap(values) : noopMap();
|
1879
|
+
const includes = values ? valuesIncludes(values) : anyIncludes(start, end, startIncluded, endIncluded);
|
1880
|
+
return new Range({
|
1881
|
+
start,
|
1882
|
+
end,
|
1883
|
+
'start included': startIncluded,
|
1884
|
+
'end included': endIncluded,
|
1885
|
+
map,
|
1886
|
+
includes
|
1887
|
+
});
|
1888
|
+
}
|
1889
|
+
function createNumberRange(start, end, startIncluded, endIncluded) {
|
1890
|
+
const map = start !== null && end !== null ? numberMap(start, end, startIncluded, endIncluded) : noopMap();
|
1891
|
+
const includes = anyIncludes(start, end, startIncluded, endIncluded);
|
1892
|
+
return new Range({
|
1893
|
+
start,
|
1894
|
+
end,
|
1895
|
+
'start included': startIncluded,
|
1896
|
+
'end included': endIncluded,
|
1897
|
+
map,
|
1898
|
+
includes
|
1899
|
+
});
|
1900
|
+
}
|
1901
|
+
/**
|
1902
|
+
* @param {Duration} start
|
1903
|
+
* @param {Duration} end
|
1904
|
+
* @param {boolean} startIncluded
|
1905
|
+
* @param {boolean} endIncluded
|
1906
|
+
*/
|
1907
|
+
function createDurationRange(start, end, startIncluded, endIncluded) {
|
1908
|
+
const toMillis = (d) => d ? Duration.fromDurationLike(d).toMillis() : null;
|
1909
|
+
const map = noopMap();
|
1910
|
+
const includes = anyIncludes(toMillis(start), toMillis(end), startIncluded, endIncluded, toMillis);
|
1911
|
+
return new Range({
|
1912
|
+
start,
|
1913
|
+
end,
|
1914
|
+
'start included': startIncluded,
|
1915
|
+
'end included': endIncluded,
|
1916
|
+
map,
|
1917
|
+
includes
|
1918
|
+
});
|
1919
|
+
}
|
1920
|
+
function createDateTimeRange(start, end, startIncluded, endIncluded) {
|
1921
|
+
const map = noopMap();
|
1922
|
+
const includes = anyIncludes(start, end, startIncluded, endIncluded);
|
1923
|
+
return new Range({
|
1924
|
+
start,
|
1925
|
+
end,
|
1926
|
+
'start included': startIncluded,
|
1927
|
+
'end included': endIncluded,
|
1928
|
+
map,
|
1929
|
+
includes
|
1930
|
+
});
|
1931
|
+
}
|
1932
|
+
function coalecenseTypes(a, b) {
|
1933
|
+
if (!b) {
|
1934
|
+
return a.type;
|
1935
|
+
}
|
1936
|
+
if (a.type === b.type) {
|
1937
|
+
return a.type;
|
1938
|
+
}
|
1939
|
+
return 'any';
|
1940
|
+
}
|
1941
|
+
function tag(fn, type) {
|
1942
|
+
return Object.assign(fn, {
|
1943
|
+
type,
|
1944
|
+
toString() {
|
1945
|
+
return `TaggedFunction[${type}] ${Function.prototype.toString.call(fn)}`;
|
1946
|
+
}
|
1947
|
+
});
|
1948
|
+
}
|
1949
|
+
function combineResult(result, match) {
|
1950
|
+
if (!result) {
|
1951
|
+
return match;
|
1952
|
+
}
|
1953
|
+
return result;
|
1954
|
+
}
|
1955
|
+
function isTruthy(obj) {
|
1956
|
+
return obj !== false && obj !== null;
|
1957
|
+
}
|
1958
|
+
function Test(type) {
|
1959
|
+
return `Test<${type}>`;
|
1960
|
+
}
|
1961
|
+
/**
|
1962
|
+
* @param {Function} fn
|
1963
|
+
* @param {string[]} [parameterNames]
|
1964
|
+
*
|
1965
|
+
* @return {FunctionWrapper}
|
1966
|
+
*/
|
1967
|
+
function wrapFunction(fn, parameterNames = null) {
|
1968
|
+
if (!fn) {
|
1969
|
+
return null;
|
1970
|
+
}
|
1971
|
+
if (fn instanceof FunctionWrapper) {
|
1972
|
+
return fn;
|
1973
|
+
}
|
1974
|
+
if (fn instanceof Range) {
|
1975
|
+
return new FunctionWrapper((value) => fn.includes(value), ['value']);
|
1976
|
+
}
|
1977
|
+
if (typeof fn !== 'function') {
|
1978
|
+
return null;
|
1979
|
+
}
|
1980
|
+
return new FunctionWrapper(fn, parameterNames || parseParameterNames(fn));
|
1981
|
+
}
|
1982
|
+
function parseString(str) {
|
1983
|
+
if (str.startsWith('"')) {
|
1984
|
+
str = str.slice(1);
|
1985
|
+
}
|
1986
|
+
if (str.endsWith('"')) {
|
1987
|
+
str = str.slice(0, -1);
|
1988
|
+
}
|
1989
|
+
return str.replace(/(\\")|(\\\\)|(\\n)|(\\r)|(\\t)|(\\u[a-fA-F0-9]{5,6})|((?:\\u[a-fA-F0-9]{1,4})+)/ig, function (substring, ...groups) {
|
1990
|
+
const [quotes, backslash, newline, carriageReturn, tab, codePoint, charCodes] = groups;
|
1991
|
+
if (quotes) {
|
1992
|
+
return '"';
|
1993
|
+
}
|
1994
|
+
if (newline) {
|
1995
|
+
return '\n';
|
1996
|
+
}
|
1997
|
+
if (carriageReturn) {
|
1998
|
+
return '\r';
|
1999
|
+
}
|
2000
|
+
if (tab) {
|
2001
|
+
return '\t';
|
2002
|
+
}
|
2003
|
+
if (backslash) {
|
2004
|
+
return '\\';
|
2005
|
+
}
|
2006
|
+
const escapePattern = /\\u([a-fA-F0-9]+)/ig;
|
2007
|
+
if (codePoint) {
|
2008
|
+
const codePointMatch = escapePattern.exec(codePoint);
|
2009
|
+
return String.fromCodePoint(parseInt(codePointMatch[1], 16));
|
2010
|
+
}
|
2011
|
+
if (charCodes) {
|
2012
|
+
const chars = [];
|
2013
|
+
let charCodeMatch;
|
2014
|
+
while ((charCodeMatch = escapePattern.exec(substring)) !== null) {
|
2015
|
+
chars.push(parseInt(charCodeMatch[1], 16));
|
2016
|
+
}
|
2017
|
+
return String.fromCharCode(...chars);
|
2018
|
+
}
|
2019
|
+
throw new Error('illegal match');
|
2020
|
+
});
|
2021
|
+
}
|
2022
|
+
function lintError(nodeRef) {
|
2023
|
+
const node = nodeRef.node;
|
2024
|
+
const parent = node.parent;
|
2025
|
+
if (node.from !== node.to) {
|
2026
|
+
return {
|
2027
|
+
from: node.from,
|
2028
|
+
to: node.to,
|
2029
|
+
message: `Unrecognized token in <${parent.name}>`
|
2030
|
+
};
|
2031
|
+
}
|
2032
|
+
const next = findNext(node);
|
2033
|
+
if (next) {
|
2034
|
+
return {
|
2035
|
+
from: node.from,
|
2036
|
+
to: next.to,
|
2037
|
+
message: `Unrecognized token <${next.name}> in <${parent.name}>`
|
2038
|
+
};
|
2039
|
+
}
|
2040
|
+
else {
|
2041
|
+
const unfinished = parent.enterUnfinishedNodesBefore(nodeRef.to);
|
2042
|
+
return {
|
2043
|
+
from: node.from,
|
2044
|
+
to: node.to,
|
2045
|
+
message: `Incomplete <${(unfinished || parent).name}>`
|
2046
|
+
};
|
2047
|
+
}
|
2048
|
+
}
|
2049
|
+
function findNext(nodeRef) {
|
2050
|
+
const node = nodeRef.node;
|
2051
|
+
let next, parent = node;
|
2052
|
+
do {
|
2053
|
+
next = parent.nextSibling;
|
2054
|
+
if (next) {
|
2055
|
+
return next;
|
2056
|
+
}
|
2057
|
+
parent = parent.parent;
|
2058
|
+
} while (parent);
|
2059
|
+
return null;
|
2060
|
+
}
|
2061
|
+
|
2062
|
+
export { SyntaxError, date, duration, evaluate, lintError, parseExpression, parseUnaryTests, unaryTest };
|
2063
|
+
//# sourceMappingURL=index.esm.js.map
|