@malloydata/malloy-filter 0.0.237-dev250221201621
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.
- package/README.md +81 -0
- package/SAMPLES.md +381 -0
- package/SERIALIZE_SAMPLES.md +300 -0
- package/dist/a_simple_parser.d.ts +1 -0
- package/dist/a_simple_parser.js +20 -0
- package/dist/a_simple_parser.js.map +1 -0
- package/dist/a_simple_serializer.d.ts +1 -0
- package/dist/a_simple_serializer.js +31 -0
- package/dist/a_simple_serializer.js.map +1 -0
- package/dist/base_parser.d.ts +13 -0
- package/dist/base_parser.js +33 -0
- package/dist/base_parser.js.map +1 -0
- package/dist/base_serializer.d.ts +6 -0
- package/dist/base_serializer.js +11 -0
- package/dist/base_serializer.js.map +1 -0
- package/dist/boolean_parser.d.ts +7 -0
- package/dist/boolean_parser.js +59 -0
- package/dist/boolean_parser.js.map +1 -0
- package/dist/boolean_serializer.d.ts +8 -0
- package/dist/boolean_serializer.js +31 -0
- package/dist/boolean_serializer.js.map +1 -0
- package/dist/clause_types.d.ts +70 -0
- package/dist/clause_types.js +3 -0
- package/dist/clause_types.js.map +1 -0
- package/dist/date_parser.d.ts +22 -0
- package/dist/date_parser.js +315 -0
- package/dist/date_parser.js.map +1 -0
- package/dist/date_serializer.d.ts +10 -0
- package/dist/date_serializer.js +100 -0
- package/dist/date_serializer.js.map +1 -0
- package/dist/filter_parser.d.ts +12 -0
- package/dist/filter_parser.js +66 -0
- package/dist/filter_parser.js.map +1 -0
- package/dist/filter_serializer.d.ts +13 -0
- package/dist/filter_serializer.js +43 -0
- package/dist/filter_serializer.js.map +1 -0
- package/dist/filter_types.d.ts +10 -0
- package/dist/filter_types.js +3 -0
- package/dist/filter_types.js.map +1 -0
- package/dist/generate_samples.d.ts +1 -0
- package/dist/generate_samples.js +344 -0
- package/dist/generate_samples.js.map +1 -0
- package/dist/number_parser.d.ts +20 -0
- package/dist/number_parser.js +275 -0
- package/dist/number_parser.js.map +1 -0
- package/dist/number_serializer.d.ts +11 -0
- package/dist/number_serializer.js +76 -0
- package/dist/number_serializer.js.map +1 -0
- package/dist/string_parser.d.ts +18 -0
- package/dist/string_parser.js +198 -0
- package/dist/string_parser.js.map +1 -0
- package/dist/string_serializer.d.ts +11 -0
- package/dist/string_serializer.js +77 -0
- package/dist/string_serializer.js.map +1 -0
- package/dist/token_types.d.ts +7 -0
- package/dist/token_types.js +3 -0
- package/dist/token_types.js.map +1 -0
- package/dist/tokenizer.d.ts +52 -0
- package/dist/tokenizer.js +263 -0
- package/dist/tokenizer.js.map +1 -0
- package/dist/tokenizer.spec.d.ts +1 -0
- package/dist/tokenizer.spec.js +255 -0
- package/dist/tokenizer.spec.js.map +1 -0
- package/jest.config.js +3 -0
- package/package.json +21 -0
- package/src/DEVELOPING.md +26 -0
- package/src/a_simple_parser.ts +22 -0
- package/src/a_simple_serializer.ts +40 -0
- package/src/base_parser.ts +45 -0
- package/src/base_serializer.ts +9 -0
- package/src/boolean_parser.ts +60 -0
- package/src/boolean_serializer.ts +32 -0
- package/src/clause_types.ts +160 -0
- package/src/date_parser.ts +413 -0
- package/src/date_serializer.ts +114 -0
- package/src/filter_parser.ts +68 -0
- package/src/filter_serializer.ts +49 -0
- package/src/filter_types.ts +12 -0
- package/src/generate_samples.ts +387 -0
- package/src/number_parser.ts +308 -0
- package/src/number_serializer.ts +96 -0
- package/src/string_parser.ts +193 -0
- package/src/string_serializer.ts +87 -0
- package/src/token_types.ts +7 -0
- package/src/tokenizer.spec.ts +273 -0
- package/src/tokenizer.ts +320 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {FilterSerializer} from './filter_serializer';
|
|
2
|
+
import {
|
|
3
|
+
NumberClause,
|
|
4
|
+
StringCondition,
|
|
5
|
+
BooleanClause,
|
|
6
|
+
DateClause,
|
|
7
|
+
} from './clause_types';
|
|
8
|
+
|
|
9
|
+
/* eslint-disable no-console */
|
|
10
|
+
function aSimpleSerializer() {
|
|
11
|
+
const strings: StringCondition[] = [{operator: '=', values: ['CAT', 'DOG']}];
|
|
12
|
+
let response = new FilterSerializer(strings, 'string').serialize();
|
|
13
|
+
console.log(...strings, '\n', response.result, '\n');
|
|
14
|
+
|
|
15
|
+
const numbers: NumberClause[] = [
|
|
16
|
+
{
|
|
17
|
+
operator: 'range',
|
|
18
|
+
startOperator: '>=',
|
|
19
|
+
startValue: -5.5,
|
|
20
|
+
endOperator: '<',
|
|
21
|
+
endValue: 10,
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
response = new FilterSerializer(numbers, 'number').serialize();
|
|
25
|
+
console.log(...numbers, '\n', response.result, '\n');
|
|
26
|
+
|
|
27
|
+
const booleans: BooleanClause[] = [{operator: 'NULL'}, {operator: 'TRUE'}];
|
|
28
|
+
|
|
29
|
+
response = new FilterSerializer(booleans, 'boolean').serialize();
|
|
30
|
+
console.log(...booleans, '\n', response.result, '\n');
|
|
31
|
+
|
|
32
|
+
const dates: DateClause[] = [
|
|
33
|
+
{operator: 'YESTERDAY'},
|
|
34
|
+
{operator: 'NEXT', unit: 'TUESDAY'},
|
|
35
|
+
];
|
|
36
|
+
response = new FilterSerializer(dates, 'date').serialize();
|
|
37
|
+
console.log(...dates, '\n', response.result, '\n');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
aSimpleSerializer();
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {Token} from './token_types';
|
|
2
|
+
import {FilterParserResponse} from './filter_types';
|
|
3
|
+
|
|
4
|
+
export abstract class BaseParser {
|
|
5
|
+
protected inputString: string;
|
|
6
|
+
protected index: number;
|
|
7
|
+
protected tokens: Token[];
|
|
8
|
+
|
|
9
|
+
constructor(inputString: string) {
|
|
10
|
+
this.index = 0;
|
|
11
|
+
this.tokens = [];
|
|
12
|
+
this.inputString = inputString;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public getTokens(): Token[] {
|
|
16
|
+
return this.tokens;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public abstract parse(): FilterParserResponse;
|
|
20
|
+
|
|
21
|
+
protected getAt(index: number): Token {
|
|
22
|
+
return this.tokens[index];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
protected getNext(): Token {
|
|
26
|
+
return this.getAt(this.index);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
protected static matchTokenTypes(
|
|
30
|
+
candidates: string[],
|
|
31
|
+
index: number,
|
|
32
|
+
tokens: Token[]
|
|
33
|
+
): boolean {
|
|
34
|
+
const maxIndex = index + candidates.length;
|
|
35
|
+
if (index < 0 || maxIndex > tokens.length) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
39
|
+
if (candidates[i] !== tokens[i + index].type) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {SpecialToken, Tokenizer, TokenizerParams} from './tokenizer';
|
|
2
|
+
import {BooleanClause, BooleanOperator} from './clause_types';
|
|
3
|
+
import {BaseParser} from './base_parser';
|
|
4
|
+
import {FilterParserResponse, FilterError} from './filter_types';
|
|
5
|
+
|
|
6
|
+
export class BooleanParser extends BaseParser {
|
|
7
|
+
constructor(input: string) {
|
|
8
|
+
super(input);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
private tokenize(): void {
|
|
12
|
+
const specialSubstrings: SpecialToken[] = [{type: ',', value: ','}];
|
|
13
|
+
const specialWords: SpecialToken[] = [
|
|
14
|
+
{type: 'NULL', value: 'null', ignoreCase: true},
|
|
15
|
+
{type: 'NOTNULL', value: '-null', ignoreCase: true},
|
|
16
|
+
{type: 'TRUE', value: 'true', ignoreCase: true},
|
|
17
|
+
{type: 'FALSE', value: 'false', ignoreCase: true},
|
|
18
|
+
];
|
|
19
|
+
const params: TokenizerParams = {
|
|
20
|
+
trimWordWhitespace: true,
|
|
21
|
+
splitOnWhitespace: true,
|
|
22
|
+
specialSubstrings,
|
|
23
|
+
specialWords: specialWords,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const tokenizer = new Tokenizer(this.inputString, params);
|
|
27
|
+
this.tokens = tokenizer.parse();
|
|
28
|
+
this.tokens = Tokenizer.convertSpecialWords(this.tokens, specialWords);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public parse(): FilterParserResponse {
|
|
32
|
+
this.index = 0;
|
|
33
|
+
this.tokenize();
|
|
34
|
+
const clauses: BooleanClause[] = [];
|
|
35
|
+
const errors: FilterError[] = [];
|
|
36
|
+
while (this.index < this.tokens.length) {
|
|
37
|
+
const token = this.getNext();
|
|
38
|
+
if (token.type === ',') {
|
|
39
|
+
this.index++;
|
|
40
|
+
} else if (
|
|
41
|
+
token.type === 'NULL' ||
|
|
42
|
+
token.type === 'TRUE' ||
|
|
43
|
+
token.type === 'FALSE' ||
|
|
44
|
+
token.type === 'NOTNULL'
|
|
45
|
+
) {
|
|
46
|
+
const clause: BooleanClause = {operator: token.type as BooleanOperator};
|
|
47
|
+
clauses.push(clause);
|
|
48
|
+
this.index++;
|
|
49
|
+
} else {
|
|
50
|
+
errors.push({
|
|
51
|
+
message: 'Invalid token ' + token.value,
|
|
52
|
+
startIndex: token.startIndex,
|
|
53
|
+
endIndex: token.endIndex,
|
|
54
|
+
});
|
|
55
|
+
this.index++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return {clauses, errors};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {BooleanClause, Clause} from './clause_types';
|
|
2
|
+
import {BaseSerializer} from './base_serializer';
|
|
3
|
+
|
|
4
|
+
export class BooleanSerializer extends BaseSerializer {
|
|
5
|
+
constructor(clauses: Clause[]) {
|
|
6
|
+
super(clauses);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
public serialize(): string {
|
|
10
|
+
const result = BooleanSerializer.clauseToString(this.clauses);
|
|
11
|
+
return result.trim().replace(/,$/, '');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
private static booleanClauseToString(clause: BooleanClause): string {
|
|
15
|
+
return clause.operator === 'NOTNULL' ? '-NULL' : clause.operator;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
private static clauseToString(clauses: Clause[]): string {
|
|
19
|
+
let result = '';
|
|
20
|
+
for (const clause of clauses) {
|
|
21
|
+
if ('operator' in clause) {
|
|
22
|
+
result += BooleanSerializer.booleanClauseToString(
|
|
23
|
+
clause as BooleanClause
|
|
24
|
+
);
|
|
25
|
+
} else {
|
|
26
|
+
throw new Error('Invalid boolean clause ' + JSON.stringify(clause));
|
|
27
|
+
}
|
|
28
|
+
result += ', ';
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
export type NumberOperator = '<=' | '>=' | '!=' | '=' | '>' | '<';
|
|
2
|
+
|
|
3
|
+
export type NumberValue = number | null;
|
|
4
|
+
|
|
5
|
+
export interface NumberCondition {
|
|
6
|
+
operator: NumberOperator;
|
|
7
|
+
values: NumberValue[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type NumberRangeOperator = '<=' | '>=' | '>' | '<';
|
|
11
|
+
|
|
12
|
+
export interface NumberRange {
|
|
13
|
+
operator: 'range';
|
|
14
|
+
startOperator: NumberRangeOperator;
|
|
15
|
+
startValue: NumberValue;
|
|
16
|
+
endOperator: NumberRangeOperator;
|
|
17
|
+
endValue: NumberValue;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type QuoteType =
|
|
21
|
+
| 'SINGLE'
|
|
22
|
+
| 'DOUBLE'
|
|
23
|
+
| 'BACKTICK'
|
|
24
|
+
| 'TRIPLESINGLE'
|
|
25
|
+
| 'TRIPLEDOUBLE'
|
|
26
|
+
| 'ESCAPEDSINGLE'
|
|
27
|
+
| 'ESCAPEDDOUBLE'
|
|
28
|
+
| 'ESCAPEDBACKTICK';
|
|
29
|
+
|
|
30
|
+
export type StringOperator =
|
|
31
|
+
| 'EMPTY'
|
|
32
|
+
| 'NOTEMPTY'
|
|
33
|
+
| 'starts'
|
|
34
|
+
| 'ends'
|
|
35
|
+
| 'contains'
|
|
36
|
+
| 'notStarts'
|
|
37
|
+
| 'notEnds'
|
|
38
|
+
| 'notContains'
|
|
39
|
+
| '~'
|
|
40
|
+
| '='
|
|
41
|
+
| '!~'
|
|
42
|
+
| '!=';
|
|
43
|
+
|
|
44
|
+
export type StringValue = string | null;
|
|
45
|
+
|
|
46
|
+
export interface StringCondition {
|
|
47
|
+
operator: StringOperator;
|
|
48
|
+
values: StringValue[];
|
|
49
|
+
quotes?: QuoteType[]; // List of quote types found in the string.
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type BooleanOperator = 'TRUE' | 'FALSE' | 'NULL' | 'NOTNULL';
|
|
53
|
+
|
|
54
|
+
export interface BooleanClause {
|
|
55
|
+
operator: BooleanOperator;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type DatePrefix = 'BEFORE' | 'AFTER';
|
|
59
|
+
|
|
60
|
+
export type DateTimeUnit =
|
|
61
|
+
| 'YEAR'
|
|
62
|
+
| 'QUARTER'
|
|
63
|
+
| 'MONTH'
|
|
64
|
+
| 'WEEK'
|
|
65
|
+
| 'DAY'
|
|
66
|
+
| 'HOUR'
|
|
67
|
+
| 'MINUTE'
|
|
68
|
+
| 'SECOND';
|
|
69
|
+
|
|
70
|
+
export type DateWeekday =
|
|
71
|
+
| 'MONDAY'
|
|
72
|
+
| 'TUESDAY'
|
|
73
|
+
| 'WEDNESDAY'
|
|
74
|
+
| 'THURSDAY'
|
|
75
|
+
| 'FRIDAY'
|
|
76
|
+
| 'SATURDAY'
|
|
77
|
+
| 'SUNDAY';
|
|
78
|
+
|
|
79
|
+
export type DateMomentNowOperator = 'NOW' | 'TODAY' | 'YESTERDAY' | 'TOMORROW';
|
|
80
|
+
|
|
81
|
+
// now, after today, before yesterday, tomorrow
|
|
82
|
+
export interface DateMomentNow {
|
|
83
|
+
prefix?: DatePrefix;
|
|
84
|
+
operator: DateMomentNowOperator;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type DateMomentIntervalOperator = 'LAST' | 'THIS' | 'NEXT';
|
|
88
|
+
|
|
89
|
+
// LAST|UNITOFTIME, LAST|DAYOFWEEK
|
|
90
|
+
// THIS|UNITOFTIME
|
|
91
|
+
// NEXT|UNITOFTIME, NEXT|DAYOFWEEK
|
|
92
|
+
// before last month, after next tuesday, this month
|
|
93
|
+
export interface DateMomentInterval {
|
|
94
|
+
prefix?: DatePrefix;
|
|
95
|
+
operator: DateMomentIntervalOperator;
|
|
96
|
+
unit: DateTimeUnit | DateWeekday;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export type DateMomentNumberIntervalOperator =
|
|
100
|
+
| 'LASTN'
|
|
101
|
+
| 'NEXTN'
|
|
102
|
+
| 'AGO'
|
|
103
|
+
| 'FROMNOW';
|
|
104
|
+
|
|
105
|
+
// LAST|NUMBER|UNITOFTIME
|
|
106
|
+
// NEXT|NUMBER|UNITOFTIME
|
|
107
|
+
// NUMBER|UNITOFTIME|AGO
|
|
108
|
+
// NUMBER|UNITOFTIME|FROM|NOW
|
|
109
|
+
// before 3 hours ago, next 5 days, 2025 seconds ago, after 6 weeks from now
|
|
110
|
+
export interface DateMomentNumberInterval {
|
|
111
|
+
prefix?: DatePrefix;
|
|
112
|
+
operator: DateMomentNumberIntervalOperator;
|
|
113
|
+
unit: DateTimeUnit;
|
|
114
|
+
value: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export type DateMomentNumberUnitOperator = 'TIMEBLOCK';
|
|
118
|
+
|
|
119
|
+
// NUMBER|UNITOFTIME
|
|
120
|
+
// 2025 seconds, after 32 hours
|
|
121
|
+
export interface DateMomentNumberUnit {
|
|
122
|
+
prefix?: DatePrefix;
|
|
123
|
+
operator: DateMomentNumberUnitOperator;
|
|
124
|
+
unit: DateTimeUnit;
|
|
125
|
+
value: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export type DateMomentNumberOperator = 'DATE' | 'DATETIME';
|
|
129
|
+
|
|
130
|
+
// DATE|TIME, DATE
|
|
131
|
+
// after 2025, before 2025-08-04, 2025-08-04 08:11:52
|
|
132
|
+
export interface DateMomentNumber {
|
|
133
|
+
prefix?: DatePrefix;
|
|
134
|
+
operator: DateMomentNumberOperator;
|
|
135
|
+
date: string;
|
|
136
|
+
time?: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export type DateMoment =
|
|
140
|
+
| DateMomentNow
|
|
141
|
+
| DateMomentInterval
|
|
142
|
+
| DateMomentNumberInterval
|
|
143
|
+
| DateMomentNumberUnit
|
|
144
|
+
| DateMomentNumber;
|
|
145
|
+
|
|
146
|
+
export interface DateRange {
|
|
147
|
+
start: DateMoment;
|
|
148
|
+
operator: 'TO' | 'FOR';
|
|
149
|
+
end: DateMoment;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export type NumberClause = NumberCondition | NumberRange;
|
|
153
|
+
|
|
154
|
+
export type DateClause = DateMoment | DateRange;
|
|
155
|
+
|
|
156
|
+
export type Clause =
|
|
157
|
+
| NumberClause
|
|
158
|
+
| StringCondition
|
|
159
|
+
| BooleanClause
|
|
160
|
+
| DateClause;
|