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