forked from Expensify/App
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathautocompleteParser.peggy
More file actions
154 lines (141 loc) · 4.6 KB
/
Copy pathautocompleteParser.peggy
File metadata and controls
154 lines (141 loc) · 4.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// This file defines the grammar that's used by [Peggy](https://peggyjs.org/) to generate the autocompleteParser.js file.
// The autocompleteParser is setup to parse our custom search syntax and output information needed for autocomplete and syntax highlighting to work.
//
// Here's the general grammar structure:
//
// query: the entry point for the parser and rule to process the values returned by the filterList rule. It takes filters as an argument and returns the final ranges and autocomplete objects.
// filterList: a rule to process information returned by filter rule.
// filter: an abstract rule to simplify the filterList rule. It takes all filter types.
// defaultFilter: a rule to process the default values returned by the defaultKey autocompleteKey. It updates the autocomplete field.
// freeTextFilter: a rule to process text that isn't a filter that needs autocomplete. It resets the autocomplete field (it means the user started a new filter).
// autocompleteKey: a rule to match pre-defined search syntax fields that need autocomplete, e.g. tag, currency, etc.
//
// filter, logicalAnd, operator, alphanumeric, quotedString are defined in baseRules.peggy grammar.
//
{{// CAUTION: DO NOT DIRECTLY ALTER OR MODIFY `searchParser.js` OR `autocompleteParser.js`
// These files are auto-generated by Peggy from grammar files (*.peggy).
// To make changes, edit the corresponding *.peggy files only.
// Use the `generate-search-parser` and `generate-autocomplete-parser` scripts to regenerate parsers after modifications.
}}
// per-parser initializer (code executed before every parse).
{
let autocomplete = null;
// List fields where you cannot prefix it with "-" to negate it
const nonNegatableKeys = new Set([
"type", "keyword", "groupCurrency", "groupBy", "columns", "limit", "view"
]);
}
query = _ ranges:filterList? _ { return { autocomplete, ranges }; }
filterList
= filters:filter|.., logicalAnd| { return filters.filter(Boolean).flat(); }
filter = @(defaultFilter / freeTextFilter)
defaultFilter
= _ neg:"-"? key:filterKey _ op:filterOperator _ value:identifier? {
expectingNestedQuote = false; nameOperator = false; // Reset string parser
const isNegated = !!neg && !nonNegatableKeys.has(key);
if (!value) {
autocomplete = {
key,
negated: isNegated,
value: "",
start: location().end.offset,
length: 0,
};
return;
}
autocomplete = {
key,
negated: isNegated,
...value[value.length - 1],
};
return value
.filter((v) => v.length > 0)
.map((v) => ({
key,
negated: isNegated,
...v,
}));
}
reportFieldDynamic
= ("report-field"i / "reportfield"i) "-" rest:$((!([ \t\r\n\xA0,:=<>!]) .)*) {
const suffix = rest.replaceAll(/^-+/g, "");
return "reportField-" + (suffix ? suffix : "");
}
freeTextFilter = _ (identifier / ",") _ { autocomplete = null; }
autocompleteKey "key"
= @(
in
/ currency
/ groupCurrency
/ total
/ tag
/ category
/ to
/ taxRate
/ from
/ attendee
/ payer
/ exporter
/ createdBy
/ assignee
/ expenseType
/ withdrawalType
/ type
/ status
/ cardID
/ feed
/ groupBy
/ reimbursable
/ billable
/ policyID
/ action
/ date
/ submitted
/ approved
/ paid
/ exported
/ withdrawn
/ posted
/ has
/ is
/ purchaseCurrency
/ purchaseAmount
/ amount
/ merchant
/ description
/ reportID
/ withdrawalID
/ title
/ reportFieldDynamic
/ columns
/ limit
/ view
)
filterKey
= k:autocompleteKey {
nameOperator = (k === "from" || k === "to" || k === "payer" || k === "exporter" || k === "attendee" || k === "createdBy" || k === "assignee");
return k;
}
identifier
= parts:(quotedString / alphanumeric)|1.., ","| empty:","? {
const ends = location();
const value = parts.flat().filter(Boolean); // Filter out undefined values returned by the predicate
if (empty) {
value.push("");
}
let count = ends.start.offset;
const result = [];
value.forEach((filter) => {
let word = filter;
if (word.startsWith('"') && word.endsWith('"') && word.length >= 2) {
word = word.slice(1, -1);
}
result.push({
value: word,
start: count,
length: filter.length,
});
count += filter.length + 1;
});
return result;
}