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

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.
@@ -1,4 +1,4 @@
1
- import {SpecialToken, Tokenizer, TokenizerParams} from './tokenizer';
1
+ import * as fs from 'fs';
2
2
  import {BooleanParser} from './boolean_parser';
3
3
  import {StringParser} from './string_parser';
4
4
  import {NumberParser} from './number_parser';
@@ -7,10 +7,6 @@ import {BooleanSerializer} from './boolean_serializer';
7
7
  import {StringSerializer} from './string_serializer';
8
8
  import {NumberSerializer} from './number_serializer';
9
9
  import {DateSerializer} from './date_serializer';
10
- import {BooleanClause, NumberClause, StringClause} from './clause_types';
11
- import {DateClause} from './date_types';
12
-
13
- type Clause = BooleanClause | DateClause | NumberClause | StringClause;
14
10
 
15
11
  const numberExamples = [
16
12
  '5',
@@ -85,7 +81,6 @@ const dateExamples = [
85
81
  '3 days',
86
82
  '3 days ago',
87
83
  '3 months ago for 2 days',
88
- 'after 2025 seconds',
89
84
  '2025 weeks ago',
90
85
  'before 3 days ago',
91
86
  'before 2025-08-30 08:30:20',
@@ -113,338 +108,240 @@ const dateExamples = [
113
108
  '7', // Bad syntax
114
109
  'from now', // Bad syntax
115
110
  '2025-12-25 12:32:', // Bad syntax
111
+ 'after 2025 seconds', // Bad syntax
116
112
  '',
117
113
  ];
118
114
 
119
115
  /* eslint-disable @typescript-eslint/no-unused-vars */
120
116
  /* eslint-disable no-console */
117
+ /* eslint-disable @typescript-eslint/no-explicit-any */
121
118
 
122
- function testTokenizerSingle(str: string, params: TokenizerParams): void {
123
- const tokenizer = new Tokenizer(str, params);
124
- console.log(str, ' -> ', tokenizer.parse(), '\n');
125
- }
126
-
127
- function testTokenizerString() {
128
- // Test string parser tokenizer. Do not split on whitespace
129
- const specialSubstrings: SpecialToken[] = [{type: ',', value: ','}];
130
- const specialWords: SpecialToken[] = [];
131
- const params: TokenizerParams = {
132
- trimWordWhitespace: true,
133
- combineAdjacentWords: true,
134
- specialSubstrings,
135
- specialWords,
136
- };
137
- const examples = [
138
- 'CAT, DOG,mouse ',
139
- '-CAT,-DOG , -mouse',
140
- ' CAT,-"DOG",m o u s e',
141
- '-CAT,-DOG,mouse, bird, zebra, -horse, -goat',
142
- 'Missing ,NULL',
143
- 'CAT%,D%OG',
144
- 'CAT%,-CATALOG',
145
- '_CAT,D_G',
146
- 'CAT,-NULL',
147
- 'CAT,-"NULL"',
148
- 'EMPTY',
149
- '-EMPTY',
150
- 'CAT,-EMPTY',
151
- '"CAT,DOG"',
152
- 'CAT\\,DOG',
153
- 'CAT,DOG,-, - ',
154
- '--CAT,DOG,\\',
155
- 'CA--,D-G', // _ = 'CA--' OR _ = 'D-G'
156
- ' hello world, foo="bar baz" , qux=quux',
157
- 'one ,Null , Empty,E M P T Y Y,EEmpty, emptIEs',
158
- '',
159
- ];
160
- for (const example of examples) {
161
- testTokenizerSingle(example, params);
162
- }
163
- }
164
-
165
- function testTokenizerNumber() {
166
- // Test number parser tokenizer, split on whitespace
167
- const specialSubstrings: SpecialToken[] = [
168
- {type: ',', value: ','},
169
- {type: '[', value: '['},
170
- {type: ']', value: ']'},
171
- {type: '(', value: '('},
172
- {type: ')', value: ')'},
173
- {type: '<=', value: '<='},
174
- {type: '>=', value: '>='},
175
- {type: '!=', value: '!='},
176
- {type: '=', value: '='},
177
- {type: '>', value: '>'},
178
- {type: '<', value: '<'},
179
- ];
180
- const specialWords: SpecialToken[] = [
181
- {type: 'TO', value: 'to', ignoreCase: true},
182
- {type: '-NULL', value: '-null', ignoreCase: true},
183
- {type: 'NULL', value: 'null', ignoreCase: true},
184
- ];
185
- const params: TokenizerParams = {
186
- trimWordWhitespace: true,
187
- combineAdjacentWords: false,
188
- splitOnWhitespace: true,
189
- specialSubstrings,
190
- specialWords,
191
- };
192
- const examples = [
193
- '5',
194
- '!=5',
195
- '1, 3, null , 7',
196
- '<1, >=100 ',
197
- '-5.5 to 10',
198
- '>=1',
199
- ' <= 10 ',
200
- 'NULL',
201
- ' -NULL',
202
- '(1, 7)',
203
- '[-5, 90]',
204
- ' ( 12, 20 ] ',
205
- '[.12e-20, 20.0e3)',
206
- '[0,9],[20,29]',
207
- '[0,10], 20, NULL, ( 72, 82 ] ',
208
- ', notanumber,, -"-null", apple pear orange, nulle, nnull',
209
- ];
210
- for (const example of examples) {
211
- testTokenizerSingle(example, params);
212
- }
213
- }
119
+ class GenerateSamples {
120
+ public samplesFile: fs.WriteStream = fs.createWriteStream('dist/SAMPLES.md');
121
+ public serializedFile: fs.WriteStream = fs.createWriteStream(
122
+ 'dist/SERIALIZE_SAMPLES.md'
123
+ );
124
+ constructor() {}
214
125
 
215
- function testNumberParserSingle(
216
- str: string,
217
- outputFormatter?: (clauses: Clause[]) => string
218
- ): void {
219
- console.log('Input: ', str);
220
- const parser = new NumberParser(str);
221
- const response = parser.parse();
222
- // console.log('Tokens: ', parser.getTokens());
223
- if (response.clauses && response.clauses.length > 0) {
224
- if (outputFormatter) {
225
- console.log('Output: ', outputFormatter(response.clauses));
226
- } else {
227
- console.log('Output: ', ...response.clauses);
126
+ private static removeQuotes(word: string) {
127
+ if (word.startsWith('"')) {
128
+ word = word.substring(1);
228
129
  }
229
- }
230
- if (response.errors && response.errors.length > 0) {
231
- console.log('Errors: ', ...response.errors);
232
- }
233
- console.log('');
234
- }
235
-
236
- function testNumberParser() {
237
- for (const example of numberExamples) {
238
- testNumberParserSingle(example);
239
- }
240
- }
241
-
242
- function testStringParserSingle(
243
- str: string,
244
- outputFormatter?: (clauses: Clause[]) => string
245
- ): void {
246
- console.log('Input: ', str);
247
- const parser = new StringParser(str);
248
- const response = parser.parse();
249
- // console.log('Tokens: ', parser.getTokens());
250
- if (response.clauses && response.clauses.length > 0) {
251
- if (outputFormatter) {
252
- console.log('Output: ', outputFormatter(response.clauses));
253
- } else {
254
- console.log('Output: ', ...response.clauses);
130
+ if (word.endsWith('"')) {
131
+ word = word.substring(0, word.length - 1);
255
132
  }
133
+ return word;
256
134
  }
257
- if (response.quotes) {
258
- console.log('Quotes: ', ...response.quotes);
259
- }
260
- if (response.errors && response.errors.length > 0) {
261
- console.log('Errors: ', ...response.errors);
262
- }
263
- console.log('');
264
- }
265
135
 
266
- function testStringParser() {
267
- for (const example of stringExamples) {
268
- const parser = new StringParser(example);
269
- testStringParserSingle(example);
136
+ private static writeJson(
137
+ fp: fs.WriteStream,
138
+ title: string,
139
+ ...args: any[]
140
+ ): void {
141
+ const result: string[] = args.map(str => JSON.stringify(str));
142
+ const result2: string[] = result.map(str =>
143
+ GenerateSamples.removeQuotes(str)
144
+ );
145
+ fp.write(title + ' ' + result2.join(' ') + '\n');
146
+ console.log(title, ...args);
270
147
  }
271
- }
272
148
 
273
- function testBooleanParserSingle(
274
- str: string,
275
- outputFormatter?: (clauses: Clause[]) => string
276
- ): void {
277
- console.log('Input: ', str);
278
- const parser = new BooleanParser(str);
279
- const response = parser.parse();
280
- // console.log('Tokens: ', parser.getTokens());
281
- if (response.clauses && response.clauses.length > 0) {
282
- if (outputFormatter) {
283
- console.log('Output: ', outputFormatter(response.clauses));
284
- } else {
285
- console.log('Output: ', ...response.clauses);
286
- }
287
- }
288
- if (response.errors && response.errors.length > 0) {
289
- console.log('Errors: ', ...response.errors);
149
+ public static writeRaw(fp: fs.WriteStream, ...args: any[]): void {
150
+ fp.write(args.join(' ') + '\n');
151
+ console.log(...args);
290
152
  }
291
- console.log('');
292
- }
293
153
 
294
- function testBooleanParser() {
295
- for (const example of booleanExamples) {
296
- const parser = new BooleanParser(example);
297
- testBooleanParserSingle(example);
154
+ public booleanSample(str: string, fp: fs.WriteStream): void {
155
+ GenerateSamples.writeRaw(fp, 'Input: ', str);
156
+ const parser = new BooleanParser(str);
157
+ const response = parser.parse();
158
+ // GenerateSamples.writeJson(fp, 'Tokens: ', parser.getTokens());
159
+ if (response.clauses && response.clauses.length > 0) {
160
+ GenerateSamples.writeJson(fp, 'Output: ', ...response.clauses);
161
+ }
162
+ if (response.errors && response.errors.length > 0) {
163
+ GenerateSamples.writeJson(fp, 'Errors: ', ...response.errors);
164
+ }
165
+ GenerateSamples.writeRaw(fp, '');
298
166
  }
299
- }
300
167
 
301
- function jsonFormatter(clauses: Clause[]): string {
302
- let str = '';
303
- for (const clause of clauses) {
304
- str += JSON.stringify(clause, null, ' ');
168
+ public dateSample(str: string, fp: fs.WriteStream): void {
169
+ GenerateSamples.writeRaw(fp, 'Input: ', str);
170
+ const parser = new DateParser(str);
171
+ const response = parser.parse();
172
+ // GenerateSamples.writeJson(fp, 'Tokens: ', parser.getTokens());
173
+ if (response.clauses && response.clauses.length > 0) {
174
+ GenerateSamples.writeJson(fp, 'Output: ', ...response.clauses);
175
+ }
176
+ if (response.errors && response.errors.length > 0) {
177
+ GenerateSamples.writeJson(fp, 'Errors: ', ...response.errors);
178
+ }
179
+ GenerateSamples.writeRaw(fp, '');
305
180
  }
306
- return str;
307
- }
308
181
 
309
- function testDateParserSingle(
310
- str: string,
311
- outputFormatter?: (clauses: Clause[]) => string
312
- ): void {
313
- console.log('Input: ', str);
314
- const parser = new DateParser(str);
315
- const response = parser.parse();
316
- // console.log('Tokens: ', parser.getTokens());
317
- if (response.clauses && response.clauses.length > 0) {
318
- if (outputFormatter) {
319
- console.log('Output: ', outputFormatter(response.clauses));
320
- } else {
321
- console.log('Output: ', ...response.clauses);
182
+ public numberSample(str: string, fp: fs.WriteStream): void {
183
+ GenerateSamples.writeRaw(fp, 'Input: ', str);
184
+ const parser = new NumberParser(str);
185
+ const response = parser.parse();
186
+ // GenerateSamples.writeJson(fp, 'Tokens: ', parser.getTokens());
187
+ if (response.clauses && response.clauses.length > 0) {
188
+ GenerateSamples.writeJson(fp, 'Output: ', ...response.clauses);
322
189
  }
190
+ if (response.errors && response.errors.length > 0) {
191
+ GenerateSamples.writeJson(fp, 'Errors: ', ...response.errors);
192
+ }
193
+ GenerateSamples.writeRaw(fp, '');
323
194
  }
324
- if (response.errors && response.errors.length > 0) {
325
- console.log('Errors: ', ...response.errors);
326
- }
327
- console.log('');
328
- }
329
195
 
330
- function testDateParser() {
331
- for (const example of dateExamples) {
332
- const parser = new DateParser(example);
333
- testDateParserSingle(example);
196
+ public stringSample(str: string, fp: fs.WriteStream): void {
197
+ GenerateSamples.writeRaw(fp, 'Input: ', str);
198
+ const parser = new StringParser(str);
199
+ const response = parser.parse();
200
+ // GenerateSamples.writeJson(fp, 'Tokens: ', parser.getTokens());
201
+ if (response.clauses && response.clauses.length > 0) {
202
+ GenerateSamples.writeJson(fp, 'Output: ', ...response.clauses);
203
+ }
204
+ if (response.quotes) {
205
+ GenerateSamples.writeJson(fp, 'Quotes: ', ...response.quotes);
206
+ }
207
+ if (response.errors && response.errors.length > 0) {
208
+ GenerateSamples.writeJson(fp, 'Errors: ', ...response.errors);
209
+ }
210
+ GenerateSamples.writeRaw(fp, '');
334
211
  }
335
- }
336
212
 
337
- function testNumberRoundtrip(str: string): void {
338
- console.log('Input: ' + str);
339
- const response = new NumberParser(str).parse();
340
- // console.log('Clause: ', ...response.clauses, '\n');
341
- if (response.clauses && response.clauses.length > 0) {
342
- const result = new NumberSerializer(response.clauses || []).serialize();
343
- console.log('Output: ' + result);
344
- }
345
- if (response.errors && response.errors.length > 0) {
346
- console.log('Errors: ', ...response.errors);
213
+ public booleanSerialized(str: string, fp: fs.WriteStream): void {
214
+ GenerateSamples.writeRaw(fp, 'Input: ' + str);
215
+ const response = new BooleanParser(str).parse();
216
+ // this.writeJson('Clause: ', ...response.clauses, '\n');
217
+ if (response.clauses && response.clauses.length > 0) {
218
+ const result = new BooleanSerializer(response.clauses || []).serialize();
219
+ GenerateSamples.writeRaw(fp, 'Output: ' + result);
220
+ }
221
+ if (response.errors && response.errors.length > 0) {
222
+ GenerateSamples.writeJson(fp, 'Errors: ', ...response.errors);
223
+ }
224
+ GenerateSamples.writeRaw(fp, '');
347
225
  }
348
- console.log('');
349
- }
350
226
 
351
- function testNumberSerializer(): void {
352
- for (const example of numberExamples) {
353
- testNumberRoundtrip(example);
227
+ public dateSerialized(str: string, fp: fs.WriteStream): void {
228
+ GenerateSamples.writeRaw(fp, 'Input: ' + str);
229
+ const response = new DateParser(str).parse();
230
+ // this.writeJson('Clause: ', ...response.clauses, '\n');
231
+ if (response.clauses && response.clauses.length > 0) {
232
+ const result = new DateSerializer(response.clauses || []).serialize();
233
+ GenerateSamples.writeRaw(fp, 'Output: ' + result);
234
+ }
235
+ if (response.errors && response.errors.length > 0) {
236
+ GenerateSamples.writeJson(fp, 'Errors: ', ...response.errors);
237
+ }
238
+ GenerateSamples.writeRaw(fp, '');
354
239
  }
355
- }
356
240
 
357
- function testStringRoundtrip(str: string): void {
358
- console.log('Input: ' + str);
359
- const response = new StringParser(str).parse();
360
- // console.log('Clause: ', ...response.clauses, '\n');
361
- if (response.clauses && response.clauses.length > 0) {
362
- const result = new StringSerializer(response.clauses || []).serialize();
363
- console.log('Output: ' + result);
364
- }
365
- if (response.errors && response.errors.length > 0) {
366
- console.log('Errors: ', ...response.errors);
241
+ public numberSerialized(str: string, fp: fs.WriteStream): void {
242
+ GenerateSamples.writeRaw(fp, 'Input: ' + str);
243
+ const response = new NumberParser(str).parse();
244
+ // this.writeJson('Clause: ', ...response.clauses, '\n');
245
+ if (response.clauses && response.clauses.length > 0) {
246
+ const result = new NumberSerializer(response.clauses || []).serialize();
247
+ GenerateSamples.writeRaw(fp, 'Output: ' + result);
248
+ }
249
+ if (response.errors && response.errors.length > 0) {
250
+ GenerateSamples.writeJson(fp, 'Errors: ', ...response.errors);
251
+ }
252
+ GenerateSamples.writeRaw(fp, '');
367
253
  }
368
- console.log('');
369
- }
370
254
 
371
- function testStringSerializer(): void {
372
- for (const example of stringExamples) {
373
- testStringRoundtrip(example);
255
+ public stringSerialized(str: string, fp: fs.WriteStream): void {
256
+ GenerateSamples.writeRaw(fp, 'Input: ' + str);
257
+ const response = new StringParser(str).parse();
258
+ // this.writeJson('Clause: ', ...response.clauses, '\n');
259
+ if (response.clauses && response.clauses.length > 0) {
260
+ const result = new StringSerializer(response.clauses || []).serialize();
261
+ GenerateSamples.writeRaw(fp, 'Output: ' + result);
262
+ }
263
+ if (response.errors && response.errors.length > 0) {
264
+ GenerateSamples.writeJson(fp, 'Errors: ', ...response.errors);
265
+ }
266
+ GenerateSamples.writeRaw(fp, '');
374
267
  }
375
- }
376
268
 
377
- function testBooleanRoundtrip(str: string): void {
378
- console.log('Input: ' + str);
379
- const response = new BooleanParser(str).parse();
380
- // console.log('Clause: ', ...response.clauses, '\n');
381
- if (response.clauses && response.clauses.length > 0) {
382
- const result = new BooleanSerializer(response.clauses || []).serialize();
383
- console.log('Output: ' + result);
384
- }
385
- if (response.errors && response.errors.length > 0) {
386
- console.log('Errors: ', ...response.errors);
269
+ public loop(
270
+ title: string,
271
+ examples: string[],
272
+ func: (str: string, fp: fs.WriteStream) => void,
273
+ fp: fs.WriteStream
274
+ ): void {
275
+ GenerateSamples.writeRaw(
276
+ fp,
277
+ '-------------------------------------------------------------------------'
278
+ );
279
+ GenerateSamples.writeRaw(fp, '## ' + title + '\n');
280
+ GenerateSamples.writeRaw(fp, '```code');
281
+ for (const example of examples) {
282
+ func(example, fp);
283
+ }
284
+ GenerateSamples.writeRaw(fp, '```\n');
387
285
  }
388
- console.log('');
389
286
  }
390
287
 
391
- function testBooleanSerializer(): void {
392
- const examples = [[{operator: 'TRUE'}]];
393
- for (const example of booleanExamples) {
394
- testBooleanRoundtrip(example);
395
- }
396
- }
288
+ // Comment or uncomment the following function calls to disable/enable examples.
289
+ function generateSamples() {
290
+ try {
291
+ const gen = new GenerateSamples();
292
+ GenerateSamples.writeRaw(
293
+ gen.samplesFile,
294
+ `
295
+ # Parsers
397
296
 
398
- function testDateRoundtrip(str: string): void {
399
- console.log('Input: ' + str);
400
- const response = new DateParser(str).parse();
401
- // console.log('Clause: ', ...response.clauses, '\n');
402
- if (response.clauses && response.clauses.length > 0) {
403
- const result = new DateSerializer(response.clauses || []).serialize();
404
- console.log('Output: ' + result);
405
- }
406
- if (response.errors && response.errors.length > 0) {
407
- console.log('Errors: ', ...response.errors);
408
- }
409
- console.log('');
410
- }
297
+ Each filter type is handled by a different parser (strings, numbers, dates and times, etc).
298
+ Sample outputs from each parser follow...
299
+ `
300
+ );
411
301
 
412
- function testDateSerializer(): void {
413
- for (const example of dateExamples) {
414
- testDateRoundtrip(example);
415
- }
416
- }
302
+ GenerateSamples.writeRaw(
303
+ gen.serializedFile,
304
+ `
305
+ # Serializers
417
306
 
418
- function printHeader(title: string): void {
419
- console.log(
420
- '\n-------------------------------------------------------------------------'
421
- );
422
- console.log('## ', title, '\n');
423
- }
307
+ Each parser has a complementary serializer that converts the structured clause list back
308
+ to string format. Below are round-trip samples: \`string\` to \`Clause[]\` back to \`string\`.
309
+ Round-trip Examples:
424
310
 
425
- // Comment or uncomment the following function calls to disable/enable examples.
426
- function generateSamples() {
427
- try {
428
- //printHeader('Tokenizer');
429
- //testTokenizerString();
430
- //testTokenizerNumber();
431
- //testTokenizerMatchTypes();
432
- printHeader('Numbers');
433
- testNumberParser();
434
- printHeader('Strings');
435
- testStringParser();
436
- printHeader('Booleans');
437
- testBooleanParser();
438
- printHeader('Dates and Times');
439
- testDateParser();
440
- printHeader('Number Serializer');
441
- testNumberSerializer();
442
- printHeader('String Serializer');
443
- testStringSerializer();
444
- printHeader('Boolean Serializer');
445
- testBooleanSerializer();
446
- printHeader('Date and Time Serializer');
447
- testDateSerializer();
311
+ \`\`\`code
312
+ Input > parse > Clause[] > serialize > Output
313
+ string string
314
+ \`\`\`
315
+ `
316
+ );
317
+ gen.loop('Numbers', numberExamples, gen.numberSample, gen.samplesFile);
318
+ gen.loop('Strings', stringExamples, gen.stringSample, gen.samplesFile);
319
+ gen.loop('Booleans', booleanExamples, gen.booleanSample, gen.samplesFile);
320
+ gen.loop('Dates and Times', dateExamples, gen.dateSample, gen.samplesFile);
321
+ gen.loop(
322
+ 'Number Serializer',
323
+ numberExamples,
324
+ gen.numberSerialized,
325
+ gen.serializedFile
326
+ );
327
+ gen.loop(
328
+ 'String Serializer',
329
+ stringExamples,
330
+ gen.stringSerialized,
331
+ gen.serializedFile
332
+ );
333
+ gen.loop(
334
+ 'Boolean Serializer',
335
+ booleanExamples,
336
+ gen.booleanSerialized,
337
+ gen.serializedFile
338
+ );
339
+ gen.loop(
340
+ 'Date Serializer',
341
+ dateExamples,
342
+ gen.dateSerialized,
343
+ gen.serializedFile
344
+ );
448
345
  } catch (ex: Error | unknown) {
449
346
  if (ex instanceof Error) console.error('Thrown Error: ', ex.message);
450
347
  else {
@@ -94,7 +94,7 @@ export class StringSerializer {
94
94
  result += word + ', ';
95
95
  }
96
96
  }
97
- return result;
97
+ return result.trim().replace(/,$/, '');
98
98
  }
99
99
 
100
100
  private static clauseToString(clauses: StringClause[]): string {