@malloydata/malloy-filter 0.0.237-dev250222012247 → 0.0.237-dev250222034547

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 (65) hide show
  1. package/dist/a_simple_parser.js +12 -9
  2. package/dist/a_simple_parser.js.map +1 -1
  3. package/dist/base_parser.d.ts +0 -2
  4. package/dist/base_parser.js.map +1 -1
  5. package/dist/boolean_parser.d.ts +2 -2
  6. package/dist/boolean_parser.js.map +1 -1
  7. package/dist/boolean_serializer.d.ts +4 -4
  8. package/dist/boolean_serializer.js +3 -3
  9. package/dist/boolean_serializer.js.map +1 -1
  10. package/dist/clause_types.d.ts +15 -41
  11. package/dist/date_parser.d.ts +13 -11
  12. package/dist/date_parser.js +187 -130
  13. package/dist/date_parser.js.map +1 -1
  14. package/dist/date_serializer.d.ts +6 -5
  15. package/dist/date_serializer.js +53 -71
  16. package/dist/date_serializer.js.map +1 -1
  17. package/dist/date_types.d.ts +69 -0
  18. package/dist/{filter_types.js → date_types.js} +1 -1
  19. package/dist/date_types.js.map +1 -0
  20. package/dist/generate_samples.js +154 -85
  21. package/dist/generate_samples.js.map +1 -1
  22. package/dist/number_parser.d.ts +2 -2
  23. package/dist/number_parser.js.map +1 -1
  24. package/dist/number_serializer.d.ts +4 -4
  25. package/dist/number_serializer.js +3 -3
  26. package/dist/number_serializer.js.map +1 -1
  27. package/dist/string_parser.d.ts +2 -2
  28. package/dist/string_parser.js.map +1 -1
  29. package/dist/string_serializer.d.ts +5 -5
  30. package/dist/string_serializer.js +5 -5
  31. package/dist/string_serializer.js.map +1 -1
  32. package/package.json +1 -2
  33. package/src/a_simple_parser.ts +12 -9
  34. package/src/base_parser.ts +0 -3
  35. package/src/boolean_parser.ts +7 -3
  36. package/src/boolean_serializer.ts +5 -6
  37. package/src/clause_types.ts +15 -97
  38. package/src/date_parser.ts +224 -191
  39. package/src/date_serializer.ts +55 -87
  40. package/src/date_types.ts +149 -0
  41. package/src/generate_samples.ts +170 -107
  42. package/src/number_parser.ts +5 -5
  43. package/src/number_serializer.ts +5 -6
  44. package/src/string_parser.ts +14 -9
  45. package/src/string_serializer.ts +8 -9
  46. package/tsconfig.json +1 -6
  47. package/dist/a_simple_serializer.d.ts +0 -1
  48. package/dist/a_simple_serializer.js +0 -31
  49. package/dist/a_simple_serializer.js.map +0 -1
  50. package/dist/base_serializer.d.ts +0 -6
  51. package/dist/base_serializer.js +0 -11
  52. package/dist/base_serializer.js.map +0 -1
  53. package/dist/filter_parser.d.ts +0 -12
  54. package/dist/filter_parser.js +0 -66
  55. package/dist/filter_parser.js.map +0 -1
  56. package/dist/filter_serializer.d.ts +0 -13
  57. package/dist/filter_serializer.js +0 -43
  58. package/dist/filter_serializer.js.map +0 -1
  59. package/dist/filter_types.d.ts +0 -10
  60. package/dist/filter_types.js.map +0 -1
  61. package/src/a_simple_serializer.ts +0 -40
  62. package/src/base_serializer.ts +0 -9
  63. package/src/filter_parser.ts +0 -68
  64. package/src/filter_serializer.ts +0 -49
  65. package/src/filter_types.ts +0 -12
@@ -1,26 +1,29 @@
1
1
  import {SpecialToken, Tokenizer, TokenizerParams} from './tokenizer';
