@witchcraft/expressit 0.0.2 → 0.1.0

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 (124) hide show
  1. package/README.md +6 -4
  2. package/dist/Lexer.d.ts +146 -0
  3. package/dist/Lexer.d.ts.map +1 -0
  4. package/dist/Lexer.js +960 -0
  5. package/dist/Parser.d.ts +140 -0
  6. package/dist/Parser.d.ts.map +1 -0
  7. package/dist/Parser.js +668 -0
  8. package/dist/ast/builders/token.js +1 -1
  9. package/dist/ast/handlers.d.ts +3 -3
  10. package/dist/ast/handlers.d.ts.map +1 -1
  11. package/dist/ast/index.d.ts.map +1 -1
  12. package/dist/examples/index.d.ts +2 -0
  13. package/dist/examples/index.d.ts.map +1 -0
  14. package/dist/examples/index.js +4 -0
  15. package/dist/examples/shortcutContextParser.d.ts +2 -1
  16. package/dist/examples/shortcutContextParser.d.ts.map +1 -1
  17. package/dist/examples/shortcutContextParser.js +9 -5
  18. package/dist/helpers/errors.d.ts.map +1 -1
  19. package/dist/helpers/errors.js +3 -1
  20. package/dist/helpers/index.d.ts.map +1 -1
  21. package/dist/helpers/parser/checkParserOpts.d.ts.map +1 -1
  22. package/dist/helpers/parser/checkParserOpts.js +3 -2
  23. package/dist/helpers/parser/extractPosition.d.ts +2 -6
  24. package/dist/helpers/parser/extractPosition.d.ts.map +1 -1
  25. package/dist/helpers/parser/extractPosition.js +3 -3
  26. package/dist/helpers/parser/getUnclosedRightParenCount.d.ts +2 -3
  27. package/dist/helpers/parser/getUnclosedRightParenCount.d.ts.map +1 -1
  28. package/dist/helpers/parser/getUnclosedRightParenCount.js +4 -4
  29. package/dist/index.d.ts +1 -2
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +3 -5
  32. package/dist/methods/autocomplete.d.ts.map +1 -1
  33. package/dist/methods/autocomplete.js +1 -1
  34. package/dist/methods/autoreplace.js +1 -1
  35. package/dist/methods/autosuggest.js +1 -1
  36. package/dist/methods/evaluate.d.ts.map +1 -1
  37. package/dist/methods/evaluate.js +3 -1
  38. package/dist/methods/getIndexes.d.ts.map +1 -1
  39. package/dist/methods/getIndexes.js +2 -1
  40. package/dist/methods/normalize.d.ts +0 -2
  41. package/dist/methods/normalize.d.ts.map +1 -1
  42. package/dist/methods/normalize.js +2 -3
  43. package/dist/methods/validate.d.ts.map +1 -1
  44. package/dist/methods/validate.js +3 -1
  45. package/dist/package.json.js +44 -37
  46. package/dist/types/ast.d.ts +2 -8
  47. package/dist/types/ast.d.ts.map +1 -1
  48. package/dist/types/errors.d.ts +5 -17
  49. package/dist/types/errors.d.ts.map +1 -1
  50. package/dist/types/errors.js +0 -1
  51. package/dist/types/parser.d.ts +6 -2
  52. package/dist/types/parser.d.ts.map +1 -1
  53. package/dist/utils/extractTokens.js +1 -1
  54. package/dist/utils/getCursorInfo.d.ts +2 -2
  55. package/dist/utils/getCursorInfo.d.ts.map +1 -1
  56. package/dist/utils/getCursorInfo.js +3 -2
  57. package/dist/utils/getOppositeDelimiter.d.ts.map +1 -1
  58. package/dist/utils/getOppositeDelimiter.js +1 -1
  59. package/dist/utils/prettyAst.d.ts.map +1 -1
  60. package/dist/utils/prettyAst.js +15 -9
  61. package/package.json +42 -37
  62. package/src/Lexer.ts +704 -0
  63. package/src/Parser.ts +972 -0
  64. package/src/ast/builders/array.ts +2 -2
  65. package/src/ast/builders/condition.ts +1 -1
  66. package/src/ast/builders/expression.ts +1 -1
  67. package/src/ast/builders/group.ts +1 -1
  68. package/src/ast/builders/index.ts +1 -1
  69. package/src/ast/builders/pos.ts +1 -1
  70. package/src/ast/builders/token.ts +2 -2
  71. package/src/ast/builders/type.ts +1 -1
  72. package/src/ast/builders/variable.ts +1 -1
  73. package/src/ast/classes/ConditionNode.ts +1 -1
  74. package/src/ast/classes/ErrorToken.ts +1 -1
  75. package/src/ast/classes/ValidToken.ts +2 -2
  76. package/src/ast/classes/index.ts +1 -1
  77. package/src/ast/handlers.ts +6 -6
  78. package/src/ast/index.ts +2 -2
  79. package/src/examples/index.ts +3 -0
  80. package/src/examples/shortcutContextParser.ts +11 -6
  81. package/src/helpers/errors.ts +5 -3
  82. package/src/helpers/general/defaultConditionNormalizer.ts +1 -1
  83. package/src/helpers/general/index.ts +1 -1
  84. package/src/helpers/index.ts +3 -2
  85. package/src/helpers/parser/checkParserOpts.ts +13 -12
  86. package/src/helpers/parser/extractPosition.ts +4 -8
  87. package/src/helpers/parser/getUnclosedRightParenCount.ts +6 -6
  88. package/src/helpers/parser/index.ts +1 -1
  89. package/src/helpers/parser/parseParserOptions.ts +1 -1
  90. package/src/index.ts +2 -2
  91. package/src/methods/autocomplete.ts +5 -5
  92. package/src/methods/autoreplace.ts +2 -2
  93. package/src/methods/autosuggest.ts +3 -3
  94. package/src/methods/evaluate.ts +4 -2
  95. package/src/methods/getIndexes.ts +2 -1
  96. package/src/methods/normalize.ts +3 -4
  97. package/src/methods/validate.ts +4 -2
  98. package/src/types/ast.ts +2 -9
  99. package/src/types/errors.ts +12 -22
  100. package/src/types/parser.ts +6 -4
  101. package/src/utils/extractTokens.ts +1 -1
  102. package/src/utils/getCursorInfo.ts +6 -4
  103. package/src/utils/getOppositeDelimiter.ts +5 -2
  104. package/src/utils/prettyAst.ts +5 -3
  105. package/dist/examples/advancedValueComparer.d.ts +0 -3
  106. package/dist/examples/advancedValueComparer.d.ts.map +0 -1
  107. package/dist/examples/advancedValueComparer.js +0 -28
  108. package/dist/grammar/ParserBase.d.ts +0 -51
  109. package/dist/grammar/ParserBase.d.ts.map +0 -1
  110. package/dist/grammar/ParserBase.js +0 -516
  111. package/dist/grammar/createTokens.d.ts +0 -56
  112. package/dist/grammar/createTokens.d.ts.map +0 -1
  113. package/dist/grammar/createTokens.js +0 -843
  114. package/dist/grammar/index.d.ts +0 -3
  115. package/dist/grammar/index.d.ts.map +0 -1
  116. package/dist/grammar/index.js +0 -6
  117. package/dist/parser.d.ts +0 -58
  118. package/dist/parser.d.ts.map +0 -1
  119. package/dist/parser.js +0 -136
  120. package/src/examples/advancedValueComparer.ts +0 -31
  121. package/src/grammar/ParserBase.ts +0 -715
  122. package/src/grammar/createTokens.ts +0 -512
  123. package/src/grammar/index.ts +0 -4
  124. package/src/parser.ts +0 -183
