@witchcraft/expressit 0.0.2

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 (307) hide show
  1. package/README.md +86 -0
  2. package/dist/ast/builders/array.d.ts +8 -0
  3. package/dist/ast/builders/array.d.ts.map +1 -0
  4. package/dist/ast/builders/array.js +31 -0
  5. package/dist/ast/builders/condition.d.ts +20 -0
  6. package/dist/ast/builders/condition.d.ts.map +1 -0
  7. package/dist/ast/builders/condition.js +28 -0
  8. package/dist/ast/builders/delim.d.ts +11 -0
  9. package/dist/ast/builders/delim.d.ts.map +1 -0
  10. package/dist/ast/builders/delim.js +20 -0
  11. package/dist/ast/builders/error.d.ts +9 -0
  12. package/dist/ast/builders/error.d.ts.map +1 -0
  13. package/dist/ast/builders/error.js +16 -0
  14. package/dist/ast/builders/expression.d.ts +12 -0
  15. package/dist/ast/builders/expression.d.ts.map +1 -0
  16. package/dist/ast/builders/expression.js +31 -0
  17. package/dist/ast/builders/group.d.ts +20 -0
  18. package/dist/ast/builders/group.d.ts.map +1 -0
  19. package/dist/ast/builders/group.js +47 -0
  20. package/dist/ast/builders/index.d.ts +12 -0
  21. package/dist/ast/builders/index.d.ts.map +1 -0
  22. package/dist/ast/builders/index.js +24 -0
  23. package/dist/ast/builders/isFullPos.d.ts +6 -0
  24. package/dist/ast/builders/isFullPos.d.ts.map +1 -0
  25. package/dist/ast/builders/isFullPos.js +6 -0
  26. package/dist/ast/builders/pos.d.ts +21 -0
  27. package/dist/ast/builders/pos.d.ts.map +1 -0
  28. package/dist/ast/builders/pos.js +28 -0
  29. package/dist/ast/builders/token.d.ts +12 -0
  30. package/dist/ast/builders/token.d.ts.map +1 -0
  31. package/dist/ast/builders/token.js +26 -0
  32. package/dist/ast/builders/type.d.ts +6 -0
  33. package/dist/ast/builders/type.d.ts.map +1 -0
  34. package/dist/ast/builders/type.js +37 -0
  35. package/dist/ast/builders/variable.d.ts +17 -0
  36. package/dist/ast/builders/variable.d.ts.map +1 -0
  37. package/dist/ast/builders/variable.js +62 -0
  38. package/dist/ast/classes/ArrayNode.d.ts +18 -0
  39. package/dist/ast/classes/ArrayNode.d.ts.map +1 -0
  40. package/dist/ast/classes/ArrayNode.js +55 -0
  41. package/dist/ast/classes/Condition.d.ts +13 -0
  42. package/dist/ast/classes/Condition.d.ts.map +1 -0
  43. package/dist/ast/classes/Condition.js +21 -0
  44. package/dist/ast/classes/ConditionNode.d.ts +73 -0
  45. package/dist/ast/classes/ConditionNode.d.ts.map +1 -0
  46. package/dist/ast/classes/ConditionNode.js +101 -0
  47. package/dist/ast/classes/ErrorToken.d.ts +27 -0
  48. package/dist/ast/classes/ErrorToken.d.ts.map +1 -0
  49. package/dist/ast/classes/ErrorToken.js +47 -0
  50. package/dist/ast/classes/Expression.d.ts +13 -0
  51. package/dist/ast/classes/Expression.d.ts.map +1 -0
  52. package/dist/ast/classes/Expression.js +19 -0
  53. package/dist/ast/classes/ExpressionNode.d.ts +21 -0
  54. package/dist/ast/classes/ExpressionNode.d.ts.map +1 -0
  55. package/dist/ast/classes/ExpressionNode.js +57 -0
  56. package/dist/ast/classes/GroupNode.d.ts +64 -0
  57. package/dist/ast/classes/GroupNode.d.ts.map +1 -0
  58. package/dist/ast/classes/GroupNode.js +69 -0
  59. package/dist/ast/classes/Node.d.ts +22 -0
  60. package/dist/ast/classes/Node.d.ts.map +1 -0
  61. package/dist/ast/classes/Node.js +28 -0
  62. package/dist/ast/classes/Token.d.ts +27 -0
  63. package/dist/ast/classes/Token.d.ts.map +1 -0
  64. package/dist/ast/classes/Token.js +28 -0
  65. package/dist/ast/classes/ValidToken.d.ts +26 -0
  66. package/dist/ast/classes/ValidToken.d.ts.map +1 -0
  67. package/dist/ast/classes/ValidToken.js +49 -0
  68. package/dist/ast/classes/VariableNode.d.ts +33 -0
  69. package/dist/ast/classes/VariableNode.d.ts.map +1 -0
  70. package/dist/ast/classes/VariableNode.js +58 -0
  71. package/dist/ast/classes/index.d.ts +12 -0
  72. package/dist/ast/classes/index.d.ts.map +1 -0
  73. package/dist/ast/classes/index.js +24 -0
  74. package/dist/ast/handlers.d.ts +42 -0
  75. package/dist/ast/handlers.d.ts.map +1 -0
  76. package/dist/ast/handlers.js +150 -0
  77. package/dist/ast/index.d.ts +4 -0
  78. package/dist/ast/index.d.ts.map +1 -0
  79. package/dist/ast/index.js +8 -0
  80. package/dist/examples/advancedValueComparer.d.ts +3 -0
  81. package/dist/examples/advancedValueComparer.d.ts.map +1 -0
  82. package/dist/examples/advancedValueComparer.js +28 -0
  83. package/dist/examples/shortcutContextParser.d.ts +22 -0
  84. package/dist/examples/shortcutContextParser.d.ts.map +1 -0
  85. package/dist/examples/shortcutContextParser.js +126 -0
  86. package/dist/global.d.js +1 -0
  87. package/dist/grammar/ParserBase.d.ts +51 -0
  88. package/dist/grammar/ParserBase.d.ts.map +1 -0
  89. package/dist/grammar/ParserBase.js +516 -0
  90. package/dist/grammar/createTokens.d.ts +56 -0
  91. package/dist/grammar/createTokens.d.ts.map +1 -0
  92. package/dist/grammar/createTokens.js +843 -0
  93. package/dist/grammar/index.d.ts +3 -0
  94. package/dist/grammar/index.d.ts.map +1 -0
  95. package/dist/grammar/index.js +6 -0
  96. package/dist/helpers/errors.d.ts +9 -0
  97. package/dist/helpers/errors.d.ts.map +1 -0
  98. package/dist/helpers/errors.js +41 -0
  99. package/dist/helpers/general/applyBoolean.d.ts +3 -0
  100. package/dist/helpers/general/applyBoolean.d.ts.map +1 -0
  101. package/dist/helpers/general/applyBoolean.js +17 -0
  102. package/dist/helpers/general/applyPrefix.d.ts +4 -0
  103. package/dist/helpers/general/applyPrefix.d.ts.map +1 -0
  104. package/dist/helpers/general/applyPrefix.js +9 -0
  105. package/dist/helpers/general/defaultConditionNormalizer.d.ts +3 -0
  106. package/dist/helpers/general/defaultConditionNormalizer.d.ts.map +1 -0
  107. package/dist/helpers/general/defaultConditionNormalizer.js +6 -0
  108. package/dist/helpers/general/defaultKeyParser.d.ts +3 -0
  109. package/dist/helpers/general/defaultKeyParser.d.ts.map +1 -0
  110. package/dist/helpers/general/defaultKeyParser.js +8 -0
  111. package/dist/helpers/general/defaultPrefixApplier.d.ts +3 -0
  112. package/dist/helpers/general/defaultPrefixApplier.d.ts.map +1 -0
  113. package/dist/helpers/general/defaultPrefixApplier.js +6 -0
  114. package/dist/helpers/general/defaultValueComparer.d.ts +3 -0
  115. package/dist/helpers/general/defaultValueComparer.d.ts.map +1 -0
  116. package/dist/helpers/general/defaultValueComparer.js +6 -0
  117. package/dist/helpers/general/index.d.ts +7 -0
  118. package/dist/helpers/general/index.d.ts.map +1 -0
  119. package/dist/helpers/general/index.js +14 -0
  120. package/dist/helpers/index.d.ts +4 -0
  121. package/dist/helpers/index.d.ts.map +1 -0
  122. package/dist/helpers/index.js +8 -0
  123. package/dist/helpers/parser/assignParents.d.ts +4 -0
  124. package/dist/helpers/parser/assignParents.d.ts.map +1 -0
  125. package/dist/helpers/parser/assignParents.js +71 -0
  126. package/dist/helpers/parser/checkParserOpts.d.ts +3 -0
  127. package/dist/helpers/parser/checkParserOpts.d.ts.map +1 -0
  128. package/dist/helpers/parser/checkParserOpts.js +126 -0
  129. package/dist/helpers/parser/extractPosition.d.ts +9 -0
  130. package/dist/helpers/parser/extractPosition.d.ts.map +1 -0
  131. package/dist/helpers/parser/extractPosition.js +9 -0
  132. package/dist/helpers/parser/getUnclosedRightParenCount.d.ts +5 -0
  133. package/dist/helpers/parser/getUnclosedRightParenCount.d.ts.map +1 -0
  134. package/dist/helpers/parser/getUnclosedRightParenCount.js +20 -0
  135. package/dist/helpers/parser/index.d.ts +9 -0
  136. package/dist/helpers/parser/index.d.ts.map +1 -0
  137. package/dist/helpers/parser/index.js +18 -0
  138. package/dist/helpers/parser/parseParserOptions.d.ts +4 -0
  139. package/dist/helpers/parser/parseParserOptions.d.ts.map +1 -0
  140. package/dist/helpers/parser/parseParserOptions.js +45 -0
  141. package/dist/helpers/parser/seal.d.ts +8 -0
  142. package/dist/helpers/parser/seal.d.ts.map +1 -0
  143. package/dist/helpers/parser/seal.js +10 -0
  144. package/dist/helpers/parser/setParent.d.ts +6 -0
  145. package/dist/helpers/parser/setParent.d.ts.map +1 -0
  146. package/dist/helpers/parser/setParent.js +4 -0
  147. package/dist/helpers/parser/unescape.d.ts +3 -0
  148. package/dist/helpers/parser/unescape.d.ts.map +1 -0
  149. package/dist/helpers/parser/unescape.js +6 -0
  150. package/dist/index.d.ts +6 -0
  151. package/dist/index.d.ts.map +1 -0
  152. package/dist/index.js +17 -0
  153. package/dist/methods/autocomplete.d.ts +18 -0
  154. package/dist/methods/autocomplete.d.ts.map +1 -0
  155. package/dist/methods/autocomplete.js +109 -0
  156. package/dist/methods/autoreplace.d.ts +13 -0
  157. package/dist/methods/autoreplace.d.ts.map +1 -0
  158. package/dist/methods/autoreplace.js +36 -0
  159. package/dist/methods/autosuggest.d.ts +28 -0
  160. package/dist/methods/autosuggest.d.ts.map +1 -0
  161. package/dist/methods/autosuggest.js +371 -0
  162. package/dist/methods/evaluate.d.ts +11 -0
  163. package/dist/methods/evaluate.d.ts.map +1 -0
  164. package/dist/methods/evaluate.js +30 -0
  165. package/dist/methods/getBestIndex.d.ts +19 -0
  166. package/dist/methods/getBestIndex.d.ts.map +1 -0
  167. package/dist/methods/getBestIndex.js +53 -0
  168. package/dist/methods/getIndexes.d.ts +17 -0
  169. package/dist/methods/getIndexes.d.ts.map +1 -0
  170. package/dist/methods/getIndexes.js +97 -0
  171. package/dist/methods/index.d.ts +9 -0
  172. package/dist/methods/index.d.ts.map +1 -0
  173. package/dist/methods/index.js +18 -0
  174. package/dist/methods/normalize.d.ts +12 -0
  175. package/dist/methods/normalize.d.ts.map +1 -0
  176. package/dist/methods/normalize.js +99 -0
  177. package/dist/methods/validate.d.ts +11 -0
  178. package/dist/methods/validate.d.ts.map +1 -0
  179. package/dist/methods/validate.js +111 -0
  180. package/dist/package.js +7 -0
  181. package/dist/package.json.js +193 -0
  182. package/dist/parser.d.ts +58 -0
  183. package/dist/parser.d.ts.map +1 -0
  184. package/dist/parser.js +136 -0
  185. package/dist/types/ast.d.ts +70 -0
  186. package/dist/types/ast.d.ts.map +1 -0
  187. package/dist/types/ast.js +29 -0
  188. package/dist/types/autocomplete.d.ts +143 -0
  189. package/dist/types/autocomplete.d.ts.map +1 -0
  190. package/dist/types/autocomplete.js +24 -0
  191. package/dist/types/errors.d.ts +34 -0
  192. package/dist/types/errors.d.ts.map +1 -0
  193. package/dist/types/errors.js +10 -0
  194. package/dist/types/index.d.ts +5 -0
  195. package/dist/types/index.d.ts.map +1 -0
  196. package/dist/types/index.js +9 -0
  197. package/dist/types/parser.d.ts +451 -0
  198. package/dist/types/parser.d.ts.map +1 -0
  199. package/dist/types/parser.js +1 -0
  200. package/dist/utils/extractTokens.d.ts +8 -0
  201. package/dist/utils/extractTokens.d.ts.map +1 -0
  202. package/dist/utils/extractTokens.js +50 -0
  203. package/dist/utils/getCursorInfo.d.ts +7 -0
  204. package/dist/utils/getCursorInfo.d.ts.map +1 -0
  205. package/dist/utils/getCursorInfo.js +86 -0
  206. package/dist/utils/getOppositeDelimiter.d.ts +6 -0
  207. package/dist/utils/getOppositeDelimiter.d.ts.map +1 -0
  208. package/dist/utils/getOppositeDelimiter.js +35 -0
  209. package/dist/utils/getSurroundingErrors.d.ts +25 -0
  210. package/dist/utils/getSurroundingErrors.d.ts.map +1 -0
  211. package/dist/utils/getSurroundingErrors.js +37 -0
  212. package/dist/utils/index.d.ts +10 -0
  213. package/dist/utils/index.d.ts.map +1 -0
  214. package/dist/utils/index.js +20 -0
  215. package/dist/utils/isBracket.d.ts +3 -0
  216. package/dist/utils/isBracket.d.ts.map +1 -0
  217. package/dist/utils/isBracket.js +7 -0
  218. package/dist/utils/isDelimiter.d.ts +6 -0
  219. package/dist/utils/isDelimiter.d.ts.map +1 -0
  220. package/dist/utils/isDelimiter.js +17 -0
  221. package/dist/utils/isParen.d.ts +3 -0
  222. package/dist/utils/isParen.d.ts.map +1 -0
  223. package/dist/utils/isParen.js +7 -0
  224. package/dist/utils/isQuote.d.ts +4 -0
  225. package/dist/utils/isQuote.d.ts.map +1 -0
  226. package/dist/utils/isQuote.js +7 -0
  227. package/dist/utils/prettyAst.d.ts +35 -0
  228. package/dist/utils/prettyAst.d.ts.map +1 -0
  229. package/dist/utils/prettyAst.js +112 -0
  230. package/package.json +152 -0
  231. package/src/ast/builders/array.ts +45 -0
  232. package/src/ast/builders/condition.ts +56 -0
  233. package/src/ast/builders/delim.ts +39 -0
  234. package/src/ast/builders/error.ts +22 -0
  235. package/src/ast/builders/expression.ts +66 -0
  236. package/src/ast/builders/group.ts +79 -0
  237. package/src/ast/builders/index.ts +13 -0
  238. package/src/ast/builders/isFullPos.ts +10 -0
  239. package/src/ast/builders/pos.ts +57 -0
  240. package/src/ast/builders/token.ts +46 -0
  241. package/src/ast/builders/type.ts +32 -0
  242. package/src/ast/builders/variable.ts +89 -0
  243. package/src/ast/classes/ArrayNode.ts +46 -0
  244. package/src/ast/classes/Condition.ts +22 -0
  245. package/src/ast/classes/ConditionNode.ts +141 -0
  246. package/src/ast/classes/ErrorToken.ts +49 -0
  247. package/src/ast/classes/Expression.ts +26 -0
  248. package/src/ast/classes/ExpressionNode.ts +62 -0
  249. package/src/ast/classes/GroupNode.ts +127 -0
  250. package/src/ast/classes/Node.ts +47 -0
  251. package/src/ast/classes/Token.ts +59 -0
  252. package/src/ast/classes/ValidToken.ts +56 -0
  253. package/src/ast/classes/VariableNode.ts +67 -0
  254. package/src/ast/classes/index.ts +13 -0
  255. package/src/ast/handlers.ts +190 -0
  256. package/src/ast/index.ts +5 -0
  257. package/src/examples/advancedValueComparer.ts +31 -0
  258. package/src/examples/shortcutContextParser.ts +140 -0
  259. package/src/global.d.ts +4 -0
  260. package/src/grammar/ParserBase.ts +715 -0
  261. package/src/grammar/createTokens.ts +512 -0
  262. package/src/grammar/index.ts +4 -0
  263. package/src/helpers/errors.ts +45 -0
  264. package/src/helpers/general/applyBoolean.ts +9 -0
  265. package/src/helpers/general/applyPrefix.ts +7 -0
  266. package/src/helpers/general/defaultConditionNormalizer.ts +9 -0
  267. package/src/helpers/general/defaultKeyParser.ts +8 -0
  268. package/src/helpers/general/defaultPrefixApplier.ts +7 -0
  269. package/src/helpers/general/defaultValueComparer.ts +7 -0
  270. package/src/helpers/general/index.ts +8 -0
  271. package/src/helpers/index.ts +5 -0
  272. package/src/helpers/parser/assignParents.ts +51 -0
  273. package/src/helpers/parser/checkParserOpts.ts +143 -0
  274. package/src/helpers/parser/extractPosition.ts +15 -0
  275. package/src/helpers/parser/getUnclosedRightParenCount.ts +22 -0
  276. package/src/helpers/parser/index.ts +10 -0
  277. package/src/helpers/parser/parseParserOptions.ts +54 -0
  278. package/src/helpers/parser/seal.ts +14 -0
  279. package/src/helpers/parser/setParent.ts +5 -0
  280. package/src/helpers/parser/unescape.ts +4 -0
  281. package/src/index.ts +7 -0
  282. package/src/methods/autocomplete.ts +128 -0
  283. package/src/methods/autoreplace.ts +46 -0
  284. package/src/methods/autosuggest.ts +543 -0
  285. package/src/methods/evaluate.ts +37 -0
  286. package/src/methods/getBestIndex.ts +53 -0
  287. package/src/methods/getIndexes.ts +99 -0
  288. package/src/methods/index.ts +10 -0
  289. package/src/methods/normalize.ts +138 -0
  290. package/src/methods/validate.ts +141 -0
  291. package/src/package.js +11 -0
  292. package/src/parser.ts +183 -0
  293. package/src/types/ast.ts +148 -0
  294. package/src/types/autocomplete.ts +152 -0
  295. package/src/types/errors.ts +40 -0
  296. package/src/types/index.ts +6 -0
  297. package/src/types/parser.ts +479 -0
  298. package/src/utils/extractTokens.ts +67 -0
  299. package/src/utils/getCursorInfo.ts +106 -0
  300. package/src/utils/getOppositeDelimiter.ts +36 -0
  301. package/src/utils/getSurroundingErrors.ts +57 -0
  302. package/src/utils/index.ts +11 -0
  303. package/src/utils/isBracket.ts +6 -0
  304. package/src/utils/isDelimiter.ts +18 -0
  305. package/src/utils/isParen.ts +6 -0
  306. package/src/utils/isQuote.ts +6 -0
  307. package/src/utils/prettyAst.ts +152 -0