2
2
  import {
3
- DateRange,
4
- DatePrefix,
5
- DateMoment,
6
- DateMomentNow,
7
- DateMomentInterval,
8
- DateMomentNumberInterval,
9
- DateMomentNumberUnit,
10
- DateMomentNumber,
11
3
  DateTimeUnit,
12
4
  DateWeekday,
5
+ DateMomentName,
6
+ NamedMoment,
13
7
  DateMomentIntervalOperator,
14
- DateMomentNumberIntervalOperator,
15
- DateMomentNumberUnitOperator,
16
- DateMomentNumberOperator,
17
- DateMomentNowOperator,
8
+ IntervalMoment,
9
+ DateMomentOffsetFromNowDirection,
10
+ OffsetMoment,
11
+ DateMomentSpanFromNowDirection,
12
+ SpanMoment,
13
+ AbsoluteMoment,
14
+ DateMoment,
15
+ DateBetweenClause,
16
+ Duration,
17
+ DateForClause,
18
+ DateDurationClause,
18
19
  DateClause,
19
- Clause,
20
- } from './clause_types';
20
+ DateParserResponse,
21
+ } from './date_types';
21
22
  import {BaseParser} from './base_parser';
22
23
  import {Token} from './token_types';
23
- import {FilterParserResponse, FilterError} from './filter_types';
24
+ import {FilterError} from './clause_types';
25
+
26
+ type DatePrefix = 'BEFORE' | 'AFTER';
24
27
 