@@ -1,512 +0,0 @@
1
- /* eslint-disable camelcase */
2
- import { escapeRegex, isBlank } from "@alanscodelog/utils"
3
- import { createToken, Lexer, type TokenType } from "chevrotain"
4
-
5
- import type { FullParserOptions } from "../types/parser.js"
6
-
7
-
8
- /** Makes it easier to rename the tokens while still returning a properly typed record of them.*/
9
- // eslint-disable-next-line @typescript-eslint/naming-convention
10
- enum $T {
11
- _ = "_", // whitespace,
12
- ANY = "ANY",
13
- QUOTE_ANY = "QUOTE_ANY",
14
- QUOTE_SINGLE = "QUOTE_SINGLE",
15
- QUOTE_DOUBLE = "QUOTE_DOUBLE",
16
- QUOTE_BACKTICK = "QUOTE_BACKTICK",
17
- VALUE = "VALUE",
18
- REGEX_ANY = "REGEX_ANY",
19
- VALUE_UNQUOTED = "VALUE_UNQUOTED",
20
- BRACKET_VALUE_UNQUOTED = "BRACKET_VALUE_UNQUOTED",
21
- VALUE_REGEX = "VALUE_REGEX",
22
- VALUE_NOT_SINGLE = "VALUE_NOT_SINGLE",
23
- VALUE_NOT_DOUBLE = "VALUE_NOT_DOUBLE",
24
- VALUE_NOT_BACKTICK = "VALUE_NOT_BACKTICK",
25
- VALUE_FOR_SINGLE = "VALUE_FOR_SINGLE",
26
- VALUE_FOR_DOUBLE = "VALUE_FOR_DOUBLE",
27
- VALUE_FOR_BACKTICK = "VALUE_FOR_BACKTICK",
28
- OPERATOR_OR = "OPERATOR_OR",
29
- OPERATOR_AND = "OPERATOR_AND",
30
- OPERATOR_NOT = "OPERATOR_NOT",
31
- SYM_OR = "SYM_OR",
32
- SYM_AND = "SYM_AND",
33
- SYM_NOT = "SYM_NOT",
34
- WORD_OR = "WORD_OR",
35
- WORD_AND = "WORD_AND",
36
- WORD_NOT = "WORD_NOT",
37
- REGEX_START = "REGEX_START",
38
- REGEX_END = "REGEX_END",
39
- QUOTE_SINGLE_START = "QUOTE_SINGLE_START",
40
- QUOTE_DOUBLE_START = "QUOTE_DOUBLE_START",
41
- QUOTE_BACKTICK_START = "QUOTE_BACKTICK_START",
42
- QUOTE_SINGLE_END = "QUOTE_SINGLE_END",
43
- QUOTE_DOUBLE_END = "QUOTE_DOUBLE_END",
44
- QUOTE_BACKTICK_END = "QUOTE_BACKTICK_END",
45
- EXP_PROP_OP = "EXP_PROP_OP",
46
- CUSTOM_PROP_OP = "CUSTOM_PROP_OP",
47
- PAREN_L = "PAREN_L",
48
- PAREN_R = "PAREN_R",
49
- BRACKET_L = "BRACKET_L",
50
- BRACKET_R = "BRACKET_R",
51
- }
52
-
53
- // Should only be used internally by the lexer.
54
- type LexerOnly =
55
- // | $T.ANY - special case, needed when we want to consume any token
56
- | $T.VALUE_NOT_BACKTICK
57
- | $T.VALUE_NOT_DOUBLE
58
- | $T.VALUE_NOT_SINGLE
59
- | $T.SYM_OR
60
- | $T.SYM_AND
61
- | $T.WORD_OR
62
- | $T.WORD_AND
63
- // | $T.REGEX_START - special case, needed for recovering
64
- | $T.REGEX_END
65
- | $T.QUOTE_SINGLE_START
66
- | $T.QUOTE_DOUBLE_START
67
- | $T.QUOTE_BACKTICK_START
68
- | $T.QUOTE_SINGLE_END
69
- | $T.QUOTE_DOUBLE_END
70
- | $T.QUOTE_BACKTICK_END
71
- | $T.WORD_NOT
72
- // | $T.SYM_NOT - special case, allowed because custom prop operators can be the "symbol" negation tokens
73
-
74
- function clone(token: TokenType): TokenType {
75
- const t: any = {}
76
- if (token.name !== undefined) t.name = token.name
77
- if (token.CATEGORIES !== undefined) t.categories = token.CATEGORIES
78
- if (token.GROUP !== undefined) t.group = token.GROUP
79
- if (token.LINE_BREAKS !== undefined) t.line_breaks = token.LINE_BREAKS
80
- if (token.LONGER_ALT !== undefined) t.longet_alt = token.LONGER_ALT
81
- if (token.PATTERN !== undefined) t.pattern = token.PATTERN
82
- if (token.PUSH_MODE !== undefined) t.push_mode = token.PUSH_MODE
83
- if (token.POP_MODE !== undefined) t.pop_mode = token.POP_MODE
84
- return createToken(t)
85
- }
86
-
87
- function changePushMode(tokens: TokenType[], mode: string | undefined | ((str?: string) => string)): TokenType[] {
88
- return tokens.map(_ => clone({
89
- ..._,
90
- ...(mode ? { PUSH_MODE: typeof mode === "function" ? mode(_.PUSH_MODE) : mode } : {}),
91
- }))
92
- }
93
-
94
- export function createTokens<T extends {} = {}>(opts: FullParserOptions<T>): {
95
- tokens: Record<Exclude<$T, LexerOnly>, TokenType>
96
- lexer: Lexer
97
- info: {
98
- expandedSepAlsoCustom: boolean
99
- customOpAlsoNegation: boolean
100
- }
101
- } {
102
- const $: Record<$T, TokenType> = {} as any
103
-
104
- /* #region TOKEN CATEGORIES */
105
-
106
- $[$T.ANY] = createToken({
107
- name: $T.ANY,
108
- pattern: Lexer.NA,
109
- })
110
- $[$T.QUOTE_ANY] = createToken({
111
- name: $T.QUOTE_ANY,
112
- pattern: Lexer.NA,
113
- })
114
-
115
- // so we can easily match start/end tokens in wrong positions
116
- $[$T.QUOTE_SINGLE] = createToken({
117
- name: $T.QUOTE_SINGLE,
118
- pattern: Lexer.NA,
119
- })
120
- $[$T.QUOTE_DOUBLE] = createToken({
121
- name: $T.QUOTE_DOUBLE,
122
- pattern: Lexer.NA,
123
- })
124
- $[$T.QUOTE_BACKTICK] = createToken({
125
- name: $T.QUOTE_BACKTICK,
126
- pattern: Lexer.NA,
127
- })
128
- $[$T.REGEX_ANY] = createToken({
129
- name: $T.REGEX_ANY,
130
- pattern: Lexer.NA,
131
- })
132
-
133
- // required to make bracket values work
134
- // see the bracket modes at the end for an explanation
135
- $[$T.VALUE_FOR_SINGLE] = createToken({
136
- name: $T.VALUE_FOR_SINGLE,
137
- pattern: Lexer.NA,
138
- })
139
- $[$T.VALUE_FOR_DOUBLE] = createToken({
140
- name: $T.VALUE_FOR_DOUBLE,
141
- pattern: Lexer.NA,
142
- })
143
- $[$T.VALUE_FOR_BACKTICK] = createToken({
144
- name: $T.VALUE_FOR_BACKTICK,
145
- pattern: Lexer.NA,
146
- })
147
-
148
- $[$T.OPERATOR_OR] = createToken({
149
- name: $T.OPERATOR_OR,
150
- pattern: Lexer.NA,
151
- })
152
- $[$T.OPERATOR_AND] = createToken({
153
- name: $T.OPERATOR_AND,
154
- pattern: Lexer.NA,
155
- })
156
- $[$T.OPERATOR_NOT] = createToken({
157
- name: $T.OPERATOR_NOT,
158
- pattern: Lexer.NA,
159
- })
160
-
161
- $[$T.VALUE] = createToken({
162
- name: $T.VALUE,
163
- pattern: Lexer.NA,
164
- })
165
- /* #regionend */
166
- /* #region ACTUAL TOKENS */
167
-
168
- $[$T._] = createToken({
169
- name: $T._,
170
- pattern: /\s+/,
171
- group: Lexer.SKIPPED,
172
- line_breaks: true,
173
- })
174
-
175
-
176
- $[$T.REGEX_START] = createToken({
177
- name: $T.REGEX_START,
178
- push_mode: "notRegex",
179
- pattern: /\//,
180
- categories: [$[$T.REGEX_ANY], $[$T.ANY]],
181
- })
182
- $[$T.REGEX_END] = createToken({
183
- name: $T.REGEX_END,
184
- push_mode: "main",
185
- pattern: /\/[a-z]*/,
186
- categories: [$[$T.REGEX_ANY], $[$T.ANY]],
187
- })
188
- $[$T.VALUE_REGEX] = createToken({
189
- name: $T.VALUE_REGEX,
190
- push_mode: "regexEnd",
191
- line_breaks: true,
192
- categories: [$[$T.ANY]],
193
- pattern: {
194
- exec: (text, start) => {
195
- let end = start
196
- let inGroup = 0
197
- let char = text[end]
198
- let prevEscaped = false
199
- while (char !== undefined && (char !== "/" || inGroup > 0 || prevEscaped)) {
200
- if (char === "[") inGroup++
201
- // normally something like /][/ will error, but we pretend the initial "negative" ] are ignored so things like /][]/ won't
202
- if (char === "]" && inGroup > 0) inGroup--
203
- if (char === "\\") {
204
- if (!prevEscaped) {
205
- prevEscaped = true
206
- } else {
207
- prevEscaped = false
208
- }
209
- } else {
210
- prevEscaped &&= false
211
- }
212
- end++
213
- char = text[end]
214
- }
215
- if (start === end) return null
216
- return [text.substring(start, end)]
217
- },
218
- },
219
- })
220
-
221
-
222
- $[$T.QUOTE_SINGLE_START] = createToken({
223
- name: $T.QUOTE_SINGLE_START,
224
- pattern: /'/,
225
- push_mode: "notSingle",
226
- categories: [$[$T.QUOTE_SINGLE], $[$T.QUOTE_ANY], $[$T.ANY]],
227
- })
228
- $[$T.QUOTE_DOUBLE_START] = createToken({
229
- name: $T.QUOTE_DOUBLE_START,
230
- pattern: /"/,
231
- push_mode: "notDouble",
232
- categories: [$[$T.QUOTE_DOUBLE], $[$T.QUOTE_ANY], $[$T.ANY]],
233
- })
234
- $[$T.QUOTE_BACKTICK_START] = createToken({
235
- name: $T.QUOTE_BACKTICK_START,
236
- pattern: /`/,
237
- push_mode: "notBacktick",
238
- categories: [$[$T.QUOTE_BACKTICK], $[$T.QUOTE_ANY], $[$T.ANY]],
239
- })
240
- $[$T.QUOTE_SINGLE_END] = createToken({
241
- name: $T.QUOTE_SINGLE_END,
242
- pattern: /'/,
243
- push_mode: "main",
244
- categories: [$[$T.QUOTE_SINGLE], $[$T.QUOTE_ANY], $[$T.ANY]],
245
- })
246
- $[$T.QUOTE_DOUBLE_END] = createToken({
247
- name: $T.QUOTE_DOUBLE_END,
248
- pattern: /"/,
249
- push_mode: "main",
250
- categories: [$[$T.QUOTE_DOUBLE], $[$T.QUOTE_ANY], $[$T.ANY]],
251
- })
252
- $[$T.QUOTE_BACKTICK_END] = createToken({
253
- name: $T.QUOTE_BACKTICK_END,
254
- pattern: /`/,
255
- push_mode: "main",
256
- categories: [$[$T.QUOTE_BACKTICK], $[$T.QUOTE_ANY], $[$T.ANY]],
257
- })
258
- $[$T.VALUE_NOT_SINGLE] = createToken({
259
- name: $T.VALUE_NOT_SINGLE,
260
- pattern: /(\\[\s\S]|[^'])+/,
261
- push_mode: "endQuotes",
262
- categories: [$[$T.VALUE], $[$T.VALUE_FOR_SINGLE], $[$T.ANY]],
263
- line_breaks: true,
264
- })
265
- $[$T.VALUE_NOT_DOUBLE] = createToken({
266
- name: $T.VALUE_NOT_DOUBLE,
267
- pattern: /(\\[\s\S]|[^"])+/,
268
- push_mode: "endQuotes",
269
- categories: [$[$T.VALUE], $[$T.VALUE_FOR_DOUBLE], $[$T.ANY]],
270
- line_breaks: true,
271
- })
272
- $[$T.VALUE_NOT_BACKTICK] = createToken({
273
- name: $T.VALUE_NOT_BACKTICK,
274
- pattern: /(\\[\s\S]|[^`])+/,
275
- push_mode: "endQuotes",
276
- categories: [$[$T.VALUE], $[$T.VALUE_FOR_BACKTICK], $[$T.ANY]],
277
- line_breaks: true,
278
- })
279
-
280
- const symOrs = opts.keywords.or.filter(_ => _.isSymbol).map(_ => escapeRegex(_.value))
281
- const symAnds = opts.keywords.and.filter(_ => _.isSymbol).map(_ => escapeRegex(_.value))
282
- const symNots = opts.keywords.not.filter(_ => _.isSymbol).map(_ => escapeRegex(_.value))
283
- const wordOrs = opts.keywords.or.filter(_ => !_.isSymbol).map(_ => escapeRegex(_.value))
284
- const wordAnds = opts.keywords.and.filter(_ => !_.isSymbol).map(_ => escapeRegex(_.value))
285
- const wordNots = opts.keywords.not.filter(_ => !_.isSymbol).map(_ => escapeRegex(_.value))
286
- let syms = [...symOrs, ...symAnds, ...symNots]
287
-
288
- const customPropertyOperators = (opts.customPropertyOperators ?? []).map(_ => escapeRegex(_))
289
-
290
- const expandedPropertySeparator = escapeRegex(opts.expandedPropertySeparator ?? "")
291
-
292
- if (expandedPropertySeparator) syms.push(expandedPropertySeparator)
293
- if (customPropertyOperators) syms = syms.concat(customPropertyOperators)
294
- if (opts.regexValues) syms.push("\\/")
295
- if (opts.arrayValues) {
296
- syms.push("\\[")
297
- // [ makes the lexer enter a bracket value, but ] should not be ignored by VALUE_UNQUOTED in case we get input like just `]` or `...]` which should be parsed as values
298
- }
299
-
300
- // future change to custom pattern, should be faster
301
- $[$T.VALUE_UNQUOTED] = createToken({
302
- name: $T.VALUE_UNQUOTED,
303
- pattern: new RegExp(`(\\\\[\\s\\S]|(${syms.length > 0 ? `(?!(${syms.join("|")}))` : ``}[^ \\t()'"\`\\\\]))+`),
304
- push_mode: "endQuotes",
305
- categories: [$[$T.VALUE], $[$T.ANY]],
306
- })
307
-
308
- $[$T.BRACKET_VALUE_UNQUOTED] = createToken({
309
- name: $T.BRACKET_VALUE_UNQUOTED,
310
- pattern: /(\\[\s\S]|([^ \]\\t'"`\\]))+/,
311
- push_mode: "bracket_endQuotes",
312
- categories: [$[$T.VALUE], $[$T.ANY]],
313
- })
314
-
315
- // operators are only added if they're enabled, otherwise cheverotain will complain about empty regex expressions
316
- const operators = []
317
-
318
- $[$T.SYM_OR] = createToken({
319
- name: $T.SYM_OR,
320
- pattern: new RegExp(`(${symOrs.join("|")})`),
321
- categories: [$[$T.OPERATOR_OR], $[$T.ANY]],
322
- })
323
- if (symOrs.length > 0) operators.push($[$T.SYM_OR])
324
-
325
- $[$T.SYM_AND] = createToken({
326
- name: $T.SYM_AND,
327
- pattern: new RegExp(`(${symAnds.join("|")})`),
328
- categories: [$[$T.OPERATOR_AND], $[$T.ANY]],
329
- })
330
- if (symAnds.length > 0) operators.push($[$T.SYM_AND])
331
-
332
- $[$T.SYM_NOT] = createToken({
333
- name: $T.SYM_NOT,
334
- pattern: new RegExp(`(${symNots.join("|")})`),
335
- categories: [$[$T.OPERATOR_NOT], $[$T.ANY]],
336
- })
337
- if (symNots.length > 0) operators.push($[$T.SYM_NOT])
338
-
339
- $[$T.WORD_OR] = createToken({
340
- name: $T.WORD_OR,
341
- pattern: new RegExp(`(${wordOrs.join("|")})`),
342
- longer_alt: $[$T.VALUE_UNQUOTED],
343
- categories: [$[$T.OPERATOR_OR], $[$T.ANY]],
344
- })
345
- if (wordOrs.length > 0) operators.push($[$T.WORD_OR])
346
-
347
- $[$T.WORD_AND] = createToken({
348
- name: $T.WORD_AND,
349
- pattern: new RegExp(`(${wordAnds.join("|")})`),
350
- longer_alt: $[$T.VALUE_UNQUOTED],
351
- categories: [$[$T.OPERATOR_AND], $[$T.ANY]],
352
- })
353
- if (wordAnds.length > 0) operators.push($[$T.WORD_AND])
354
-
355
- $[$T.WORD_NOT] = createToken({
356
- name: $T.WORD_NOT,
357
- pattern: new RegExp(`(${wordNots.join("|")})`),
358
- longer_alt: $[$T.VALUE_UNQUOTED],
359
- categories: [$[$T.OPERATOR_NOT], $[$T.ANY]],
360
- })
361
- if (wordNots.length > 0) operators.push($[$T.WORD_NOT])
362
-
363
-
364
- /* region Operators */
365
- $[$T.EXP_PROP_OP] = createToken({
366
- name: $T.EXP_PROP_OP,
367
- pattern: new RegExp(`${expandedPropertySeparator}`),
368
- categories: [$[$T.ANY]],
369
- })
370
-
371
- if (!isBlank(expandedPropertySeparator)) operators.splice(0, 0, $[$T.EXP_PROP_OP])
372
-
373
- $[$T.CUSTOM_PROP_OP] = createToken({
374
- name: $T.CUSTOM_PROP_OP,
375
- pattern: new RegExp(`(${customPropertyOperators.join("|")})`),
376
- categories: [$[$T.ANY]],
377
- })
378
-
379
- // only add custom operator if it's pattern doesn't match the not or expanded operator separator patterns (which can be the same)
380
- // otherwise chevrotain will complain when the regex patterns match exactly
381
- const customOpEqualsExpandedOrNegationToken = [$[$T.SYM_NOT].PATTERN, $[$T.EXP_PROP_OP].PATTERN]
382
- .find(_ => _?.toString() === $[$T.CUSTOM_PROP_OP].PATTERN?.toString()) !== undefined
383
-
384
- if (
385
- (customPropertyOperators?.length ?? 0) > 0 &&
386
- !customOpEqualsExpandedOrNegationToken
387
- ) operators.splice(1, 0, $[$T.CUSTOM_PROP_OP])
388
-
389
-
390
- // for parser
391
- const expandedSepAlsoCustom = customPropertyOperators.includes(expandedPropertySeparator)
392
- const customOpAlsoNegation = symNots.length > 0 &&
393
- customPropertyOperators?.find(_ => symNots.includes(_)) !== undefined
394
- /* regionend */
395
-
396
-
397
- $[$T.PAREN_L] = createToken({
398
- name: $T.PAREN_L,
399
- pattern: /\(/,
400
- categories: [$[$T.ANY]],
401
- })
402
- $[$T.PAREN_R] = createToken({
403
- name: $T.PAREN_R,
404
- pattern: /\)/,
405
- categories: [$[$T.ANY]],
406
- })
407
- const parens = [$[$T.PAREN_L], $[$T.PAREN_R]]
408
- // they still need to be defined for the parser, but if opts.arrayValues is false, they're never in any of the lexer modes
409
- $[$T.BRACKET_L] = createToken({
410
- name: $T.BRACKET_L,
411
- pattern: /\[/,
412
- push_mode: "bracket_main",
413
- categories: [$[$T.ANY]],
414
- })
415
- $[$T.BRACKET_R] = createToken({
416
- name: $T.BRACKET_R,
417
- pattern: /\]/,
418
- push_mode: "main",
419
- categories: [$[$T.ANY]],
420
- })
421
-
422
- const quotes = [
423
- $[$T.QUOTE_SINGLE_START],
424
- $[$T.QUOTE_DOUBLE_START],
425
- $[$T.QUOTE_BACKTICK_START],
426
- ]
427
-
428
- const quotesEnds = [
429
- $[$T.QUOTE_SINGLE_END],
430
- $[$T.QUOTE_DOUBLE_END],
431
- $[$T.QUOTE_BACKTICK_END],
432
- ]
433
-
434
- /* #regionend */
435
-
436
-
437
- const toBracket = (mode?: string): string => `bracket_${mode!}`
438
- const lexerOptions = {
439
- modes: {
440
- main: [
441
- $[$T._],
442
- ...parens,
443
- ...(opts.arrayValues ? [$[$T.BRACKET_L]] : []), // moves to bracket_main until a bracket pops it back out
444
- ...operators,
445
- ...quotes, // moves to not*
446
- ...(opts.regexValues ? [$[$T.REGEX_START]] : []), // moves to notRegex
447
- $[$T.VALUE_UNQUOTED], // moves to maybeQuoteError
448
- ],
449
- endQuotes: [
450
- $[$T._],
451
- ...parens,
452
- ...(opts.arrayValues ? [$[$T.BRACKET_L]] : []), // moves to bracket_main until a bracket pops it back out to main
453
- ...operators,
454
- ...quotesEnds,
455
- $[$T.VALUE_UNQUOTED], // moves to maybeQuoteError
456
- ...(opts.regexValues ? [$[$T.REGEX_START]] : []), // error
457
- ],
458
- // we can have situations like `a"` where the left quote is missing
459
- // we want the quote to match a quote end so that it pushes the state to main again, instead of shifting how everything is parsed
460
- maybeQuoteError: [
461
- ...quotesEnds,
462
- ...(opts.regexValues ? [$[$T.REGEX_END]] : []),
463
- ],
464
- // all move to endQuotes
465
- notSingle: [$[$T.VALUE_NOT_SINGLE], $[$T.QUOTE_SINGLE_END]],
466
- notDouble: [$[$T.VALUE_NOT_DOUBLE], $[$T.QUOTE_DOUBLE_END]],
467
- notBacktick: [$[$T.VALUE_NOT_BACKTICK], $[$T.QUOTE_BACKTICK_END]],
468
- ...(opts.regexValues
469
- ? {
470
- notRegex: [
471
- $[$T.VALUE_REGEX],
472
- $[$T.REGEX_END], // regex is empty
473
- ], // moves to regexEnd
474
- regexEnd: [$[$T.REGEX_END]], // moves to main
475
- } : {}),
476
- ...(opts.arrayValues
477
- ? {
478
- bracket_main: [
479
- ...changePushMode(quotes, toBracket),
480
- $[$T.BRACKET_R], // move back to main
481
- $[$T.BRACKET_VALUE_UNQUOTED],
482
- ],
483
- // all the following follow the same logic as the non-bracket modes, except operators and parens and regexes are not supported and are just parsed as values with BRACKET_VALUE_UNQUOTED
484
- // the following tokens are also cloned to push differently: quotes (above), quote values, and quote ends
485
- // they can still be matched because their parent categories are also cloned and it's those we match against
486
- bracket_endQuotes: [
487
- $[$T._],
488
- ...changePushMode(quotesEnds, toBracket),
489
- $[$T.BRACKET_R], // move back to main
490
- $[$T.BRACKET_VALUE_UNQUOTED],
491
- ],
492
- bracket_maybeQuoteError: changePushMode(quotesEnds, toBracket),
493
- bracket_notSingle: changePushMode([
494
- $[$T.VALUE_NOT_SINGLE], $[$T.QUOTE_SINGLE_END],
495
- ], toBracket),
496
- bracket_notDouble: changePushMode([
497
- $[$T.VALUE_NOT_DOUBLE], $[$T.QUOTE_DOUBLE_END],
498
- ], toBracket),
499
- bracket_notBacktick: changePushMode([
500
- $[$T.VALUE_NOT_BACKTICK], $[$T.QUOTE_BACKTICK_END],
501
- ], toBracket),
502
- } : {}
503
- ),
504
-
505
- },
506
- defaultMode: "main",
507
- }
508
-
509
- const lexer = new Lexer(lexerOptions) // only because we don't care about newlines
510
- return { tokens: $, lexer, info: { expandedSepAlsoCustom, customOpAlsoNegation } }
511
- }
512
-
@@ -1,4 +0,0 @@
1
- /* Autogenerated Index */
2
-
3
- export { createTokens } from "./createTokens.js"
4
- export { ParserBase } from "./ParserBase.js"
package/src/parser.ts DELETED
@@ -1,183 +0,0 @@
1
- /**
2
- * The parser's methods are often long and have a lot of documentation per method, so it's methods have been split into mixins. They can be found in the `./methods` folder.
3
- *
4
- * Writing from within any of these methods is like writing a method from here except:
5
- * - `this` calls are wrapped in `(this as any as Parser<T>)`
6
- * - private method/property access requires `// @ts-expect-error`.
7
- * - recursion with hidden parameters requires re-typing this (see evaluate/validate for examples) since otherwise if we only retyped the function it would become unbound from `this`.
8
- *
9
- * Docs work like normal (on methods). From the outside, users of the library cannot even tell the class is composed of mixins.
10
- */
11
-
12
- import type { Mixin } from "@alanscodelog/utils"
13
- import { isWhitespace, mixin } from "@alanscodelog/utils"
14
- import { createSyntaxDiagramsCode, type ILexingResult, type Lexer } from "chevrotain"
15
-
16
- import { token as tokenHandler } from "./ast/handlers.js"
17
- import { createTokens } from "./grammar/createTokens.js"
18
- import { ParserBase } from "./grammar/ParserBase.js"
19
- import { BooleanParserLibraryError } from "./helpers/errors.js"
20
- import { checkParserOpts } from "./helpers/parser/checkParserOpts.js"
21
- import { getUnclosedRightParenCount } from "./helpers/parser/getUnclosedRightParenCount.js"
22
- import { parseParserOptions, seal } from "./helpers/parser/index.js"
23
- import { AutocompleteMixin } from "./methods/autocomplete.js"
24
- import { AutoreplaceMixin } from "./methods/autoreplace.js"
25
- import { Autosuggest } from "./methods/autosuggest.js"
26
- import { EvaluateMixin } from "./methods/evaluate.js"
27
- import { GetBestIndexesMixin } from "./methods/getBestIndex.js"
28
- import { GetIndexMixin } from "./methods/getIndexes.js"
29
- import { NormalizeMixin, ValidateMixin } from "./methods/index.js"
30
- import type { ParserResults } from "./types/ast.js"
31
- import { ERROR_CODES } from "./types/errors.js"
32
- import type { FullParserOptions, ParserOptions } from "./types/parser.js"
33
-
34
-
35
- /**
36
- * Creates the main parser class which handles all functionality (evaluation, validation, etc).
37
- */
38
- export class Parser<T extends {} = {}> {
39
- options: FullParserOptions<T>
40
-
41
- private readonly rawOptions: ParserOptions<T>
42
-
43
- parser: ParserBase<T>
44
-
45
- private readonly lexer: Lexer
46
-
47
- private readonly tokens: ReturnType<typeof createTokens>["tokens"]
48
-
49
- info: ReturnType<typeof createTokens>["info"]
50
-
51
- constructor(options?: ParserOptions<T>) {
52
- this.rawOptions = options ?? {}
53
- const opts = parseParserOptions<T>(this.rawOptions)
54
- checkParserOpts<T>(opts)
55
- this.options = opts
56
- const { lexer, tokens, info } = createTokens<T>(opts)
57
- this.lexer = lexer
58
- this.tokens = tokens
59
- this.info = info
60
- this.parser = new ParserBase<T>(opts, this.tokens, this.info)
61
- }
62
-
63
- /**
64
- * Parses a string.
65
- */
66
- parse(input: string): ParserResults {
67
- if (isWhitespace(input)) {
68
- return tokenHandler.value(undefined, { start: 0, end: 0 }) as any
69
- }
70
-
71
- let lexed = this._lex(input)
72
-
73
- const shift = getUnclosedRightParenCount(lexed.tokens, this.tokens)
74
- if (shift) {
75
- input = "(".repeat(shift) + input
76
- lexed = this._lex(input)
77
- }
78
- this.parser.shift = shift
79
- this.parser.input = lexed.tokens
80
- this.parser.rawInput = input
81
-
82
- /**
83
- * The parser can't handle unmatched right parens (i.e. left is missing) so we just insert them and shift the locations of all the tokens. Then the parser is designed to ignore parenthesis we added at the start and just return undefined for that rule as if the parenthesis didn't exist.
84
- */
85
- try {
86
- if (lexed.errors.length > 0) throw new Error("Unexpected Lexer Errors")
87
- this.parser.input = lexed.tokens
88
- const res = this.parser.main()
89
- if (res === undefined) { throw new Error("throw") }
90
- // hidden param
91
- // eslint-disable-next-line prefer-rest-params
92
- if (!arguments[1]?.unsealed) seal(res)
93
- return res
94
- } catch (error: unknown) {
95
- // eslint-disable-next-line no-ex-assign
96
- if ((error as Error).message === "throw") error = undefined
97
- const err = new BooleanParserLibraryError(ERROR_CODES.PARSER_ERROR, {
98
- input,
99
- options: this.rawOptions,
100
- "parsed options": this.options,
101
- error: error as Error,
102
- "lexed tokens": lexed.tokens,
103
- "lexer errors": lexed.errors,
104
- "parser errors": this.parser.errors,
105
- })
106
-
107
- throw err
108
- }
109
- }
110
-
111
- // needed for evaluate and validate so they are only checked on demand
112
- private evaluationOptionsChecked: boolean = false
113
-
114
- // eslint-disable-next-line @typescript-eslint/naming-convention
115
- _checkEvaluationOptions(): void {
116
- if (!this.evaluationOptionsChecked) {
117
- checkParserOpts(this.options, true)
118
- this.evaluationOptionsChecked = true
119
- }
120
- }
121
-
122
- private validationOptionsChecked: boolean = false
123
-
124
- // eslint-disable-next-line @typescript-eslint/naming-convention
125
- _checkValidationOptions(): void {
126
- if (!this.validationOptionsChecked) {
127
- checkParserOpts(this.options, false, true)
128
- this.validationOptionsChecked = true
129
- }
130
- }
131
-
132
- /**
133
- * Generates a railroad diagram for debugging. Does not 100% represent how things are actually handled internally.
134
- *
135
- * Not exposed because it uses the raw chevrotain tokens.
136
- *
137
- * **Note: It is not 100% accurate. Some special cases are parsed one way but handled internally differently.**
138
- */
139
- private _generateRailRoadDiagram(): string {
140
- const serialized = this.parser.getSerializedGastProductions()
141
- const html = createSyntaxDiagramsCode(serialized)
142
- return html
143
- }
144
-
145
- /**
146
- * For debugging.
147
- * Not exposed because it returns the raw chevrotain tokens.
148
- */
149
- private _lex(input: string): ILexingResult {
150
- return this.lexer.tokenize(input)
151
- }
152
- }
153
-
154
- export interface Parser<T extends {} = {}> extends Mixin<
155
- | AutocompleteMixin<T>
156
- | AutoreplaceMixin
157
- | Autosuggest<T>
158
- | EvaluateMixin<T>
159
- | ValidateMixin<T>
160
- | NormalizeMixin<T>
161
- | GetIndexMixin<T>
162
- | GetBestIndexesMixin
163
- >,
164
- AutocompleteMixin<T>,
165
- AutoreplaceMixin,
166
- Autosuggest<T>,
167
- EvaluateMixin<T>,
168
- ValidateMixin < T >,
169
- NormalizeMixin<T>,
170
- GetIndexMixin<T>,
171
- GetBestIndexesMixin
172
- {}
173
-
174
- mixin(Parser, [
175
- AutocompleteMixin,
176
- AutoreplaceMixin,
177
- Autosuggest,
178
- EvaluateMixin,
179
- ValidateMixin,
180
- NormalizeMixin,
181
- GetIndexMixin,
182
- GetBestIndexesMixin,
183
- ])