@malloydata/malloy-filter 0.0.237-dev250222025222 → 0.0.237-dev250222205057

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 (69) 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 +3 -1
  7. package/dist/boolean_parser.js.map +1 -1
  8. package/dist/boolean_serializer.d.ts +4 -4
  9. package/dist/boolean_serializer.js +12 -4
  10. package/dist/boolean_serializer.js.map +1 -1
  11. package/dist/clause_types.d.ts +40 -47
  12. package/dist/date_parser.d.ts +13 -11
  13. package/dist/date_parser.js +192 -131
  14. package/dist/date_parser.js.map +1 -1
  15. package/dist/date_serializer.d.ts +6 -5
  16. package/dist/date_serializer.js +65 -77
  17. package/dist/date_serializer.js.map +1 -1
  18. package/dist/date_types.d.ts +75 -0
  19. package/dist/{filter_types.js → date_types.js} +1 -1
  20. package/dist/date_types.js.map +1 -0
  21. package/dist/generate_samples.js +162 -87
  22. package/dist/generate_samples.js.map +1 -1
  23. package/dist/number_parser.d.ts +2 -5
  24. package/dist/number_parser.js +10 -51
  25. package/dist/number_parser.js.map +1 -1
  26. package/dist/number_serializer.d.ts +4 -4
  27. package/dist/number_serializer.js +17 -14
  28. package/dist/number_serializer.js.map +1 -1
  29. package/dist/string_parser.d.ts +2 -2
  30. package/dist/string_parser.js +21 -21
  31. package/dist/string_parser.js.map +1 -1
  32. package/dist/string_serializer.d.ts +6 -5
  33. package/dist/string_serializer.js +38 -24
  34. package/dist/string_serializer.js.map +1 -1
  35. package/package.json +1 -2
  36. package/src/DEVELOPING.md +2 -5
  37. package/src/a_simple_parser.ts +12 -9
  38. package/src/base_parser.ts +0 -3
  39. package/src/boolean_parser.ts +10 -5
  40. package/src/boolean_serializer.ts +14 -7
  41. package/src/clause_types.ts +65 -108
  42. package/src/date_parser.ts +229 -192
  43. package/src/date_serializer.ts +59 -87
  44. package/src/date_types.ts +159 -0
  45. package/src/generate_samples.ts +178 -109
  46. package/src/number_parser.ts +14 -63
  47. package/src/number_serializer.ts +19 -20
  48. package/src/string_parser.ts +40 -27
  49. package/src/string_serializer.ts +58 -32
  50. package/tsconfig.json +1 -6
  51. package/dist/a_simple_serializer.d.ts +0 -1
  52. package/dist/a_simple_serializer.js +0 -31
  53. package/dist/a_simple_serializer.js.map +0 -1
  54. package/dist/base_serializer.d.ts +0 -6
  55. package/dist/base_serializer.js +0 -11
  56. package/dist/base_serializer.js.map +0 -1
  57. package/dist/filter_parser.d.ts +0 -12
  58. package/dist/filter_parser.js +0 -66
  59. package/dist/filter_parser.js.map +0 -1
  60. package/dist/filter_serializer.d.ts +0 -13
  61. package/dist/filter_serializer.js +0 -43
  62. package/dist/filter_serializer.js.map +0 -1
  63. package/dist/filter_types.d.ts +0 -10
  64. package/dist/filter_types.js.map +0 -1
  65. package/src/a_simple_serializer.ts +0 -40
  66. package/src/base_serializer.ts +0 -9
  67. package/src/filter_parser.ts +0 -68
  68. package/src/filter_serializer.ts +0 -49
  69. 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,11 +164,13 @@ 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({
188
- message: 'Invalid token ' + token.value,
171
+ message:
172
+ 'Invalid token ' + token.value ||
173
+ (token.values ? token.values.join(' ') : ''),
189
174
  startIndex: token.startIndex,
190
175
  endIndex: token.endIndex,
191
176
  });
@@ -196,151 +181,187 @@ export class DateParser extends BaseParser {
196
181
  return {clauses, errors};
197
182
  }
198
183
 