25
28
  export class DateParser extends BaseParser {
26
29
  private static readonly yearRegex: RegExp = /[%_]/;
@@ -62,7 +65,7 @@ export class DateParser extends BaseParser {
62
65
  {type: 'FROM', value: 'from', ignoreCase: true},
63
66
  {type: 'FOR', value: 'for', ignoreCase: true},
64
67
  {type: 'TO', value: 'to', ignoreCase: true},
65
- {type: 'YEARORNUMBER', value: /^\d\d\d\d$/}, // Years are ambiguous, and require special handling.
68
+ {type: 'YEAR', value: /^\d\d\d\d$/}, // Years are ambiguous, and require special handling.
66
69
  {type: 'NUMBER', value: /^[\d.]+/, ignoreCase: true},
67
70
  ];
68
71
  const params: TokenizerParams = {
@@ -83,44 +86,28 @@ export class DateParser extends BaseParser {
83
86
  this.index = 0;
84
87
  while (this.index < this.tokens.length) {
85
88
  if (
86
- this.matchAndMerge('LAST|UNITOFTIME', this.tokens, output) ||
87
- this.matchAndMerge('LAST|DAYOFWEEK', this.tokens, output) ||
88
- this.matchAndMerge('LAST|NUMBER|UNITOFTIME', this.tokens, output) ||
89
- this.matchAndMerge(
90
- 'LAST|YEARORNUMBER|UNITOFTIME',
91
- this.tokens,
92
- output
93
- ) ||
94
- this.matchAndMerge('THIS|UNITOFTIME', this.tokens, output) ||
95
- this.matchAndMerge('NEXT|UNITOFTIME', this.tokens, output) ||
96
- this.matchAndMerge('NEXT|DAYOFWEEK', this.tokens, output) ||
97
- this.matchAndMerge('NEXT|NUMBER|UNITOFTIME', this.tokens, output) ||
98
- this.matchAndMerge(
99
- 'NEXT|YEARORNUMBER|UNITOFTIME',
100
- this.tokens,
101
- output
102
- ) ||
103
- this.matchAndMerge('NUMBER|UNITOFTIME|AGO', this.tokens, output) ||
104
- this.matchAndMerge(
105
- 'YEARORNUMBER|UNITOFTIME|AGO',
106
- this.tokens,
107
- output
108
- ) ||
109
- this.matchAndMerge('NUMBER|UNITOFTIME|FROM|NOW', this.tokens, output) ||
110
- this.matchAndMerge(
111
- 'YEARORNUMBER|UNITOFTIME|FROM|NOW',
112
- this.tokens,
113
- output
114
- ) ||
115
- this.matchAndMerge('NUMBER|UNITOFTIME', this.tokens, output) ||
116
- this.matchAndMerge('YEARORNUMBER|UNITOFTIME', this.tokens, output) ||
117
- this.matchAndMerge('DATE|TIME', this.tokens, output) ||
118
- this.matchAndMerge('TODAY', this.tokens, output) ||
119
- this.matchAndMerge('YESTERDAY', this.tokens, output) ||
120
- this.matchAndMerge('TOMORROW', this.tokens, output) ||
121
- this.matchAndMerge('DATE', this.tokens, output) ||
122
- this.matchAndMerge('YEARORNUMBER', this.tokens, output) ||
123
- this.matchAndMerge('NOW', this.tokens, output)
89
+ this.matchAndMerge('LAST|UNITOFTIME', output) ||
90
+ this.matchAndMerge('LAST|DAYOFWEEK', output) ||
91
+ this.matchAndMerge('LAST|NUMBER|UNITOFTIME', output) ||
92
+ this.matchAndMerge('LAST|YEAR|UNITOFTIME', output) ||
93
+ this.matchAndMerge('THIS|UNITOFTIME', output) ||
94
+ this.matchAndMerge('NEXT|UNITOFTIME', output) ||
95
+ this.matchAndMerge('NEXT|DAYOFWEEK', output) ||
96
+ this.matchAndMerge('NEXT|NUMBER|UNITOFTIME', output) ||
97
+ this.matchAndMerge('NEXT|YEAR|UNITOFTIME', output) ||
98
+ this.matchAndMerge('NUMBER|UNITOFTIME|AGO', output) ||
99
+ this.matchAndMerge('YEAR|UNITOFTIME|AGO', output) ||
100
+ this.matchAndMerge('NUMBER|UNITOFTIME|FROM|NOW', output) ||
101
+ this.matchAndMerge('YEAR|UNITOFTIME|FROM|NOW', output) ||
102
+ this.matchAndMerge('NUMBER|UNITOFTIME', output) ||
103
+ this.matchAndMerge('YEAR|UNITOFTIME', output) ||
104
+ this.matchAndMerge('DATE|TIME', output) ||
105
+ this.matchAndMerge('TODAY', output) ||
106
+ this.matchAndMerge('YESTERDAY', output) ||
107
+ this.matchAndMerge('TOMORROW', output) ||
108
+ this.matchAndMerge('DATE', output) ||
109
+ this.matchAndMerge('YEAR', output) ||
110
+ this.matchAndMerge('NOW', output)
124
111
  ) {
125
112
  continue;
126
113
  } else {
@@ -131,16 +118,12 @@ export class DateParser extends BaseParser {
131
118
  return output;
132
119
  }
133
120
 
134
- private matchAndMerge(
135
- types: string,
136
- tokens: Token[],
137
- output: Token[]
138
- ): boolean {
121
+ private matchAndMerge(types: string, output: Token[]): boolean {
139
122
  const idx = this.index;
140
- const matchedTokens = Tokenizer.matchTypes(types, tokens, idx);
123
+ const matchedTokens = Tokenizer.matchTypes(types, this.tokens, idx);
141
124
  if (matchedTokens) {
142
125
  output.push({
143
- type: 'MOMENT:' + types,
126
+ type: 'MERGE:' + types,
144
127
  value: '',
145
128
  values: matchedTokens,
146
129
  startIndex: matchedTokens[0].startIndex,
@@ -152,7 +135,7 @@ export class DateParser extends BaseParser {
152
135
  return false;
153
136
  }
154
137
 
155
- public parse(): FilterParserResponse {
138
+ public parse(): DateParserResponse {
156
139
  this.tokenize();
157
140
  let prefix: DatePrefix | undefined = undefined;
158
141
  const clauses: DateClause[] = [];
@@ -181,7 +164,7 @@ export class DateParser extends BaseParser {
181
164
  });
182
165
  this.index++;
183
166
  }
184
- } else if (this.handleMoment(prefix, clauses)) {
167
+ } else if (this.handleMerged(prefix, clauses)) {
185
168
  prefix = undefined;
186
169
  } else {
187
170
  errors.push({
@@ -196,151 +179,187 @@ export class DateParser extends BaseParser {
196
179
  return {clauses, errors};
197
180
  }
198
181
 
199
- // LAST|DAYOFWEEK
200
- private static createMomentInterval(
182
+ private static createMomentClause(
201
183
  prefix: DatePrefix | undefined,
202
- tokens: Token[]
203
- ): DateMomentInterval {
204
- const operator: DateMomentIntervalOperator = tokens[0]
205
- .type as DateMomentIntervalOperator;
206
- const unit: DateTimeUnit | DateWeekday = tokens[1].value as
207
- | DateTimeUnit
208
- | DateWeekday;
209
- const moment: DateMomentInterval = {operator, unit};
210
- if (prefix) {
211
- moment.prefix = prefix;
184
+ moment: DateMoment
185
+ ): DateClause {
186
+ if (!prefix) {
187
+ return {operator: 'ON', moment}; // DateOnClause
188
+ } else if (prefix === 'BEFORE') {
189
+ return {operator: 'BEFORE', moment}; // DateBeforeClause
190
+ } else {
191
+ return {operator: 'AFTER', moment}; // DateAfterClause
212
192
  }
213
- return moment;
214
193
  }
215
194
 
216
- // LAST|NUMBER|UNITOFTIME
217
- private static createMomentNumberInterval(
195
+ // (BEFORE|AFTER) LAST|DAYOFWEEK
196
+ private static createIntervalMoment(
218
197
  prefix: DatePrefix | undefined,
219
198
  tokens: Token[]
220
- ): DateMomentNumberInterval {
221
- const type0 = tokens[0].type;
222
- const operator: DateMomentNumberIntervalOperator =
223
- type0 === 'LAST' ? 'LASTN' : 'NEXTN';
224
- const value: string = tokens[1].value;
225
- const unit: DateTimeUnit = tokens[2].value as DateTimeUnit;
226
- const moment: DateMomentNumberInterval = {operator, value, unit};
227
- if (prefix) {
228
- moment.prefix = prefix;
229
- }
230
- return moment;
199
+ ): DateClause {
200
+ const kind: DateMomentIntervalOperator = tokens[0]
201
+ .type as DateMomentIntervalOperator;
202
+ const unit: DateTimeUnit | DateWeekday = tokens[1].value as
203
+ | DateTimeUnit
204
+ | DateWeekday;
205
+ const moment: IntervalMoment = {type: 'INTERVAL', kind, unit};
206
+ return DateParser.createMomentClause(prefix, moment);
231
207
  }
232
208
 
233
209
  // NUMBER|UNITOFTIME|AGO
234
- private static createMomentNumberIntervalAgo(
210
+ // NUMBER|UNITOFTIME|FROM|NOW
211
+ private static createOffsetMoment(
235
212
  prefix: DatePrefix | undefined,
236
213
  tokens: Token[]
237
- ): DateMomentNumberInterval {
238
- const operator: DateMomentNumberIntervalOperator = 'AGO';
239
- const value: string = tokens[0].value;
214
+ ): DateClause | undefined {
215
+ const amount = Number(tokens[0].value);
240
216
  const unit: DateTimeUnit = tokens[1].value as DateTimeUnit;
241
- const moment: DateMomentNumberInterval = {operator, value, unit};
242
- if (prefix) {
243
- moment.prefix = prefix;
217
+ const direction: DateMomentOffsetFromNowDirection =
218
+ tokens[2].type === 'AGO' ? 'AGO' : 'FROMNOW';
219
+ if (!DateParser.isValidNumber(amount)) {
220
+ return undefined;
244
221
  }
245
- return moment;
222
+ const moment: OffsetMoment = {
223
+ type: 'OFFSET_FROM_NOW',
224
+ direction,
225
+ amount,
226
+ unit,
227
+ };
228
+ return DateParser.createMomentClause(prefix, moment);
246
229
  }
247
230
 
248
- // NUMBER|UNITOFTIME|FROM|NOW
249
- private static createMomentNumberIntervalFromNow(
231
+ // (LAST|NEXT)|NUMBER|UNITOFTIME
232
+ private static createSpanMoment(
250
233
  prefix: DatePrefix | undefined,
251
234
  tokens: Token[]
252
- ): DateMomentNumberInterval | undefined {
253
- const operator: DateMomentNumberIntervalOperator = 'FROMNOW';
254
- const value: string = tokens[0].value;
235
+ ): DateClause | undefined {
236
+ const amount = Number(tokens[0].value);
255
237
  const unit: DateTimeUnit = tokens[1].value as DateTimeUnit;
256
- const moment: DateMomentNumberInterval = {operator, value, unit};
257
- if (prefix) {
258
- moment.prefix = prefix;
238
+ const direction: DateMomentSpanFromNowDirection =
239
+ tokens[2].type === 'LAST' ? 'LAST' : 'NEXT';
240
+ if (!DateParser.isValidNumber(amount)) {
241
+ return undefined;
259
242
  }
260
- return moment;
243
+ const moment: SpanMoment = {type: 'SPAN_FROM_NOW', direction, amount, unit};
244
+ return DateParser.createMomentClause(prefix, moment);
261
245
  }
262
246
 
263
- // NUMBER|UNITOFTIME
264
- private static createMomentNumberUnit(
247
+ // (NUMBER|YEAR)|UNITOFTIME
248
+ private static createDateDuration(
265
249
  prefix: DatePrefix | undefined,
266
250
  tokens: Token[]
267
- ): DateMomentNumberUnit {
268
- const operator: DateMomentNumberUnitOperator = 'TIMEBLOCK';
269
- const value: string = tokens[0].value;
270
- const unit: DateTimeUnit = tokens[1].value as DateTimeUnit;
271
- const moment: DateMomentNumberUnit = {operator, value, unit};
251
+ ): DateClause | undefined {
272
252
  if (prefix) {
273
- moment.prefix = prefix;
253
+ return undefined; // before 7 hours is ambiguous, not allowed.
254
+ }
255
+ const operator = 'DURATION';
256
+ const amount = Number(tokens[0].value);
257
+ const unit: DateTimeUnit = tokens[1].value as DateTimeUnit;
258
+ if (!DateParser.isValidNumber(amount)) {
259
+ return undefined;
274
260
  }
275
- return moment;
261
+ const clause: DateDurationClause = {operator, duration: {amount, unit}};
262
+ return clause;
276
263
  }
277
264
 
278
- // DATE DATE|TIME
279
- private static createMomentNumber(
265
+ // (BEFORE|AFTER) DATE DATE|TIME
266
+ private static createAbsoluteMoment(
280
267
  prefix: DatePrefix | undefined,
281
268
  tokens: Token[]
282
- ): DateMomentNumber {
283
- const operator: DateMomentNumberOperator =
284
- tokens.length === 2 ? 'DATETIME' : 'DATE';
285
- const moment: DateMomentNumber = {operator, date: tokens[0].value};
269
+ ): DateClause {
270
+ let unit: DateTimeUnit = 'YEAR';
271
+ let date = tokens[0].value;
286
272
  if (tokens.length === 2) {
287
- moment.time = tokens[1].value;
273
+ const timeStr = tokens[1].value;
274
+ date += ' ' + timeStr;
275
+ if (timeStr.length > 5) unit = 'SECOND';
276
+ else unit = 'MINUTE';
277
+ } else if (date.length > 7) {
278
+ unit = 'DAY';
279
+ } else if (date.length > 4) {
280
+ unit = 'MONTH';
288
281
  }
289
- if (prefix) {
290
- moment.prefix = prefix;
291
- }
292
- return moment;
282
+ const moment: AbsoluteMoment = {type: 'ABSOLUTE', date, unit};
283
+ return DateParser.createMomentClause(prefix, moment);
293
284
  }
294
285
 
295
286
  // NOW YESTERDAY TODAY TOMORROW
296
- private static createMomentNow(
287
+ private static createNamedMoment(
297
288
  prefix: DatePrefix | undefined,
298
289
  tokens: Token[]
299
- ): DateMomentNow {
300
- const operator: DateMomentNowOperator = tokens[0]
301
- .type as DateMomentNowOperator;
302
- const moment: DateMomentNow = {operator};
303
- if (prefix) {
304
- moment.prefix = prefix;
290
+ ): DateClause {
291
+ let momentName: DateMomentName = 'NOW';
292
+ switch (tokens[0].type) {
293
+ case 'TODAY':
294
+ momentName = 'TODAY';
295
+ break;
296
+ case 'YESTERDAY':
297
+ momentName = 'YESTERDAY';
298
+ break;
299
+ case 'TOMORROW':
300
+ momentName = 'TOMORROW';
301
+ break;
305
302
  }
306
- return moment;
303
+ const moment: NamedMoment = {type: 'NAMED', name: momentName};
304
+ return DateParser.createMomentClause(prefix, moment);
307
305
  }
308
306
 
309
- private static createMomentFromToken(
307
+ private static isValidNumber(value: number): boolean {
308
+ return Number.isNaN(value) === false;
309
+ }
310
+
311
+ private static createDurationFromMerged(token: Token): Duration | undefined {
312
+ if (!token.values || token.values.length !== 2) {
313
+ return undefined;
314
+ }
315
+ if (
316
+ token.type === 'MERGE:NUMBER|UNITOFTIME' ||
317
+ token.type === 'MERGE:YEAR|UNITOFTIME'
318
+ ) {
319
+ const value = Number(token.values[0].value);
320
+ if (!DateParser.isValidNumber(value)) {
321
+ return undefined;
322
+ }
323
+ const unit = token.values[1].value as DateTimeUnit;
324
+ return {amount: value, unit: unit};
325
+ }
326
+ return undefined;
327
+ }
328
+
329
+ private static createClauseFromMerged(
310
330
  prefix: DatePrefix | undefined,
311
331
  token: Token
312
- ): DateMoment | undefined {
332
+ ): DateClause | undefined {
313
333
  const tokens: Token[] = token.values || [];
314
334
  switch (token.type) {
315
- case 'MOMENT:LAST|UNITOFTIME':
316
- case 'MOMENT:LAST|DAYOFWEEK':
317
- case 'MOMENT:THIS|UNITOFTIME':
318
- case 'MOMENT:NEXT|UNITOFTIME':
319
- case 'MOMENT:NEXT|DAYOFWEEK':
320
- return this.createMomentInterval(prefix, tokens);
321
- case 'MOMENT:LAST|NUMBER|UNITOFTIME':
322
- case 'MOMENT:LAST|YEARORNUMBER|UNITOFTIME':
323
- case 'MOMENT:NEXT|NUMBER|UNITOFTIME':
324
- case 'MOMENT:NEXT|YEARORNUMBER|UNITOFTIME':
325
- return this.createMomentNumberInterval(prefix, tokens);
326
- case 'MOMENT:NUMBER|UNITOFTIME|AGO':
327
- case 'MOMENT:YEARORNUMBER|UNITOFTIME|AGO':
328
- return this.createMomentNumberIntervalAgo(prefix, tokens);
329
- case 'MOMENT:NUMBER|UNITOFTIME|FROM|NOW':
330
- case 'MOMENT:YEARORNUMBER|UNITOFTIME|FROM|NOW':
331
- return this.createMomentNumberIntervalFromNow(prefix, tokens);
332
- case 'MOMENT:NUMBER|UNITOFTIME':
333
- case 'MOMENT:YEARORNUMBER|UNITOFTIME':
334
- return this.createMomentNumberUnit(prefix, tokens);
335
- case 'MOMENT:DATE|TIME':
336
- case 'MOMENT:DATE':
337
- case 'MOMENT:YEARORNUMBER':
338
- return this.createMomentNumber(prefix, tokens);
339
- case 'MOMENT:NOW':
340
- case 'MOMENT:TODAY':
341
- case 'MOMENT:YESTERDAY':
342
- case 'MOMENT:TOMORROW':
343
- return this.createMomentNow(prefix, tokens);
335
+ case 'MERGE:LAST|UNITOFTIME':
336
+ case 'MERGE:LAST|DAYOFWEEK':
337
+ case 'MERGE:THIS|UNITOFTIME':
338
+ case 'MERGE:NEXT|UNITOFTIME':
339
+ case 'MERGE:NEXT|DAYOFWEEK':
340
+ return this.createIntervalMoment(prefix, tokens);
341
+ case 'MERGE:LAST|NUMBER|UNITOFTIME':
342
+ case 'MERGE:LAST|YEAR|UNITOFTIME':
343
+ case 'MERGE:NEXT|NUMBER|UNITOFTIME':
344
+ case 'MERGE:NEXT|YEAR|UNITOFTIME':
345
+ return this.createSpanMoment(prefix, tokens);
346
+ case 'MERGE:NUMBER|UNITOFTIME|AGO':
347
+ case 'MERGE:YEAR|UNITOFTIME|AGO':
348
+ case 'MERGE:NUMBER|UNITOFTIME|FROM|NOW':
349
+ case 'MERGE:YEAR|UNITOFTIME|FROM|NOW':
350
+ return this.createOffsetMoment(prefix, tokens);
351
+ case 'MERGE:NUMBER|UNITOFTIME':
352
+ case 'MERGE:YEAR|UNITOFTIME':
353
+ return this.createDateDuration(prefix, tokens);
354
+ case 'MERGE:DATE|TIME':
355
+ case 'MERGE:DATE':
356
+ case 'MERGE:YEAR':
357
+ return this.createAbsoluteMoment(prefix, tokens);
358
+ case 'MERGE:NOW':
359
+ case 'MERGE:TODAY':
360
+ case 'MERGE:YESTERDAY':
361
+ case 'MERGE:TOMORROW':
362
+ return this.createNamedMoment(prefix, tokens);
344
363
  default:
345
364
  return undefined;
346
365
  }
@@ -359,48 +378,62 @@ export class DateParser extends BaseParser {
359
378
  : this.tokens[position].type.startsWith(value);
360
379
  }
361
380
 
362
- private handleRange(clauses: Clause[]): boolean {
381
+ private handleRange(clauses: DateClause[]): boolean {
363
382
  if (
364
- this.isMatchingToken(this.index, 'MOMENT', false) &&
383
+ this.isMatchingToken(this.index, 'MERGE', false) &&
365
384
  (this.isMatchingToken(this.index + 1, 'TO', true) ||
366
385
  this.isMatchingToken(this.index + 1, 'FOR', true)) &&
367
- this.isMatchingToken(this.index + 2, 'MOMENT', false)
386
+ this.isMatchingToken(this.index + 2, 'MERGE', false)
368
387
  ) {
369
- const startMoment: DateMoment | undefined =
370
- DateParser.createMomentFromToken(undefined, this.tokens[this.index]);
371
- const endMoment: DateMoment | undefined =
372
- DateParser.createMomentFromToken(
373
- undefined,
374
- this.tokens[this.index + 2]
375
- );
376
- const operator: 'TO' | 'FOR' = this.tokens[this.index + 1].type as
377
- | 'TO'
378
- | 'FOR';
388
+ const startToken = this.tokens[this.index];
389
+ const operator = this.tokens[this.index + 1].type; // TO | FOR
390
+ const endToken = this.tokens[this.index + 2];
391
+ const startClause = DateParser.createClauseFromMerged(
392
+ undefined,
393
+ startToken
394
+ );
379
395
  this.index += 3;
380
- if (startMoment === undefined || endMoment === undefined) {
396
+ if (startClause === undefined || !('moment' in startClause)) {
381
397
  return false;
382
398
  }
383
- const dateRange: DateRange = {
384
- start: startMoment,
385
- operator,
386
- end: endMoment,
387
- };
388
- clauses.push(dateRange);
399
+ if (operator === 'TO') {
400
+ const endClause = DateParser.createClauseFromMerged(
401
+ undefined,
402
+ endToken
403
+ );
404
+ if (endClause === undefined || !('moment' in endClause)) {
405
+ return false;
406
+ }
407
+ const clause: DateBetweenClause = {
408
+ operator: 'TO_RANGE',
409
+ from: startClause.moment,
410
+ to: endClause.moment,
411
+ };
412
+ clauses.push(clause);
413
+ } else {
414
+ const endDuration = DateParser.createDurationFromMerged(endToken);
415
+ if (endDuration === undefined) {
416
+ return false;
417
+ }
418
+ const clause: DateForClause = {
419
+ operator: 'FOR_RANGE',
420
+ from: startClause.moment,
421
+ duration: endDuration,
422
+ };
423
+ clauses.push(clause);
424
+ }
389
425
  return true;
390
426
  }
391
427
  return false;
392
428
  }
393
429
 
394
- private handleMoment(
430
+ private handleMerged(
395
431
  prefix: DatePrefix | undefined,
396
432
  clauses: DateClause[]
397
433
  ): boolean {
398
434
  const token: Token = this.getNext();
399
- if (token.type.startsWith('MOMENT')) {
400
- const clause: DateMoment | undefined = DateParser.createMomentFromToken(
401
- prefix,
402
- token
403
- );
435
+ if (token.type.startsWith('MERGE')) {
436
+ const clause = DateParser.createClauseFromMerged(prefix, token);
404
437
  this.index++;
405
438
  if (clause === undefined) {
406
439
  return false;