@@ -0,0 +1,99 @@
1
+ /* eslint-disable no-labels */
2
+
3
+ import { type AddParameters, unreachable } from "@alanscodelog/utils"
4
+
5
+ import { Condition } from "../ast/classes/Condition.js"
6
+ import { Expression } from "../ast/classes/Expression.js"
7
+ import { TOKEN_TYPE } from "../types/ast.js"
8
+
9
+
10
+ export class GetIndexMixin<T> {
11
+ /**
12
+ * Returns a list of the different sets of keys that need to be indexed to run a normalized query on a database and hit an existing index.
13
+ *
14
+ * For example, the expression `a || b` requires both `a` AND `b` be indexed to use an index. The function would return `[Set(a, b)]`.
15
+ *
16
+ * On the otherhand, the expression `a && b` only requires `a` OR `b` to be indexed (`[Set(a), Set(b)]`) If at least one is indexed, the rest of the filtering can be done in memory. There is no need to in memory filter the entire database.
17
+ *
18
+ * Now take a more complicated query like `(a && b) || (a && c)`. This only requires `a` be indexed, or both `b` AND `c`. (`[Set(a)], [Set(b), Set(c)]`).
19
+ *
20
+ * Queries like `(a || b) && (a || c)` would require all the variables to be indexed `[Set(a), Set(b), Set(c)]`.
21
+ */
22
+ getIndexes(ast: Condition | Expression): Set<string>[] {
23
+ const self_ = this as any as GetIndexMixin<T> & { indexes: AddParameters<GetIndexMixin<T>["getIndexes"], []> }
24
+ if (ast instanceof Condition) {
25
+ return [new Set(ast.property.join("."))]
26
+ }
27
+ if (ast instanceof Expression) {
28
+ const left = self_.getIndexes(ast.left)
29
+ const right = self_.getIndexes(ast.right)
30
+
31
+ if (ast.operator === TOKEN_TYPE.AND) {
32
+ const sets: Set<string>[] = []
33
+ const allKeys: Set<string> = new Set()
34
+
35
+ for (const leftSet of left) {
36
+ const exists = sets.find(set => isEqualSet(set, leftSet))
37
+ if (exists) continue
38
+ sets.push(leftSet)
39
+ for (const key of leftSet) {
40
+ allKeys.add(key)
41
+ }
42
+ }
43
+ for (const rightSet of right) {
44
+ const exists = sets.find(set => isEqualSet(set, rightSet))
45
+ if (exists) continue
46
+ sets.push(rightSet)
47
+ for (const key of rightSet) {
48
+ allKeys.add(key)
49
+ }
50
+ }
51
+
52
+ const commonKeys: Set<string> = new Set()
53
+
54
+ outerCheck: for (const key of allKeys) {
55
+ for (const set of sets) {
56
+ if (!set.has(key)) continue outerCheck
57
+ }
58
+ commonKeys.add(key)
59
+ }
60
+ if (commonKeys.size > 0) {
61
+ return [commonKeys, allKeys]
62
+ } else {
63
+ return sets
64
+ }
65
+ }
66
+ if (ast.operator === TOKEN_TYPE.OR) {
67
+ for (const rightSet of right) {
68
+ for (const leftSet of left) {
69
+ if (isEqualSet(leftSet, rightSet)) {
70
+ return [rightSet]
71
+ }
72
+ }
73
+ }
74
+ const res = new Set<string>()
75
+ for (const leftSet of left) {
76
+ for (const key of leftSet) {
77
+ res.add(key)
78
+ }
79
+ }
80
+ for (const rightSet of right) {
81
+ for (const key of rightSet) {
82
+ res.add(key)
83
+ }
84
+ }
85
+ return [res]
86
+ }
87
+ }
88
+
89
+ return unreachable()
90
+ }
91
+ }
92
+
93
+ function isEqualSet(setA: Set<any>, setB: Set<any>): boolean {
94
+ if (setA.size !== setB.size) return false
95
+ for (const key of setA) {
96
+ if (!setB.has(key)) return false
97
+ }
98
+ return true
99
+ }
@@ -0,0 +1,10 @@
1
+ /* Autogenerated Index */
2
+
3
+ export { AutocompleteMixin } from "./autocomplete.js"
4
+ export { AutoreplaceMixin } from "./autoreplace.js"
5
+ export { Autosuggest } from "./autosuggest.js"
6
+ export { EvaluateMixin } from "./evaluate.js"
7
+ export { GetBestIndexesMixin } from "./getBestIndex.js"
8
+ export { GetIndexMixin } from "./getIndexes.js"
9
+ export { NormalizeMixin } from "./normalize.js"
10
+ export { ValidateMixin } from "./validate.js"
@@ -0,0 +1,138 @@
1
+ import { type AddParameters, unreachable } from "@alanscodelog/utils"
2
+
3
+ import { ArrayNode } from "../ast/classes/ArrayNode.js"
4
+ import { Condition } from "../ast/classes/Condition.js"
5
+ import { ConditionNode } from "../ast/classes/ConditionNode.js"
6
+ import { ErrorToken } from "../ast/classes/ErrorToken.js"
7
+ import { Expression } from "../ast/classes/Expression.js"
8
+ import { ExpressionNode } from "../ast/classes/ExpressionNode.js"
9
+ import { GroupNode } from "../ast/classes/GroupNode.js"
10
+ import { ValidToken } from "../ast/classes/ValidToken.js"
11
+ import { VariableNode } from "../ast/classes/VariableNode.js"
12
+ import { applyBoolean } from "../helpers/general/applyBoolean.js"
13
+ import { applyPrefix } from "../helpers/general/applyPrefix.js"
14
+ import type { Parser } from "../parser.js"
15
+ import { type ParserResults, TOKEN_TYPE, type TokenBooleanTypes } from "../types/ast.js"
16
+ import type { ValueQuery } from "../types/parser.js"
17
+
18
+
19
+ const OPPOSITE = {
20
+ [TOKEN_TYPE.AND]: TOKEN_TYPE.OR,
21
+ [TOKEN_TYPE.OR]: TOKEN_TYPE.AND,
22
+ }
23
+
24
+ export class NormalizeMixin<T extends {}> {
25
+ /**
26
+ * Normalizes the ast by applying {@link GroupNode GroupNodes} and converting {@link ConditionNode ConditionNodes} to {@link NormalizedConditionNode NormalizedConditionNodes}.
27
+ *
28
+ * To do this, a
29
+ */
30
+ normalize<TType extends string, TValue>(ast: ParserResults): Condition<TType, TValue> | Expression<TType, TValue> {
31
+ // @ts-expect-error private method
32
+ this._checkEvaluationOptions()
33
+ const opts = (this as any as Parser<T>).options
34
+ if (ast instanceof ErrorToken || !ast.valid) {
35
+ throw new Error("AST node must be valid.")
36
+ }
37
+ // eslint-disable-next-line prefer-rest-params
38
+ const prefix: string | undefined = arguments[1]
39
+ // eslint-disable-next-line prefer-rest-params
40
+ const groupValue: boolean | undefined = arguments[2]
41
+ // eslint-disable-next-line prefer-rest-params
42
+ let operator: string | undefined = arguments[3]
43
+
44
+ const self_ = this as any as NormalizeMixin<T> & { normalize: AddParameters<NormalizeMixin<T>["normalize"], [typeof prefix, typeof groupValue, typeof operator]> }
45
+
46
+ if (ast instanceof ConditionNode) {
47
+ if (!(ast.value instanceof GroupNode)) {
48
+ const isValue = ast.value instanceof ArrayNode || (ast.value as VariableNode)?.quote?.left.type === TOKEN_TYPE.REGEX
49
+ let name = ast.property
50
+ ? unescape(ast.property.value.value)
51
+ : isValue
52
+ // the property might be missing, whether this is valid or not is up to the user
53
+ // e.g. if prefix is defined this would make some sense
54
+ ? undefined
55
+ : unescape((ast.value as VariableNode)?.value.value)
56
+ // some ancestor node went through the else block because it was a group node (e.g. prop:op(val))
57
+ // so the "prefix" we passed is actually the name of the property (e.g. prop) and the value is the name we're getting here (e.g. val)
58
+ const isNested = operator !== undefined
59
+ if (prefix !== undefined && !isNested) {
60
+ name = name ? applyPrefix(prefix, name, opts.prefixApplier) : prefix
61
+ }
62
+ let value: any
63
+ if (isNested) {
64
+ value = name ?? true
65
+ name = prefix
66
+ } else {
67
+ value = ast.value instanceof ArrayNode
68
+ ? ast.value.values.map(val => unescape(val.value.value))
69
+ : (ast.value as VariableNode)?.quote?.left.type === TOKEN_TYPE.REGEX
70
+ ? ast.value.value.value
71
+ : ast.property && ast.value instanceof VariableNode
72
+ ? unescape(ast.value.value.value)
73
+ : true
74
+ }
75
+ const propertyKeys = name ? opts.keyParser(name) : []
76
+
77
+ const boolValue = applyBoolean(groupValue, ast.operator === undefined)
78
+ const valuePrefix = ast.value instanceof VariableNode && ast.value.prefix
79
+ ? unescape(ast.value.prefix.value)
80
+ : undefined
81
+ // one or the other might be defined, but never both since nested properties (e.g. `prop:op(prop:op(...))`) are not allowed
82
+ operator ??= ast.propertyOperator?.value
83
+ const isRegex = (ast.value as VariableNode)?.quote?.left.type === TOKEN_TYPE.REGEX
84
+ const isQuoted = (ast.value as VariableNode)?.quote !== undefined
85
+ const isExpanded = ast.sep !== undefined
86
+ const regexFlags = (ast.value as VariableNode)?.quote?.flags?.value
87
+ const query: ValueQuery = {
88
+ value,
89
+ operator,
90
+ prefix: valuePrefix,
91
+ regexFlags,
92
+ property: propertyKeys,
93
+ isRegex,
94
+ isQuoted,
95
+ isExpanded,
96
+ isNegated: !boolValue,
97
+ condition: ast,
98
+ }
99
+ const res = opts.conditionNormalizer(query)
100
+ return new Condition({ property: propertyKeys, ...res })
101
+ } else {
102
+ let name = unescape((ast.property as VariableNode).value.value) // this is always a variable node
103
+ if (prefix !== undefined) {
104
+ name = applyPrefix(prefix, name, opts.prefixApplier)
105
+ }
106
+ const boolValue = applyBoolean(groupValue, ast.operator === undefined)
107
+ // other operator is never defined see comments in other block above
108
+ // eslint-disable-next-line @typescript-eslint/no-shadow
109
+ const operator = ast.propertyOperator?.value
110
+ // this call will at some point lead us to the above block with isNested = true
111
+ return self_.normalize(ast.value, name, boolValue, operator) as any
112
+ }
113
+ }
114
+
115
+ if (ast instanceof GroupNode) {
116
+ const _prefix = ast.prefix instanceof ConditionNode && ast.prefix.value instanceof VariableNode
117
+ ? unescape(ast.prefix.value.value.value)
118
+ : undefined // we do not want to apply not tokens
119
+ const _groupValue = ast.prefix instanceof ConditionNode
120
+ ? ast.prefix.operator === undefined
121
+ : !(ast.prefix instanceof ValidToken)
122
+
123
+ const applied = applyPrefix(prefix, _prefix ?? "", opts.prefixApplier)
124
+
125
+ return self_.normalize(ast.expression as any, applied, applyBoolean(groupValue, _groupValue), operator) as any
126
+ }
127
+ if (ast instanceof ExpressionNode) {
128
+ const left = self_.normalize(ast.left, prefix, groupValue, operator)
129
+ const right = self_.normalize(ast.right, prefix, groupValue, operator)
130
+
131
+ // apply De Morgan's laws if group prefix was negative
132
+ // the values are already flipped, we just have to flip the operator
133
+ const type: TokenBooleanTypes = (groupValue === false ? OPPOSITE[ast.operator.type] : ast.operator.type) as TokenBooleanTypes
134
+ return new Expression<TType, TValue>({ operator: type, left: left as any, right: right as any })
135
+ }
136
+ return unreachable()
137
+ }
138
+ }
@@ -0,0 +1,141 @@
1
+ import { type AddParameters, get, isArray } from "@alanscodelog/utils"
2
+
3
+ import { ArrayNode } from "../ast/classes/ArrayNode.js"
4
+ import { ConditionNode } from "../ast/classes/ConditionNode.js"
5
+ import { ErrorToken } from "../ast/classes/ErrorToken.js"
6
+ import { ExpressionNode } from "../ast/classes/ExpressionNode.js"
7
+ import { GroupNode } from "../ast/classes/GroupNode.js"
8
+ import { ValidToken } from "../ast/classes/ValidToken.js"
9
+ import { VariableNode } from "../ast/classes/VariableNode.js"
10
+ import { applyBoolean } from "../helpers/general/applyBoolean.js"
11
+ import { applyPrefix } from "../helpers/general/applyPrefix.js"
12
+ import type { Parser } from "../parser.js"
13
+ import { type ParserResults, type Position, TOKEN_TYPE } from "../types/ast.js"
14
+ import type { ValidationQuery } from "../types/parser.js"
15
+
16
+
17
+ export class ValidateMixin<T extends {}> {
18
+ /**
19
+ * Allows pre-validating ASTs for syntax highlighting purposes.
20
+ * Works similar to evaluate. Internally it will use the prefixApplier, keyParser, and valueValidator (instead of comparer).
21
+ *
22
+ * The context does not need to be passed. If it's not passed, the function will not attempt to get the values (so it will not error) and the contextValue param of the valueValidator will be undefined.
23
+ */
24
+ validate(ast: ParserResults, context?: Record<string, any>): (Position & T)[] {
25
+ const self = (this as any as Parser<T>)
26
+ self._checkValidationOptions()
27
+ const opts = self.options
28
+ // see evaluate function, this method is practically identical, except we don't keep track of the real value (since we are not evaluating) and the actual nodes/tokens are passed to the valueValidator, not just the string values.
29
+ if (ast instanceof ErrorToken || !ast.valid) {
30
+ throw new Error("AST node must be valid.")
31
+ }
32
+ /** Handle hidden recursive version of the function. */
33
+ // eslint-disable-next-line prefer-rest-params
34
+ const prefix: string | undefined = arguments[2]
35
+ // eslint-disable-next-line prefer-rest-params
36
+ const groupValue: boolean | undefined = arguments[3]
37
+ // eslint-disable-next-line prefer-rest-params
38
+ const results: (Position & T)[] = arguments[4] ?? []
39
+ // eslint-disable-next-line prefer-rest-params
40
+ const prefixes: VariableNode[] = arguments[5] ?? []
41
+ // eslint-disable-next-line prefer-rest-params
42
+ let operator: ValidToken<TOKEN_TYPE.VALUE | TOKEN_TYPE.OP_CUSTOM> | undefined = arguments[6]
43
+
44
+ const self_ = this as any as ValidateMixin<T> & { validate: AddParameters<ValidateMixin<T>["validate"], [typeof prefix, typeof groupValue, typeof results, typeof prefixes, typeof operator]> }
45
+
46
+ if (ast instanceof ConditionNode) {
47
+ if (!(ast.value instanceof GroupNode)) {
48
+ const isValue = ast.value instanceof ArrayNode || (ast.value as VariableNode)?.quote?.left.type === TOKEN_TYPE.REGEX
49
+ const nameNode = ast.property
50
+ ? ast.property as VariableNode
51
+ : isValue
52
+ ? undefined
53
+ : ast.value as VariableNode
54
+
55
+ let name = nameNode ? unescape(nameNode.value.value) : undefined
56
+ const isNested = operator !== undefined
57
+ if (prefix !== undefined && !isNested) {
58
+ name = name ? applyPrefix(prefix, name, opts.prefixApplier) : prefix
59
+ }
60
+ let value: any
61
+ let propertyNodes: VariableNode[] = []
62
+
63
+ if (isNested) {
64
+ value = name
65
+ name = prefix
66
+ propertyNodes = [...prefixes]
67
+ } else {
68
+ propertyNodes = [...prefixes, ...(nameNode ? [nameNode] : [])]
69
+ value = ast.value instanceof ArrayNode
70
+ ? ast.value.values
71
+ : (ast.value as VariableNode)?.quote?.left.type === TOKEN_TYPE.REGEX
72
+ ? ast.value
73
+ : ast.property && ast.value instanceof VariableNode
74
+ ? ast.value
75
+ : true
76
+ }
77
+ const propertyKeys = name ? opts.keyParser(name) : []
78
+ const contextValue = context !== undefined ? get(context, propertyKeys) : undefined
79
+
80
+ const boolValue = applyBoolean(groupValue, ast.operator === undefined)
81
+ const valuePrefix = ast.value instanceof VariableNode && ast.value.prefix
82
+ ? ast.value.prefix
83
+ : undefined
84
+ operator ??= ast.propertyOperator as ValidToken<TOKEN_TYPE.VALUE | TOKEN_TYPE.OP_CUSTOM>
85
+ const isRegex = (ast.value as VariableNode)?.quote?.left.type === TOKEN_TYPE.REGEX
86
+ const isQuoted = (ast.value as VariableNode)?.quote !== undefined
87
+ const isExpanded = ast.sep !== undefined
88
+ const regexFlags = (ast.value as VariableNode)?.quote?.flags
89
+ const query: ValidationQuery = {
90
+ value,
91
+ operator,
92
+ prefix: valuePrefix,
93
+ prefixes,
94
+ property: propertyNodes,
95
+ propertyKeys,
96
+ propertyName: name,
97
+ regexFlags,
98
+ isRegex,
99
+ isNegated: !boolValue,
100
+ isQuoted,
101
+ isExpanded,
102
+ condition: ast,
103
+ }
104
+ const res = opts.valueValidator(contextValue, query, context)
105
+ if (res && !isArray(res)) throw new Error("The valueValidator must return an array or nothing/undefined")
106
+ if (res) { for (const entry of res) results.push(entry) }
107
+ } else {
108
+ let name = unescape((ast.property as VariableNode).value.value) // this is always a variable node
109
+ if (prefix !== undefined) {
110
+ name = applyPrefix(prefix, name, opts.prefixApplier)
111
+ }
112
+
113
+ const boolValue = applyBoolean(groupValue, ast.operator === undefined)
114
+
115
+ if (ast.property) prefixes.push((ast.property as any))
116
+ // eslint-disable-next-line @typescript-eslint/no-shadow
117
+ const operator = ast.propertyOperator as ValidToken<TOKEN_TYPE.VALUE | TOKEN_TYPE.OP_CUSTOM>
118
+ self_.validate(ast.value, context, name, boolValue, results, prefixes, operator)
119
+ }
120
+ }
121
+
122
+ if (ast instanceof GroupNode) {
123
+ const _prefix = ast.prefix instanceof ConditionNode && ast.prefix.value instanceof VariableNode
124
+ ? ast.prefix.value
125
+ : undefined // we do not want to apply not tokens
126
+ if (_prefix) prefixes.push(_prefix)
127
+
128
+ const _groupValue = ast.prefix instanceof ConditionNode
129
+ ? ast.prefix.operator === undefined
130
+ : !(ast.prefix instanceof ValidToken)
131
+
132
+ self_.validate(ast.expression as any, context, applyPrefix(prefix, _prefix?.value.value ?? "", opts.prefixApplier), applyBoolean(groupValue, _groupValue), results, prefixes, operator)
133
+ }
134
+ if (ast instanceof ExpressionNode) {
135
+ // prefixes must be spread because we don't want the left branch (if it goes deeper) to affect the right
136
+ self_.validate(ast.left, context, prefix, groupValue, results, [...prefixes], operator)
137
+ self_.validate(ast.right, context, prefix, groupValue, results, [...prefixes], operator)
138
+ }
139
+ return results
140
+ }
141
+ }
package/src/package.js ADDED
@@ -0,0 +1,11 @@
1
+ import pkg from "../package.json"
2
+
3
+ /**
4
+ * This library imports it's own package.json for inserting the version number into errors. Doing this is not a problem normally (because babel is used to transpile the typescript instead of tsc), but when tsc is used to output the types, the output is nested (dist-types/src/*) because of the json import being outside the src dir. There is a way around this but it's complicated. The easier way is to just cheat and do the importing in this js file. Typescript consumes it none the wiser and babel transpiles it to cjs/es accordingly.
5
+ *
6
+ * The values could be inlined, but there are no battle tested actively maintained babel plugins for this, and problems would be hard to debug (given when developing, the package.json never contains the real version because the library is semantically released.)
7
+ */
8
+
9
+
10
+ export const version = pkg.version
11
+ export const repository = pkg.repository
package/src/parser.ts ADDED
@@ -0,0 +1,183 @@
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
+ ])