199
- // LAST|DAYOFWEEK
200
- private static createMomentInterval(
184
+ private static createMomentClause(
201
185
  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;
186
+ moment: DateMoment
187
+ ): DateClause {
188
+ if (!prefix) {
189
+ return {operator: 'ON', moment}; // DateOnClause
190
+ } else if (prefix === 'BEFORE') {
191
+ return {operator: 'BEFORE', moment}; // DateBeforeClause
192
+ } else {
193
+ return {operator: 'AFTER', moment}; // DateAfterClause
212
194
  }
213
- return moment;
214
195
  }
215
196
 
216
- // LAST|NUMBER|UNITOFTIME
217
- private static createMomentNumberInterval(
197
+ // (BEFORE|AFTER) LAST|DAYOFWEEK
198
+ private static createIntervalMoment(
218
199
  prefix: DatePrefix | undefined,
219
200
  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;
201
+ ): DateClause {
202
+ const kind: DateMomentIntervalOperator = tokens[0]
203
+ .type as DateMomentIntervalOperator;
204
+ const unit: DateTimeUnit | DateWeekday = tokens[1].value as
205
+ | DateTimeUnit
206
+ | DateWeekday;
207
+ const moment: IntervalMoment = {type: 'INTERVAL', kind, unit};
208
+ return DateParser.createMomentClause(prefix, moment);
231
209
  }
232
210
 
233
211
  // NUMBER|UNITOFTIME|AGO
234
- private static createMomentNumberIntervalAgo(
212
+ // NUMBER|UNITOFTIME|FROM|NOW
213
+ private static createOffsetMoment(
235
214
  prefix: DatePrefix | undefined,
236
215
  tokens: Token[]
237
- ): DateMomentNumberInterval {
238
- const operator: DateMomentNumberIntervalOperator = 'AGO';
239
- const value: string = tokens[0].value;
216
+ ): DateClause | undefined {
217
+ const amount = Number(tokens[0].value);
240
218
  const unit: DateTimeUnit = tokens[1].value as DateTimeUnit;
241
- const moment: DateMomentNumberInterval = {operator, value, unit};
242
- if (prefix) {
243
- moment.prefix = prefix;
219
+ const direction: DateMomentOffsetFromNowDirection =
220
+ tokens[2].type === 'AGO' ? 'AGO' : 'FROMNOW';
221
+ if (!DateParser.isValidNumber(amount)) {
222
+ return undefined;
244
223
  }
245
- return moment;
224
+ const moment: OffsetMoment = {
225
+ type: 'OFFSET_FROM_NOW',
226
+ direction,
227
+ amount,
228
+ unit,
229
+ };
230
+ return DateParser.createMomentClause(prefix, moment);
246
231
  }
247
232
 
248
- // NUMBER|UNITOFTIME|FROM|NOW
249
- private static createMomentNumberIntervalFromNow(
233
+ // (LAST|NEXT)|NUMBER|UNITOFTIME
234
+ private static createSpanMoment(
250
235
  prefix: DatePrefix | undefined,
251
236
  tokens: Token[]
252
- ): DateMomentNumberInterval | undefined {
253
- const operator: DateMomentNumberIntervalOperator = 'FROMNOW';
254
- const value: string = tokens[0].value;
237
+ ): DateClause | undefined {
238
+ const amount = Number(tokens[0].value);
255
239
  const unit: DateTimeUnit = tokens[1].value as DateTimeUnit;
256
- const moment: DateMomentNumberInterval = {operator, value, unit};
257
- if (prefix) {
258
- moment.prefix = prefix;
240
+ const direction: DateMomentSpanFromNowDirection =
241
+ tokens[2].type === 'LAST' ? 'LAST' : 'NEXT';
242
+ if (!DateParser.isValidNumber(amount)) {
243
+ return undefined;
259
244
  }
260
- return moment;
245
+ const moment: SpanMoment = {type: 'SPAN_FROM_NOW', direction, amount, unit};
246
+ return DateParser.createMomentClause(prefix, moment);
261
247
  }
262
248
 
263
- // NUMBER|UNITOFTIME
264
- private static createMomentNumberUnit(
249
+ // (NUMBER|YEAR)|UNITOFTIME
250
+ private static createDateDuration(
265
251
  prefix: DatePrefix | undefined,
266
252
  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};
253
+ ): DateClause | undefined {
272
254
  if (prefix) {
273
- moment.prefix = prefix;
255
+ return undefined; // before 7 hours is ambiguous, not allowed.
256
+ }
257
+ const operator = 'DURATION';
258
+ const amount = Number(tokens[0].value);
259
+ const unit: DateTimeUnit = tokens[1].value as DateTimeUnit;
260
+ if (!DateParser.isValidNumber(amount)) {
261
+ return undefined;
274
262
  }
275
- return moment;
263
+ const clause: DateDurationClause = {operator, duration: {amount, unit}};
264
+ return clause;
276
265
  }
277
266
 
278
- // DATE DATE|TIME
279
- private static createMomentNumber(
267
+ // (BEFORE|AFTER) DATE DATE|TIME
268
+ private static createAbsoluteMoment(
280
269
  prefix: DatePrefix | undefined,
281
270
  tokens: Token[]
282
- ): DateMomentNumber {
283
- const operator: DateMomentNumberOperator =
284
- tokens.length === 2 ? 'DATETIME' : 'DATE';
285
- const moment: DateMomentNumber = {operator, date: tokens[0].value};
271
+ ): DateClause {
272
+ let unit: DateTimeUnit = 'YEAR';
273
+ let date = tokens[0].value;
286
274
  if (tokens.length === 2) {
287
- moment.time = tokens[1].value;
275
+ const timeStr = tokens[1].value;
276
+ date += ' ' + timeStr;
277
+ if (timeStr.length > 5) unit = 'SECOND';
278
+ else unit = 'MINUTE';
279
+ } else if (date.length > 7) {
280
+ unit = 'DAY';
281
+ } else if (date.length > 4) {
282
+ unit = 'MONTH';
288
283
  }
289
- if (prefix) {
290
- moment.prefix = prefix;
291
- }
292
- return moment;
284
+ const moment: AbsoluteMoment = {type: 'ABSOLUTE', date, unit};
285
+ return DateParser.createMomentClause(prefix, moment);
293
286
  }
294
287
 
295
288
  // NOW YESTERDAY TODAY TOMORROW
296
- private static createMomentNow(
289
+ private static createNamedMoment(
297
290
  prefix: DatePrefix | undefined,
298
291
  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;
292
+ ): DateClause {
293
+ let momentName: DateMomentName = 'NOW';
294
+ switch (tokens[0].type) {
295
+ case 'TODAY':
296
+ momentName = 'TODAY';
297
+ break;
298
+ case 'YESTERDAY':
299
+ momentName = 'YESTERDAY';
300
+ break;
301
+ case 'TOMORROW':
302
+ momentName = 'TOMORROW';
303
+ break;
305
304
  }
306
- return moment;
305
+ const moment: NamedMoment = {type: 'NAMED', name: momentName};
306
+ return DateParser.createMomentClause(prefix, moment);
307
307
  }
308
308
 
309
- private static createMomentFromToken(
309
+ private static isValidNumber(value: number): boolean {
310
+ return Number.isNaN(value) === false;
311
+ }
312
+
313
+ private static createDurationFromMerged(token: Token): Duration | undefined {
314
+ if (!token.values || token.values.length !== 2) {
315
+ return undefined;
316
+ }
317
+ if (
318
+ token.type === 'MERGE:NUMBER|UNITOFTIME' ||
319
+ token.type === 'MERGE:YEAR|UNITOFTIME'
320
+ ) {
321
+ const value = Number(token.values[0].value);
322
+ if (!DateParser.isValidNumber(value)) {
323
+ return undefined;
324
+ }
325
+ const unit = token.values[1].value as DateTimeUnit;
326
+ return {amount: value, unit: unit};
327
+ }
328
+ return undefined;
329
+ }
330
+
331
+ private static createClauseFromMerged(
310
332
  prefix: DatePrefix | undefined,
311
333
  token: Token
312
- ): DateMoment | undefined {
334
+ ): DateClause | undefined {
313
335
  const tokens: Token[] = token.values || [];
314
336
  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);
337
+ case 'MERGE:LAST|UNITOFTIME':
338
+ case 'MERGE:LAST|DAYOFWEEK':
339
+ case 'MERGE:THIS|UNITOFTIME':
340
+ case 'MERGE:NEXT|UNITOFTIME':
341
+ case 'MERGE:NEXT|DAYOFWEEK':
342
+ return this.createIntervalMoment(prefix, tokens);
343
+ case 'MERGE:LAST|NUMBER|UNITOFTIME':
344
+ case 'MERGE:LAST|YEAR|UNITOFTIME':
345
+ case 'MERGE:NEXT|NUMBER|UNITOFTIME':
346
+ case 'MERGE:NEXT|YEAR|UNITOFTIME':
347
+ return this.createSpanMoment(prefix, tokens);
348
+ case 'MERGE:NUMBER|UNITOFTIME|AGO':
349
+ case 'MERGE:YEAR|UNITOFTIME|AGO':
350
+ case 'MERGE:NUMBER|UNITOFTIME|FROM|NOW':
351
+ case 'MERGE:YEAR|UNITOFTIME|FROM|NOW':
352
+ return this.createOffsetMoment(prefix, tokens);
353
+ case 'MERGE:NUMBER|UNITOFTIME':
354
+ case 'MERGE:YEAR|UNITOFTIME':
355
+ return this.createDateDuration(prefix, tokens);
356
+ case 'MERGE:DATE|TIME':
357
+ case 'MERGE:DATE':
358
+ case 'MERGE:YEAR':
359
+ return this.createAbsoluteMoment(prefix, tokens);
360
+ case 'MERGE:NOW':
361
+ case 'MERGE:TODAY':
362
+ case 'MERGE:YESTERDAY':
363
+ case 'MERGE:TOMORROW':
364
+ return this.createNamedMoment(prefix, tokens);
344
365
  default:
345
366
  return undefined;
346
367
  }
@@ -359,54 +380,70 @@ export class DateParser extends BaseParser {
359
380
  : this.tokens[position].type.startsWith(value);
360
381
  }
361
382
 
362
- private handleRange(clauses: Clause[]): boolean {
383
+ private handleRange(clauses: DateClause[]): boolean {
363
384
  if (
364
- this.isMatchingToken(this.index, 'MOMENT', false) &&
385
+ this.isMatchingToken(this.index, 'MERGE', false) &&
365
386
  (this.isMatchingToken(this.index + 1, 'TO', true) ||
366
387
  this.isMatchingToken(this.index + 1, 'FOR', true)) &&
367
- this.isMatchingToken(this.index + 2, 'MOMENT', false)
388
+ this.isMatchingToken(this.index + 2, 'MERGE', false)
368
389
  ) {
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';
390
+ const startToken = this.tokens[this.index];
391
+ const operator = this.tokens[this.index + 1].type; // TO | FOR
392
+ const endToken = this.tokens[this.index + 2];
393
+ const startClause = DateParser.createClauseFromMerged(
394
+ undefined,
395
+ startToken
396
+ );
379
397
  this.index += 3;
380
- if (startMoment === undefined || endMoment === undefined) {
398
+ if (startClause === undefined || !('moment' in startClause)) {
381
399
  return false;
382
400
  }
383
- const dateRange: DateRange = {
384
- start: startMoment,
385
- operator,
386
- end: endMoment,
387
- };
388
- clauses.push(dateRange);
401
+ if (operator === 'TO') {
402
+ const endClause = DateParser.createClauseFromMerged(
403
+ undefined,
404
+ endToken
405
+ );
406
+ if (endClause === undefined || !('moment' in endClause)) {
407
+ return false;
408
+ }
409
+ const clause: DateBetweenClause = {
410
+ operator: 'TO_RANGE',
411
+ from: startClause.moment,
412
+ to: endClause.moment,
413
+ };
414
+ clauses.push(clause);
415
+ } else {
416
+ const endDuration = DateParser.createDurationFromMerged(endToken);
417
+ if (endDuration === undefined) {
418
+ return false;
419
+ }
420
+ const clause: DateForClause = {
421
+ operator: 'FOR_RANGE',
422
+ from: startClause.moment,
423
+ duration: endDuration,
424
+ };
425
+ clauses.push(clause);
426
+ }
389
427
  return true;
390
428
  }
391
429
  return false;
392
430
  }
393
431
 
394
- private handleMoment(
432
+ private handleMerged(
395
433
  prefix: DatePrefix | undefined,
396
434
  clauses: DateClause[]
397
435
  ): boolean {
398
436
  const token: Token = this.getNext();
399
- if (token.type.startsWith('MOMENT')) {
400
- const clause: DateMoment | undefined = DateParser.createMomentFromToken(
401
- prefix,
402
- token
403
- );
437
+ if (token.type.startsWith('MERGE')) {
438
+ const clause = DateParser.createClauseFromMerged(prefix, token);
404
439
  this.index++;
405
440
  if (clause === undefined) {
406
441
  return false;
407
442
  }
408
443
  clauses.push(clause);
409
444
  return true;
445
+ } else if (token.type === 'NULL' || token.type === 'NOTNULL') {
446
+ clauses.push({operator: token.type});
410
447
  }
411
448
  return false;
412
449
  }