flowquery 1.0.16 → 1.0.18

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 (319) hide show
  1. package/.github/workflows/python-publish.yml +96 -0
  2. package/dist/compute/runner.d.ts +3 -2
  3. package/dist/compute/runner.d.ts.map +1 -1
  4. package/dist/compute/runner.js +7 -7
  5. package/dist/compute/runner.js.map +1 -1
  6. package/dist/flowquery.min.js +1 -1
  7. package/dist/graph/data.d.ts +31 -0
  8. package/dist/graph/data.d.ts.map +1 -0
  9. package/dist/graph/data.js +110 -0
  10. package/dist/graph/data.js.map +1 -0
  11. package/dist/graph/database.d.ts +20 -0
  12. package/dist/graph/database.d.ts.map +1 -0
  13. package/dist/graph/database.js +77 -0
  14. package/dist/graph/database.js.map +1 -0
  15. package/dist/graph/hops.d.ts +11 -0
  16. package/dist/graph/hops.d.ts.map +1 -0
  17. package/dist/graph/hops.js +25 -0
  18. package/dist/graph/hops.js.map +1 -0
  19. package/dist/graph/node.d.ts +35 -0
  20. package/dist/graph/node.d.ts.map +1 -0
  21. package/dist/graph/node.js +113 -0
  22. package/dist/graph/node.js.map +1 -0
  23. package/dist/graph/node_data.d.ts +11 -0
  24. package/dist/graph/node_data.d.ts.map +1 -0
  25. package/dist/graph/node_data.js +20 -0
  26. package/dist/graph/node_data.js.map +1 -0
  27. package/dist/graph/node_reference.d.ts +10 -0
  28. package/dist/graph/node_reference.d.ts.map +1 -0
  29. package/dist/graph/node_reference.js +52 -0
  30. package/dist/graph/node_reference.js.map +1 -0
  31. package/dist/graph/pattern.d.ts +18 -0
  32. package/dist/graph/pattern.d.ts.map +1 -0
  33. package/dist/graph/pattern.js +114 -0
  34. package/dist/graph/pattern.js.map +1 -0
  35. package/dist/graph/pattern_expression.d.ts +15 -0
  36. package/dist/graph/pattern_expression.d.ts.map +1 -0
  37. package/dist/graph/pattern_expression.js +69 -0
  38. package/dist/graph/pattern_expression.js.map +1 -0
  39. package/dist/graph/patterns.d.ts +11 -0
  40. package/dist/graph/patterns.d.ts.map +1 -0
  41. package/dist/graph/patterns.js +49 -0
  42. package/dist/graph/patterns.js.map +1 -0
  43. package/dist/graph/physical_node.d.ts +10 -0
  44. package/dist/graph/physical_node.d.ts.map +1 -0
  45. package/dist/graph/physical_node.js +40 -0
  46. package/dist/graph/physical_node.js.map +1 -0
  47. package/dist/graph/physical_relationship.d.ts +10 -0
  48. package/dist/graph/physical_relationship.d.ts.map +1 -0
  49. package/dist/graph/physical_relationship.js +40 -0
  50. package/dist/graph/physical_relationship.js.map +1 -0
  51. package/dist/graph/relationship.d.ts +40 -0
  52. package/dist/graph/relationship.d.ts.map +1 -0
  53. package/dist/graph/relationship.js +124 -0
  54. package/dist/graph/relationship.js.map +1 -0
  55. package/dist/graph/relationship_data.d.ts +12 -0
  56. package/dist/graph/relationship_data.d.ts.map +1 -0
  57. package/dist/graph/relationship_data.js +40 -0
  58. package/dist/graph/relationship_data.js.map +1 -0
  59. package/dist/graph/relationship_match_collector.d.ts +19 -0
  60. package/dist/graph/relationship_match_collector.d.ts.map +1 -0
  61. package/dist/graph/relationship_match_collector.js +55 -0
  62. package/dist/graph/relationship_match_collector.js.map +1 -0
  63. package/dist/graph/relationship_reference.d.ts +8 -0
  64. package/dist/graph/relationship_reference.d.ts.map +1 -0
  65. package/dist/graph/relationship_reference.js +37 -0
  66. package/dist/graph/relationship_reference.js.map +1 -0
  67. package/dist/parsing/base_parser.d.ts +1 -0
  68. package/dist/parsing/base_parser.d.ts.map +1 -1
  69. package/dist/parsing/base_parser.js +4 -1
  70. package/dist/parsing/base_parser.js.map +1 -1
  71. package/dist/parsing/context.d.ts +2 -2
  72. package/dist/parsing/context.js +5 -5
  73. package/dist/parsing/expressions/boolean.d.ts +8 -0
  74. package/dist/parsing/expressions/boolean.d.ts.map +1 -0
  75. package/dist/parsing/expressions/boolean.js +26 -0
  76. package/dist/parsing/expressions/boolean.js.map +1 -0
  77. package/dist/parsing/expressions/expression.d.ts +4 -1
  78. package/dist/parsing/expressions/expression.d.ts.map +1 -1
  79. package/dist/parsing/expressions/expression.js +15 -8
  80. package/dist/parsing/expressions/expression.js.map +1 -1
  81. package/dist/parsing/expressions/operator.d.ts +1 -1
  82. package/dist/parsing/expressions/operator.d.ts.map +1 -1
  83. package/dist/parsing/expressions/operator.js.map +1 -1
  84. package/dist/parsing/functions/function_factory.d.ts +13 -13
  85. package/dist/parsing/functions/function_factory.d.ts.map +1 -1
  86. package/dist/parsing/functions/function_factory.js +20 -18
  87. package/dist/parsing/functions/function_factory.js.map +1 -1
  88. package/dist/parsing/operations/create_node.d.ts +14 -0
  89. package/dist/parsing/operations/create_node.d.ts.map +1 -0
  90. package/dist/parsing/operations/create_node.js +51 -0
  91. package/dist/parsing/operations/create_node.js.map +1 -0
  92. package/dist/parsing/operations/create_relationship.d.ts +14 -0
  93. package/dist/parsing/operations/create_relationship.d.ts.map +1 -0
  94. package/dist/parsing/operations/create_relationship.js +51 -0
  95. package/dist/parsing/operations/create_relationship.js.map +1 -0
  96. package/dist/parsing/operations/match.d.ts +15 -0
  97. package/dist/parsing/operations/match.d.ts.map +1 -0
  98. package/dist/parsing/operations/match.js +45 -0
  99. package/dist/parsing/operations/match.js.map +1 -0
  100. package/dist/parsing/operations/operation.d.ts +1 -0
  101. package/dist/parsing/operations/operation.d.ts.map +1 -1
  102. package/dist/parsing/operations/operation.js +6 -0
  103. package/dist/parsing/operations/operation.js.map +1 -1
  104. package/dist/parsing/operations/return.d.ts +1 -0
  105. package/dist/parsing/operations/return.d.ts.map +1 -1
  106. package/dist/parsing/operations/return.js +7 -1
  107. package/dist/parsing/operations/return.js.map +1 -1
  108. package/dist/parsing/operations/where.d.ts +1 -1
  109. package/dist/parsing/operations/where.d.ts.map +1 -1
  110. package/dist/parsing/operations/where.js +4 -0
  111. package/dist/parsing/operations/where.js.map +1 -1
  112. package/dist/parsing/parser.d.ts +10 -0
  113. package/dist/parsing/parser.d.ts.map +1 -1
  114. package/dist/parsing/parser.js +345 -5
  115. package/dist/parsing/parser.js.map +1 -1
  116. package/dist/parsing/token_to_node.d.ts.map +1 -1
  117. package/dist/parsing/token_to_node.js +7 -0
  118. package/dist/parsing/token_to_node.js.map +1 -1
  119. package/dist/tokenization/keyword.d.ts +1 -0
  120. package/dist/tokenization/keyword.d.ts.map +1 -1
  121. package/dist/tokenization/keyword.js +1 -0
  122. package/dist/tokenization/keyword.js.map +1 -1
  123. package/dist/tokenization/token.d.ts +4 -0
  124. package/dist/tokenization/token.d.ts.map +1 -1
  125. package/dist/tokenization/token.js +14 -1
  126. package/dist/tokenization/token.js.map +1 -1
  127. package/dist/tokenization/token_type.d.ts +1 -0
  128. package/dist/tokenization/token_type.d.ts.map +1 -1
  129. package/dist/tokenization/token_type.js +1 -0
  130. package/dist/tokenization/token_type.js.map +1 -1
  131. package/dist/tokenization/tokenizer.d.ts +2 -1
  132. package/dist/tokenization/tokenizer.d.ts.map +1 -1
  133. package/dist/tokenization/tokenizer.js +25 -12
  134. package/dist/tokenization/tokenizer.js.map +1 -1
  135. package/docs/flowquery.min.js +1 -1
  136. package/flowquery-py/CONTRIBUTING.md +127 -0
  137. package/flowquery-py/README.md +67 -0
  138. package/flowquery-py/pyproject.toml +75 -0
  139. package/flowquery-py/setup_env.ps1 +92 -0
  140. package/flowquery-py/setup_env.sh +87 -0
  141. package/flowquery-py/src/__init__.py +34 -0
  142. package/flowquery-py/src/__main__.py +10 -0
  143. package/flowquery-py/src/compute/__init__.py +5 -0
  144. package/flowquery-py/src/compute/runner.py +60 -0
  145. package/flowquery-py/src/extensibility.py +52 -0
  146. package/flowquery-py/src/graph/__init__.py +31 -0
  147. package/flowquery-py/src/graph/data.py +118 -0
  148. package/flowquery-py/src/graph/database.py +82 -0
  149. package/flowquery-py/src/graph/hops.py +43 -0
  150. package/flowquery-py/src/graph/node.py +112 -0
  151. package/flowquery-py/src/graph/node_data.py +26 -0
  152. package/flowquery-py/src/graph/node_reference.py +49 -0
  153. package/flowquery-py/src/graph/pattern.py +125 -0
  154. package/flowquery-py/src/graph/pattern_expression.py +65 -0
  155. package/flowquery-py/src/graph/patterns.py +42 -0
  156. package/flowquery-py/src/graph/physical_node.py +40 -0
  157. package/flowquery-py/src/graph/physical_relationship.py +36 -0
  158. package/flowquery-py/src/graph/relationship.py +135 -0
  159. package/flowquery-py/src/graph/relationship_data.py +33 -0
  160. package/flowquery-py/src/graph/relationship_match_collector.py +77 -0
  161. package/flowquery-py/src/graph/relationship_reference.py +21 -0
  162. package/flowquery-py/src/io/__init__.py +5 -0
  163. package/flowquery-py/src/io/command_line.py +109 -0
  164. package/flowquery-py/src/parsing/__init__.py +17 -0
  165. package/flowquery-py/src/parsing/alias.py +20 -0
  166. package/flowquery-py/src/parsing/alias_option.py +11 -0
  167. package/flowquery-py/src/parsing/ast_node.py +146 -0
  168. package/flowquery-py/src/parsing/base_parser.py +84 -0
  169. package/flowquery-py/src/parsing/components/__init__.py +19 -0
  170. package/flowquery-py/src/parsing/components/csv.py +8 -0
  171. package/flowquery-py/src/parsing/components/from_.py +10 -0
  172. package/flowquery-py/src/parsing/components/headers.py +12 -0
  173. package/flowquery-py/src/parsing/components/json.py +8 -0
  174. package/flowquery-py/src/parsing/components/null.py +10 -0
  175. package/flowquery-py/src/parsing/components/post.py +8 -0
  176. package/flowquery-py/src/parsing/components/text.py +8 -0
  177. package/flowquery-py/src/parsing/context.py +50 -0
  178. package/flowquery-py/src/parsing/data_structures/__init__.py +15 -0
  179. package/flowquery-py/src/parsing/data_structures/associative_array.py +41 -0
  180. package/flowquery-py/src/parsing/data_structures/json_array.py +30 -0
  181. package/flowquery-py/src/parsing/data_structures/key_value_pair.py +38 -0
  182. package/flowquery-py/src/parsing/data_structures/lookup.py +49 -0
  183. package/flowquery-py/src/parsing/data_structures/range_lookup.py +42 -0
  184. package/flowquery-py/src/parsing/expressions/__init__.py +57 -0
  185. package/flowquery-py/src/parsing/expressions/boolean.py +20 -0
  186. package/flowquery-py/src/parsing/expressions/expression.py +138 -0
  187. package/flowquery-py/src/parsing/expressions/expression_map.py +26 -0
  188. package/flowquery-py/src/parsing/expressions/f_string.py +27 -0
  189. package/flowquery-py/src/parsing/expressions/identifier.py +20 -0
  190. package/flowquery-py/src/parsing/expressions/number.py +32 -0
  191. package/flowquery-py/src/parsing/expressions/operator.py +169 -0
  192. package/flowquery-py/src/parsing/expressions/reference.py +47 -0
  193. package/flowquery-py/src/parsing/expressions/string.py +27 -0
  194. package/flowquery-py/src/parsing/functions/__init__.py +75 -0
  195. package/flowquery-py/src/parsing/functions/aggregate_function.py +60 -0
  196. package/flowquery-py/src/parsing/functions/async_function.py +62 -0
  197. package/flowquery-py/src/parsing/functions/avg.py +55 -0
  198. package/flowquery-py/src/parsing/functions/collect.py +75 -0
  199. package/flowquery-py/src/parsing/functions/function.py +68 -0
  200. package/flowquery-py/src/parsing/functions/function_factory.py +173 -0
  201. package/flowquery-py/src/parsing/functions/function_metadata.py +149 -0
  202. package/flowquery-py/src/parsing/functions/functions.py +59 -0
  203. package/flowquery-py/src/parsing/functions/join.py +47 -0
  204. package/flowquery-py/src/parsing/functions/keys.py +34 -0
  205. package/flowquery-py/src/parsing/functions/predicate_function.py +46 -0
  206. package/flowquery-py/src/parsing/functions/predicate_sum.py +47 -0
  207. package/flowquery-py/src/parsing/functions/rand.py +28 -0
  208. package/flowquery-py/src/parsing/functions/range_.py +34 -0
  209. package/flowquery-py/src/parsing/functions/reducer_element.py +15 -0
  210. package/flowquery-py/src/parsing/functions/replace.py +37 -0
  211. package/flowquery-py/src/parsing/functions/round_.py +32 -0
  212. package/flowquery-py/src/parsing/functions/size.py +32 -0
  213. package/flowquery-py/src/parsing/functions/split.py +47 -0
  214. package/flowquery-py/src/parsing/functions/stringify.py +47 -0
  215. package/flowquery-py/src/parsing/functions/sum.py +51 -0
  216. package/flowquery-py/src/parsing/functions/to_json.py +33 -0
  217. package/flowquery-py/src/parsing/functions/type_.py +47 -0
  218. package/flowquery-py/src/parsing/functions/value_holder.py +24 -0
  219. package/flowquery-py/src/parsing/logic/__init__.py +15 -0
  220. package/flowquery-py/src/parsing/logic/case.py +29 -0
  221. package/flowquery-py/src/parsing/logic/else_.py +12 -0
  222. package/flowquery-py/src/parsing/logic/end.py +8 -0
  223. package/flowquery-py/src/parsing/logic/then.py +12 -0
  224. package/flowquery-py/src/parsing/logic/when.py +10 -0
  225. package/flowquery-py/src/parsing/operations/__init__.py +35 -0
  226. package/flowquery-py/src/parsing/operations/aggregated_return.py +24 -0
  227. package/flowquery-py/src/parsing/operations/aggregated_with.py +22 -0
  228. package/flowquery-py/src/parsing/operations/call.py +74 -0
  229. package/flowquery-py/src/parsing/operations/create_node.py +34 -0
  230. package/flowquery-py/src/parsing/operations/create_relationship.py +34 -0
  231. package/flowquery-py/src/parsing/operations/group_by.py +130 -0
  232. package/flowquery-py/src/parsing/operations/limit.py +22 -0
  233. package/flowquery-py/src/parsing/operations/load.py +146 -0
  234. package/flowquery-py/src/parsing/operations/match.py +29 -0
  235. package/flowquery-py/src/parsing/operations/operation.py +69 -0
  236. package/flowquery-py/src/parsing/operations/projection.py +21 -0
  237. package/flowquery-py/src/parsing/operations/return_op.py +50 -0
  238. package/flowquery-py/src/parsing/operations/unwind.py +37 -0
  239. package/flowquery-py/src/parsing/operations/where.py +41 -0
  240. package/flowquery-py/src/parsing/operations/with_op.py +18 -0
  241. package/flowquery-py/src/parsing/parser.py +1010 -0
  242. package/flowquery-py/src/parsing/token_to_node.py +109 -0
  243. package/flowquery-py/src/tokenization/__init__.py +23 -0
  244. package/flowquery-py/src/tokenization/keyword.py +48 -0
  245. package/flowquery-py/src/tokenization/operator.py +29 -0
  246. package/flowquery-py/src/tokenization/string_walker.py +158 -0
  247. package/flowquery-py/src/tokenization/symbol.py +19 -0
  248. package/flowquery-py/src/tokenization/token.py +605 -0
  249. package/flowquery-py/src/tokenization/token_mapper.py +52 -0
  250. package/flowquery-py/src/tokenization/token_type.py +21 -0
  251. package/flowquery-py/src/tokenization/tokenizer.py +214 -0
  252. package/flowquery-py/src/tokenization/trie.py +124 -0
  253. package/flowquery-py/src/utils/__init__.py +6 -0
  254. package/flowquery-py/src/utils/object_utils.py +20 -0
  255. package/flowquery-py/src/utils/string_utils.py +113 -0
  256. package/flowquery-py/tests/__init__.py +1 -0
  257. package/flowquery-py/tests/compute/__init__.py +1 -0
  258. package/flowquery-py/tests/compute/test_runner.py +1335 -0
  259. package/flowquery-py/tests/graph/__init__.py +1 -0
  260. package/flowquery-py/tests/graph/test_create.py +56 -0
  261. package/flowquery-py/tests/graph/test_data.py +73 -0
  262. package/flowquery-py/tests/graph/test_match.py +40 -0
  263. package/flowquery-py/tests/parsing/__init__.py +1 -0
  264. package/flowquery-py/tests/parsing/test_context.py +34 -0
  265. package/flowquery-py/tests/parsing/test_expression.py +49 -0
  266. package/flowquery-py/tests/parsing/test_parser.py +680 -0
  267. package/flowquery-py/tests/test_extensibility.py +611 -0
  268. package/flowquery-py/tests/tokenization/__init__.py +1 -0
  269. package/flowquery-py/tests/tokenization/test_token_mapper.py +60 -0
  270. package/flowquery-py/tests/tokenization/test_tokenizer.py +164 -0
  271. package/flowquery-py/tests/tokenization/test_trie.py +30 -0
  272. package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
  273. package/misc/apps/RAG/package.json +1 -1
  274. package/misc/apps/RAG/src/components/AdaptiveCardRenderer.tsx +76 -8
  275. package/misc/apps/RAG/src/components/index.ts +19 -10
  276. package/misc/apps/RAG/src/plugins/loaders/MockData.ts +70 -140
  277. package/misc/apps/RAG/src/prompts/FlowQuerySystemPrompt.ts +12 -0
  278. package/package.json +1 -1
  279. package/src/compute/runner.ts +24 -19
  280. package/src/graph/data.ts +112 -0
  281. package/src/graph/database.ts +63 -0
  282. package/src/graph/hops.ts +22 -0
  283. package/src/graph/node.ts +99 -0
  284. package/src/graph/node_data.ts +18 -0
  285. package/src/graph/node_reference.ts +33 -0
  286. package/src/graph/pattern.ts +101 -0
  287. package/src/graph/pattern_expression.ts +48 -0
  288. package/src/graph/patterns.ts +36 -0
  289. package/src/graph/physical_node.ts +23 -0
  290. package/src/graph/physical_relationship.ts +23 -0
  291. package/src/graph/relationship.ts +116 -0
  292. package/src/graph/relationship_data.ts +27 -0
  293. package/src/graph/relationship_match_collector.ts +58 -0
  294. package/src/graph/relationship_reference.ts +24 -0
  295. package/src/parsing/base_parser.ts +20 -14
  296. package/src/parsing/context.ts +14 -14
  297. package/src/parsing/expressions/boolean.ts +21 -0
  298. package/src/parsing/expressions/expression.ts +34 -26
  299. package/src/parsing/expressions/operator.ts +19 -1
  300. package/src/parsing/functions/function_factory.ts +45 -45
  301. package/src/parsing/operations/create_node.ts +39 -0
  302. package/src/parsing/operations/create_relationship.ts +38 -0
  303. package/src/parsing/operations/match.ts +31 -0
  304. package/src/parsing/operations/operation.ts +3 -0
  305. package/src/parsing/operations/return.ts +11 -7
  306. package/src/parsing/operations/where.ts +10 -6
  307. package/src/parsing/parser.ts +349 -8
  308. package/src/parsing/token_to_node.ts +6 -0
  309. package/src/tokenization/keyword.ts +41 -40
  310. package/src/tokenization/token.ts +21 -1
  311. package/src/tokenization/token_type.ts +2 -1
  312. package/src/tokenization/tokenizer.ts +52 -31
  313. package/tests/compute/runner.test.ts +654 -0
  314. package/tests/extensibility.test.ts +97 -93
  315. package/tests/graph/create.test.ts +36 -0
  316. package/tests/graph/data.test.ts +58 -0
  317. package/tests/graph/match.test.ts +29 -0
  318. package/tests/parsing/parser.test.ts +281 -2
  319. package/tests/tokenization/tokenizer.test.ts +90 -0
@@ -0,0 +1,1010 @@
1
+ """Main parser for FlowQuery statements."""
2
+
3
+ from typing import Dict, Iterator, List, Optional
4
+
5
+ from ..tokenization.token import Token
6
+ from ..utils.object_utils import ObjectUtils
7
+ from .alias import Alias
8
+ from .alias_option import AliasOption
9
+ from .ast_node import ASTNode
10
+ from .base_parser import BaseParser
11
+ from .context import Context
12
+ from .components.from_ import From
13
+ from .components.headers import Headers
14
+ from .components.null import Null
15
+ from .components.post import Post
16
+ from .data_structures.associative_array import AssociativeArray
17
+ from .data_structures.json_array import JSONArray
18
+ from .data_structures.key_value_pair import KeyValuePair
19
+ from .data_structures.lookup import Lookup
20
+ from .data_structures.range_lookup import RangeLookup
21
+ from .expressions.expression import Expression
22
+ from .expressions.f_string import FString
23
+ from .expressions.identifier import Identifier
24
+ from .expressions.operator import Not
25
+ from .expressions.reference import Reference
26
+ from .expressions.string import String
27
+ from .functions.aggregate_function import AggregateFunction
28
+ from .functions.async_function import AsyncFunction
29
+ from .functions.function import Function
30
+ from .functions.function_factory import FunctionFactory
31
+ from .functions.predicate_function import PredicateFunction
32
+ from .logic.case import Case
33
+ from .logic.when import When
34
+ from .logic.then import Then
35
+ from .logic.else_ import Else
36
+ from .operations.aggregated_return import AggregatedReturn
37
+ from .operations.aggregated_with import AggregatedWith
38
+ from .operations.call import Call
39
+ from .operations.limit import Limit
40
+ from .operations.load import Load
41
+ from .operations.match import Match
42
+ from .operations.operation import Operation
43
+ from .operations.return_op import Return
44
+ from .operations.unwind import Unwind
45
+ from .operations.where import Where
46
+ from .operations.with_op import With
47
+ from ..graph.node import Node
48
+ from ..graph.node_reference import NodeReference
49
+ from ..graph.pattern import Pattern
50
+ from ..graph.pattern_expression import PatternExpression
51
+ from ..graph.relationship import Relationship
52
+ from .operations.create_node import CreateNode
53
+ from .operations.create_relationship import CreateRelationship
54
+
55
+
56
+ class Parser(BaseParser):
57
+ """Main parser for FlowQuery statements.
58
+
59
+ Parses FlowQuery declarative query language statements into an Abstract Syntax Tree (AST).
60
+ Supports operations like WITH, UNWIND, RETURN, LOAD, WHERE, and LIMIT, along with
61
+ expressions, functions, data structures, and logical constructs.
62
+
63
+ Example:
64
+ parser = Parser()
65
+ ast = parser.parse("unwind [1, 2, 3, 4, 5] as num return num")
66
+ """
67
+
68
+ def __init__(self, tokens: Optional[List[Token]] = None):
69
+ super().__init__(tokens)
70
+ self._variables: Dict[str, ASTNode] = {}
71
+ self._context = Context()
72
+ self._returns = 0
73
+
74
+ def parse(self, statement: str) -> ASTNode:
75
+ """Parses a FlowQuery statement into an Abstract Syntax Tree.
76
+
77
+ Args:
78
+ statement: The FlowQuery statement to parse
79
+
80
+ Returns:
81
+ The root AST node containing the parsed structure
82
+
83
+ Raises:
84
+ ValueError: If the statement is malformed or contains syntax errors
85
+ """
86
+ self.tokenize(statement)
87
+ return self._parse_tokenized()
88
+
89
+ def _parse_tokenized(self, is_sub_query: bool = False) -> ASTNode:
90
+ root = ASTNode()
91
+ previous: Optional[Operation] = None
92
+ operation: Optional[Operation] = None
93
+
94
+ while not self.token.is_eof():
95
+ if root.child_count() > 0:
96
+ self._expect_and_skip_whitespace_and_comments()
97
+ else:
98
+ self._skip_whitespace_and_comments()
99
+
100
+ operation = self._parse_operation()
101
+ if operation is None and not is_sub_query:
102
+ raise ValueError("Expected one of WITH, UNWIND, RETURN, LOAD, OR CALL")
103
+ elif operation is None and is_sub_query:
104
+ return root
105
+
106
+ if self._returns > 1:
107
+ raise ValueError("Only one RETURN statement is allowed")
108
+
109
+ if isinstance(previous, Call) and not previous.has_yield:
110
+ raise ValueError(
111
+ "CALL operations must have a YIELD clause unless they are the last operation"
112
+ )
113
+
114
+ if previous is not None:
115
+ previous.add_sibling(operation)
116
+ else:
117
+ root.add_child(operation)
118
+
119
+ where = self._parse_where()
120
+ if where is not None:
121
+ if isinstance(operation, Return):
122
+ operation.where = where
123
+ else:
124
+ operation.add_sibling(where)
125
+ operation = where
126
+
127
+ limit = self._parse_limit()
128
+ if limit is not None:
129
+ operation.add_sibling(limit)
130
+ operation = limit
131
+
132
+ previous = operation
133
+
134
+ if not isinstance(operation, (Return, Call, CreateNode, CreateRelationship)):
135
+ raise ValueError("Last statement must be a RETURN, WHERE, CALL, or CREATE statement")
136
+
137
+ return root
138
+
139
+ def _parse_operation(self) -> Optional[Operation]:
140
+ return (
141
+ self._parse_with() or
142
+ self._parse_unwind() or
143
+ self._parse_return() or
144
+ self._parse_load() or
145
+ self._parse_call() or
146
+ self._parse_match() or
147
+ self._parse_create()
148
+ )
149
+
150
+ def _parse_with(self) -> Optional[With]:
151
+ if not self.token.is_with():
152
+ return None
153
+ self.set_next_token()
154
+ self._expect_and_skip_whitespace_and_comments()
155
+ expressions = list(self._parse_expressions(AliasOption.REQUIRED))
156
+ if len(expressions) == 0:
157
+ raise ValueError("Expected expression")
158
+ if any(expr.has_reducers() for expr in expressions):
159
+ return AggregatedWith(expressions)
160
+ return With(expressions)
161
+
162
+ def _parse_unwind(self) -> Optional[Unwind]:
163
+ if not self.token.is_unwind():
164
+ return None
165
+ self.set_next_token()
166
+ self._expect_and_skip_whitespace_and_comments()
167
+ expression = self._parse_expression()
168
+ if expression is None:
169
+ raise ValueError("Expected expression")
170
+ if not ObjectUtils.is_instance_of_any(
171
+ expression.first_child(),
172
+ [JSONArray, Function, Reference, Lookup, RangeLookup]
173
+ ):
174
+ raise ValueError("Expected array, function, reference, or lookup.")
175
+ self._expect_and_skip_whitespace_and_comments()
176
+ alias = self._parse_alias()
177
+ if alias is not None:
178
+ expression.set_alias(alias.get_alias())
179
+ else:
180
+ raise ValueError("Expected alias")
181
+ unwind = Unwind(expression)
182
+ self._variables[alias.get_alias()] = unwind
183
+ return unwind
184
+
185
+ def _parse_return(self) -> Optional[Return]:
186
+ if not self.token.is_return():
187
+ return None
188
+ self.set_next_token()
189
+ self._expect_and_skip_whitespace_and_comments()
190
+ expressions = list(self._parse_expressions(AliasOption.OPTIONAL))
191
+ if len(expressions) == 0:
192
+ raise ValueError("Expected expression")
193
+ if any(expr.has_reducers() for expr in expressions):
194
+ return AggregatedReturn(expressions)
195
+ self._returns += 1
196
+ return Return(expressions)
197
+
198
+ def _parse_where(self) -> Optional[Where]:
199
+ if not self.token.is_where():
200
+ return None
201
+ self._expect_previous_token_to_be_whitespace_or_comment()
202
+ self.set_next_token()
203
+ self._expect_and_skip_whitespace_and_comments()
204
+ expression = self._parse_expression()
205
+ if expression is None:
206
+ raise ValueError("Expected expression")
207
+ if ObjectUtils.is_instance_of_any(
208
+ expression.first_child(),
209
+ [JSONArray, AssociativeArray]
210
+ ):
211
+ raise ValueError("Expected an expression which can be evaluated to a boolean")
212
+ return Where(expression)
213
+
214
+ def _parse_load(self) -> Optional[Load]:
215
+ if not self.token.is_load():
216
+ return None
217
+ load = Load()
218
+ self.set_next_token()
219
+ self._expect_and_skip_whitespace_and_comments()
220
+ if not (self.token.is_json() or self.token.is_csv() or self.token.is_text()):
221
+ raise ValueError("Expected JSON, CSV, or TEXT")
222
+ load.add_child(self.token.node)
223
+ self.set_next_token()
224
+ self._expect_and_skip_whitespace_and_comments()
225
+ if not self.token.is_from():
226
+ raise ValueError("Expected FROM")
227
+ self.set_next_token()
228
+ self._expect_and_skip_whitespace_and_comments()
229
+ from_node = From()
230
+ load.add_child(from_node)
231
+
232
+ # Check if source is async function
233
+ async_func = self._parse_async_function()
234
+ if async_func is not None:
235
+ from_node.add_child(async_func)
236
+ else:
237
+ expression = self._parse_expression()
238
+ if expression is None:
239
+ raise ValueError("Expected expression or async function")
240
+ from_node.add_child(expression)
241
+
242
+ self._expect_and_skip_whitespace_and_comments()
243
+ if self.token.is_headers():
244
+ headers = Headers()
245
+ self.set_next_token()
246
+ self._expect_and_skip_whitespace_and_comments()
247
+ header = self._parse_expression()
248
+ if header is None:
249
+ raise ValueError("Expected expression")
250
+ headers.add_child(header)
251
+ load.add_child(headers)
252
+ self._expect_and_skip_whitespace_and_comments()
253
+
254
+ if self.token.is_post():
255
+ post = Post()
256
+ self.set_next_token()
257
+ self._expect_and_skip_whitespace_and_comments()
258
+ payload = self._parse_expression()
259
+ if payload is None:
260
+ raise ValueError("Expected expression")
261
+ post.add_child(payload)
262
+ load.add_child(post)
263
+ self._expect_and_skip_whitespace_and_comments()
264
+
265
+ alias = self._parse_alias()
266
+ if alias is not None:
267
+ load.add_child(alias)
268
+ self._variables[alias.get_alias()] = load
269
+ else:
270
+ raise ValueError("Expected alias")
271
+ return load
272
+
273
+ def _parse_call(self) -> Optional[Call]:
274
+ if not self.token.is_call():
275
+ return None
276
+ self.set_next_token()
277
+ self._expect_and_skip_whitespace_and_comments()
278
+ async_function = self._parse_async_function()
279
+ if async_function is None:
280
+ raise ValueError("Expected async function")
281
+ call = Call()
282
+ call.function = async_function
283
+ self._skip_whitespace_and_comments()
284
+ if self.token.is_yield():
285
+ self._expect_previous_token_to_be_whitespace_or_comment()
286
+ self.set_next_token()
287
+ self._expect_and_skip_whitespace_and_comments()
288
+ expressions = list(self._parse_expressions(AliasOption.OPTIONAL))
289
+ if len(expressions) == 0:
290
+ raise ValueError("Expected at least one expression")
291
+ call.yielded = expressions
292
+ return call
293
+
294
+ def _parse_match(self) -> Optional[Match]:
295
+ if not self.token.is_match():
296
+ return None
297
+ self.set_next_token()
298
+ self._expect_and_skip_whitespace_and_comments()
299
+ patterns = list(self._parse_patterns())
300
+ if len(patterns) == 0:
301
+ raise ValueError("Expected graph pattern")
302
+ return Match(patterns)
303
+
304
+ def _parse_create(self) -> Optional[Operation]:
305
+ """Parse CREATE VIRTUAL statement for nodes and relationships."""
306
+ if not self.token.is_create():
307
+ return None
308
+ self.set_next_token()
309
+ self._expect_and_skip_whitespace_and_comments()
310
+ if not self.token.is_virtual():
311
+ raise ValueError("Expected VIRTUAL")
312
+ self.set_next_token()
313
+ self._expect_and_skip_whitespace_and_comments()
314
+
315
+ node = self._parse_node()
316
+ if node is None:
317
+ raise ValueError("Expected node definition")
318
+
319
+ relationship: Optional[Relationship] = None
320
+ if self.token.is_subtract() and self.peek() and self.peek().is_opening_bracket():
321
+ self.set_next_token() # skip -
322
+ self.set_next_token() # skip [
323
+ if not self.token.is_colon():
324
+ raise ValueError("Expected ':' for relationship type")
325
+ self.set_next_token()
326
+ if not self.token.is_identifier():
327
+ raise ValueError("Expected relationship type identifier")
328
+ rel_type = self.token.value or ""
329
+ self.set_next_token()
330
+ if not self.token.is_closing_bracket():
331
+ raise ValueError("Expected closing bracket for relationship definition")
332
+ self.set_next_token()
333
+ if not self.token.is_subtract():
334
+ raise ValueError("Expected '-' for relationship definition")
335
+ self.set_next_token()
336
+ # Skip optional direction indicator '>'
337
+ if self.token.is_greater_than():
338
+ self.set_next_token()
339
+ target = self._parse_node()
340
+ if target is None:
341
+ raise ValueError("Expected target node definition")
342
+ relationship = Relationship()
343
+ relationship.type = rel_type
344
+
345
+ self._expect_and_skip_whitespace_and_comments()
346
+ if not self.token.is_as():
347
+ raise ValueError("Expected AS")
348
+ self.set_next_token()
349
+ self._expect_and_skip_whitespace_and_comments()
350
+
351
+ query = self._parse_sub_query()
352
+ if query is None:
353
+ raise ValueError("Expected sub-query")
354
+
355
+ if relationship is not None:
356
+ return CreateRelationship(relationship, query)
357
+ else:
358
+ return CreateNode(node, query)
359
+
360
+ def _parse_sub_query(self) -> Optional[ASTNode]:
361
+ """Parse a sub-query enclosed in braces."""
362
+ if not self.token.is_opening_brace():
363
+ return None
364
+ self.set_next_token()
365
+ self._expect_and_skip_whitespace_and_comments()
366
+ query = self._parse_tokenized(is_sub_query=True)
367
+ self._skip_whitespace_and_comments()
368
+ if not self.token.is_closing_brace():
369
+ raise ValueError("Expected closing brace for sub-query")
370
+ self.set_next_token()
371
+ return query
372
+
373
+ def _parse_patterns(self) -> Iterator[Pattern]:
374
+ while True:
375
+ identifier: Optional[str] = None
376
+ if self.token.is_identifier():
377
+ identifier = self.token.value
378
+ self.set_next_token()
379
+ self._skip_whitespace_and_comments()
380
+ if not self.token.is_equals():
381
+ raise ValueError("Expected '=' for pattern assignment")
382
+ self.set_next_token()
383
+ self._skip_whitespace_and_comments()
384
+ pattern = self._parse_pattern()
385
+ if pattern is not None:
386
+ if identifier is not None:
387
+ pattern.identifier = identifier
388
+ self._variables[identifier] = pattern
389
+ yield pattern
390
+ else:
391
+ break
392
+ self._skip_whitespace_and_comments()
393
+ if not self.token.is_comma():
394
+ break
395
+ self.set_next_token()
396
+ self._skip_whitespace_and_comments()
397
+
398
+ def _parse_pattern(self) -> Optional[Pattern]:
399
+ if not self.token.is_left_parenthesis():
400
+ return None
401
+ pattern = Pattern()
402
+ node = self._parse_node()
403
+ if node is None:
404
+ raise ValueError("Expected node definition")
405
+ pattern.add_element(node)
406
+ while True:
407
+ relationship = self._parse_relationship()
408
+ if relationship is None:
409
+ break
410
+ pattern.add_element(relationship)
411
+ node = self._parse_node()
412
+ if node is None:
413
+ raise ValueError("Expected target node definition")
414
+ pattern.add_element(node)
415
+ return pattern
416
+
417
+ def _parse_pattern_expression(self) -> Optional[PatternExpression]:
418
+ """Parse a pattern expression for WHERE clauses.
419
+
420
+ PatternExpression is used to test if a graph pattern exists.
421
+ It must start with a NodeReference (referencing an existing variable).
422
+ """
423
+ if not self.token.is_left_parenthesis():
424
+ return None
425
+ pattern = PatternExpression()
426
+ node = self._parse_node()
427
+ if node is None:
428
+ raise ValueError("Expected node definition")
429
+ pattern.add_element(node)
430
+ while True:
431
+ relationship = self._parse_relationship()
432
+ if relationship is None:
433
+ break
434
+ if relationship.hops and relationship.hops.multi():
435
+ raise ValueError("PatternExpression does not support variable-length relationships")
436
+ pattern.add_element(relationship)
437
+ node = self._parse_node()
438
+ if node is None:
439
+ raise ValueError("Expected target node definition")
440
+ pattern.add_element(node)
441
+ pattern.verify()
442
+ return pattern
443
+
444
+ def _parse_node(self) -> Optional[Node]:
445
+ if not self.token.is_left_parenthesis():
446
+ return None
447
+ self.set_next_token()
448
+ self._skip_whitespace_and_comments()
449
+ identifier: Optional[str] = None
450
+ if self.token.is_identifier():
451
+ identifier = self.token.value
452
+ self.set_next_token()
453
+ self._skip_whitespace_and_comments()
454
+ label: Optional[str] = None
455
+ peek = self.peek()
456
+ if not self.token.is_colon() and peek is not None and peek.is_identifier():
457
+ raise ValueError("Expected ':' for node label")
458
+ if self.token.is_colon() and (peek is None or not peek.is_identifier()):
459
+ raise ValueError("Expected node label identifier")
460
+ if self.token.is_colon() and peek is not None and peek.is_identifier():
461
+ self.set_next_token()
462
+ label = self.token.value
463
+ self.set_next_token()
464
+ self._skip_whitespace_and_comments()
465
+ node = Node()
466
+ node.label = label
467
+ if label is not None and identifier is not None:
468
+ node.identifier = identifier
469
+ self._variables[identifier] = node
470
+ elif identifier is not None:
471
+ reference = self._variables.get(identifier)
472
+ from ..graph.node_reference import NodeReference
473
+ if reference is None or not isinstance(reference, Node):
474
+ raise ValueError(f"Undefined node reference: {identifier}")
475
+ node = NodeReference(node, reference)
476
+ if not self.token.is_right_parenthesis():
477
+ raise ValueError("Expected closing parenthesis for node definition")
478
+ self.set_next_token()
479
+ return node
480
+
481
+ def _parse_relationship(self) -> Optional[Relationship]:
482
+ if self.token.is_less_than() and self.peek() is not None and self.peek().is_subtract():
483
+ self.set_next_token()
484
+ self.set_next_token()
485
+ elif self.token.is_subtract():
486
+ self.set_next_token()
487
+ else:
488
+ return None
489
+ if not self.token.is_opening_bracket():
490
+ return None
491
+ self.set_next_token()
492
+ variable: Optional[str] = None
493
+ if self.token.is_identifier():
494
+ variable = self.token.value
495
+ self.set_next_token()
496
+ if not self.token.is_colon():
497
+ raise ValueError("Expected ':' for relationship type")
498
+ self.set_next_token()
499
+ if not self.token.is_identifier():
500
+ raise ValueError("Expected relationship type identifier")
501
+ rel_type: str = self.token.value or ""
502
+ self.set_next_token()
503
+ hops = self._parse_relationship_hops()
504
+ if not self.token.is_closing_bracket():
505
+ raise ValueError("Expected closing bracket for relationship definition")
506
+ self.set_next_token()
507
+ if not self.token.is_subtract():
508
+ raise ValueError("Expected '-' for relationship definition")
509
+ self.set_next_token()
510
+ if self.token.is_greater_than():
511
+ self.set_next_token()
512
+ relationship = Relationship()
513
+ if rel_type is not None and variable is not None:
514
+ relationship.identifier = variable
515
+ self._variables[variable] = relationship
516
+ elif variable is not None:
517
+ reference = self._variables.get(variable)
518
+ from ..graph.relationship_reference import RelationshipReference
519
+ if reference is None or not isinstance(reference, Relationship):
520
+ raise ValueError(f"Undefined relationship reference: {variable}")
521
+ relationship = RelationshipReference(relationship, reference)
522
+ if hops is not None:
523
+ relationship.hops = hops
524
+ relationship.type = rel_type
525
+ return relationship
526
+
527
+ def _parse_relationship_hops(self):
528
+ import sys
529
+ from ..graph.hops import Hops
530
+ if not self.token.is_multiply():
531
+ return None
532
+ hops = Hops()
533
+ self.set_next_token()
534
+ if self.token.is_number():
535
+ hops.min = int(self.token.value or "0")
536
+ self.set_next_token()
537
+ if self.token.is_dot():
538
+ self.set_next_token()
539
+ if not self.token.is_dot():
540
+ raise ValueError("Expected '..' for relationship hops")
541
+ self.set_next_token()
542
+ if not self.token.is_number():
543
+ raise ValueError("Expected number for relationship hops")
544
+ hops.max = int(self.token.value or "0")
545
+ self.set_next_token()
546
+ else:
547
+ # Just * without numbers means unbounded
548
+ hops.min = 0
549
+ hops.max = sys.maxsize
550
+ return hops
551
+
552
+ def _parse_limit(self) -> Optional[Limit]:
553
+ self._skip_whitespace_and_comments()
554
+ if not self.token.is_limit():
555
+ return None
556
+ self._expect_previous_token_to_be_whitespace_or_comment()
557
+ self.set_next_token()
558
+ self._expect_and_skip_whitespace_and_comments()
559
+ if not self.token.is_number():
560
+ raise ValueError("Expected number")
561
+ limit = Limit(int(self.token.value or "0"))
562
+ self.set_next_token()
563
+ return limit
564
+
565
+ def _parse_expressions(
566
+ self, alias_option: AliasOption = AliasOption.NOT_ALLOWED
567
+ ) -> Iterator[Expression]:
568
+ while True:
569
+ expression = self._parse_expression()
570
+ if expression is not None:
571
+ alias = self._parse_alias()
572
+ if isinstance(expression.first_child(), Reference) and alias is None:
573
+ reference = expression.first_child()
574
+ expression.set_alias(reference.identifier)
575
+ self._variables[reference.identifier] = expression
576
+ elif (alias_option == AliasOption.REQUIRED and
577
+ alias is None and
578
+ not isinstance(expression.first_child(), Reference)):
579
+ raise ValueError("Alias required")
580
+ elif alias_option == AliasOption.NOT_ALLOWED and alias is not None:
581
+ raise ValueError("Alias not allowed")
582
+ elif alias_option in (AliasOption.OPTIONAL, AliasOption.REQUIRED) and alias is not None:
583
+ expression.set_alias(alias.get_alias())
584
+ self._variables[alias.get_alias()] = expression
585
+ yield expression
586
+ else:
587
+ break
588
+ self._skip_whitespace_and_comments()
589
+ if not self.token.is_comma():
590
+ break
591
+ self.set_next_token()
592
+
593
+ def _parse_expression(self) -> Optional[Expression]:
594
+ expression = Expression()
595
+ while True:
596
+ self._skip_whitespace_and_comments()
597
+ if self.token.is_identifier() and (self.peek() is None or not self.peek().is_left_parenthesis()):
598
+ identifier = self.token.value or ""
599
+ reference = Reference(identifier, self._variables.get(identifier))
600
+ self.set_next_token()
601
+ lookup = self._parse_lookup(reference)
602
+ expression.add_node(lookup)
603
+ elif self.token.is_identifier() and self.peek() is not None and self.peek().is_left_parenthesis():
604
+ func = self._parse_predicate_function() or self._parse_function()
605
+ if func is not None:
606
+ lookup = self._parse_lookup(func)
607
+ expression.add_node(lookup)
608
+ elif self.token.is_left_parenthesis() and self.peek() is not None and (self.peek().is_identifier() or self.peek().is_colon() or self.peek().is_right_parenthesis()):
609
+ # Possible graph pattern expression
610
+ pattern = self._parse_pattern_expression()
611
+ if pattern is not None:
612
+ expression.add_node(pattern)
613
+ elif self.token.is_operand():
614
+ expression.add_node(self.token.node)
615
+ self.set_next_token()
616
+ elif self.token.is_f_string():
617
+ f_string = self._parse_f_string()
618
+ if f_string is None:
619
+ raise ValueError("Expected f-string")
620
+ expression.add_node(f_string)
621
+ elif self.token.is_left_parenthesis():
622
+ self.set_next_token()
623
+ sub = self._parse_expression()
624
+ if sub is None:
625
+ raise ValueError("Expected expression")
626
+ if not self.token.is_right_parenthesis():
627
+ raise ValueError("Expected right parenthesis")
628
+ self.set_next_token()
629
+ lookup = self._parse_lookup(sub)
630
+ expression.add_node(lookup)
631
+ elif self.token.is_opening_brace() or self.token.is_opening_bracket():
632
+ json = self._parse_json()
633
+ if json is None:
634
+ raise ValueError("Expected JSON object")
635
+ lookup = self._parse_lookup(json)
636
+ expression.add_node(lookup)
637
+ elif self.token.is_case():
638
+ case = self._parse_case()
639
+ if case is None:
640
+ raise ValueError("Expected CASE statement")
641
+ expression.add_node(case)
642
+ elif self.token.is_not():
643
+ not_node = Not()
644
+ self.set_next_token()
645
+ sub = self._parse_expression()
646
+ if sub is None:
647
+ raise ValueError("Expected expression")
648
+ not_node.add_child(sub)
649
+ expression.add_node(not_node)
650
+ else:
651
+ if expression.nodes_added():
652
+ raise ValueError("Expected operand or left parenthesis")
653
+ else:
654
+ break
655
+ self._skip_whitespace_and_comments()
656
+ if self.token.is_operator():
657
+ expression.add_node(self.token.node)
658
+ else:
659
+ break
660
+ self.set_next_token()
661
+
662
+ if expression.nodes_added():
663
+ expression.finish()
664
+ return expression
665
+ return None
666
+
667
+ def _parse_lookup(self, node: ASTNode) -> ASTNode:
668
+ variable = node
669
+ lookup = None
670
+ while True:
671
+ if self.token.is_dot():
672
+ self.set_next_token()
673
+ if not self.token.is_identifier() and not self.token.is_keyword():
674
+ raise ValueError("Expected identifier")
675
+ lookup = Lookup()
676
+ lookup.index = Identifier(self.token.value or "")
677
+ lookup.variable = variable
678
+ self.set_next_token()
679
+ elif self.token.is_opening_bracket():
680
+ self.set_next_token()
681
+ self._skip_whitespace_and_comments()
682
+ index = self._parse_expression()
683
+ to = None
684
+ self._skip_whitespace_and_comments()
685
+ if self.token.is_colon():
686
+ self.set_next_token()
687
+ self._skip_whitespace_and_comments()
688
+ lookup = RangeLookup()
689
+ to = self._parse_expression()
690
+ else:
691
+ if index is None:
692
+ raise ValueError("Expected expression")
693
+ lookup = Lookup()
694
+ self._skip_whitespace_and_comments()
695
+ if not self.token.is_closing_bracket():
696
+ raise ValueError("Expected closing bracket")
697
+ self.set_next_token()
698
+ if isinstance(lookup, RangeLookup):
699
+ lookup.from_ = index or Null()
700
+ lookup.to = to or Null()
701
+ elif isinstance(lookup, Lookup) and index is not None:
702
+ lookup.index = index
703
+ lookup.variable = variable
704
+ else:
705
+ break
706
+ variable = lookup or variable
707
+ return variable
708
+
709
+ def _parse_case(self) -> Optional[Case]:
710
+ if not self.token.is_case():
711
+ return None
712
+ self.set_next_token()
713
+ case = Case()
714
+ parts = 0
715
+ self._expect_and_skip_whitespace_and_comments()
716
+ while True:
717
+ when = self._parse_when()
718
+ if when is None and parts == 0:
719
+ raise ValueError("Expected WHEN")
720
+ elif when is None and parts > 0:
721
+ break
722
+ elif when is not None:
723
+ case.add_child(when)
724
+ self._expect_and_skip_whitespace_and_comments()
725
+ then = self._parse_then()
726
+ if then is None:
727
+ raise ValueError("Expected THEN")
728
+ else:
729
+ case.add_child(then)
730
+ self._expect_and_skip_whitespace_and_comments()
731
+ parts += 1
732
+ else_ = self._parse_else()
733
+ if else_ is None:
734
+ raise ValueError("Expected ELSE")
735
+ else:
736
+ case.add_child(else_)
737
+ self._expect_and_skip_whitespace_and_comments()
738
+ if not self.token.is_end():
739
+ raise ValueError("Expected END")
740
+ self.set_next_token()
741
+ return case
742
+
743
+ def _parse_when(self) -> Optional[When]:
744
+ if not self.token.is_when():
745
+ return None
746
+ self.set_next_token()
747
+ when = When()
748
+ self._expect_and_skip_whitespace_and_comments()
749
+ expression = self._parse_expression()
750
+ if expression is None:
751
+ raise ValueError("Expected expression")
752
+ when.add_child(expression)
753
+ return when
754
+
755
+ def _parse_then(self) -> Optional[Then]:
756
+ if not self.token.is_then():
757
+ return None
758
+ self.set_next_token()
759
+ then = Then()
760
+ self._expect_and_skip_whitespace_and_comments()
761
+ expression = self._parse_expression()
762
+ if expression is None:
763
+ raise ValueError("Expected expression")
764
+ then.add_child(expression)
765
+ return then
766
+
767
+ def _parse_else(self) -> Optional[Else]:
768
+ if not self.token.is_else():
769
+ return None
770
+ self.set_next_token()
771
+ else_ = Else()
772
+ self._expect_and_skip_whitespace_and_comments()
773
+ expression = self._parse_expression()
774
+ if expression is None:
775
+ raise ValueError("Expected expression")
776
+ else_.add_child(expression)
777
+ return else_
778
+
779
+ def _parse_alias(self) -> Optional[Alias]:
780
+ self._skip_whitespace_and_comments()
781
+ if not self.token.is_as():
782
+ return None
783
+ self._expect_previous_token_to_be_whitespace_or_comment()
784
+ self.set_next_token()
785
+ self._expect_and_skip_whitespace_and_comments()
786
+ if not self.token.is_identifier():
787
+ raise ValueError("Expected identifier")
788
+ alias = Alias(self.token.value or "")
789
+ self.set_next_token()
790
+ return alias
791
+
792
+ def _parse_predicate_function(self) -> Optional[PredicateFunction]:
793
+ """Parse a predicate function like sum(n in [...] | n where condition)."""
794
+ # Lookahead: identifier ( identifier in
795
+ if not self.ahead([
796
+ Token.IDENTIFIER(""),
797
+ Token.LEFT_PARENTHESIS(),
798
+ Token.IDENTIFIER(""),
799
+ Token.IN(),
800
+ ]):
801
+ return None
802
+ if self.token.value is None:
803
+ raise ValueError("Expected identifier")
804
+ func = FunctionFactory.create_predicate(self.token.value)
805
+ self.set_next_token()
806
+ if not self.token.is_left_parenthesis():
807
+ raise ValueError("Expected left parenthesis")
808
+ self.set_next_token()
809
+ self._skip_whitespace_and_comments()
810
+ if not self.token.is_identifier():
811
+ raise ValueError("Expected identifier")
812
+ reference = Reference(self.token.value)
813
+ self._variables[reference.identifier] = reference
814
+ func.add_child(reference)
815
+ self.set_next_token()
816
+ self._expect_and_skip_whitespace_and_comments()
817
+ if not self.token.is_in():
818
+ raise ValueError("Expected IN")
819
+ self.set_next_token()
820
+ self._expect_and_skip_whitespace_and_comments()
821
+ expression = self._parse_expression()
822
+ if expression is None:
823
+ raise ValueError("Expected expression")
824
+ if not ObjectUtils.is_instance_of_any(expression.first_child(), [
825
+ JSONArray,
826
+ Reference,
827
+ Lookup,
828
+ Function,
829
+ ]):
830
+ raise ValueError("Expected array or reference")
831
+ func.add_child(expression)
832
+ self._skip_whitespace_and_comments()
833
+ if not self.token.is_pipe():
834
+ raise ValueError("Expected pipe")
835
+ self.set_next_token()
836
+ return_expr = self._parse_expression()
837
+ if return_expr is None:
838
+ raise ValueError("Expected expression")
839
+ func.add_child(return_expr)
840
+ where = self._parse_where()
841
+ if where is not None:
842
+ func.add_child(where)
843
+ self._skip_whitespace_and_comments()
844
+ if not self.token.is_right_parenthesis():
845
+ raise ValueError("Expected right parenthesis")
846
+ self.set_next_token()
847
+ del self._variables[reference.identifier]
848
+ return func
849
+
850
+ def _parse_function(self) -> Optional[Function]:
851
+ if not self.token.is_identifier():
852
+ return None
853
+ name = self.token.value or ""
854
+ if not self.peek() or not self.peek().is_left_parenthesis():
855
+ return None
856
+
857
+ try:
858
+ func = FunctionFactory.create(name)
859
+ except ValueError:
860
+ raise ValueError(f"Unknown function: {name}")
861
+
862
+ # Check for nested aggregate functions
863
+ if isinstance(func, AggregateFunction) and self._context.contains_type(AggregateFunction):
864
+ raise ValueError("Aggregate functions cannot be nested")
865
+
866
+ self._context.push(func)
867
+ self.set_next_token() # skip function name
868
+ self.set_next_token() # skip left parenthesis
869
+ self._skip_whitespace_and_comments()
870
+
871
+ # Check for DISTINCT keyword
872
+ if self.token.is_distinct():
873
+ func.distinct = True
874
+ self.set_next_token()
875
+ self._expect_and_skip_whitespace_and_comments()
876
+
877
+ params = list(self._parse_function_parameters())
878
+ func.parameters = params
879
+
880
+ if not self.token.is_right_parenthesis():
881
+ raise ValueError("Expected right parenthesis")
882
+ self.set_next_token()
883
+ self._context.pop()
884
+ return func
885
+
886
+ def _parse_async_function(self) -> Optional[AsyncFunction]:
887
+ if not self.token.is_identifier():
888
+ return None
889
+ name = self.token.value or ""
890
+ if not FunctionFactory.is_async_provider(name):
891
+ return None
892
+ self.set_next_token()
893
+ if not self.token.is_left_parenthesis():
894
+ raise ValueError("Expected left parenthesis")
895
+ self.set_next_token()
896
+
897
+ func = FunctionFactory.create_async(name)
898
+ params = list(self._parse_function_parameters())
899
+ func.parameters = params
900
+
901
+ if not self.token.is_right_parenthesis():
902
+ raise ValueError("Expected right parenthesis")
903
+ self.set_next_token()
904
+ return func
905
+
906
+ def _parse_function_parameters(self) -> Iterator[ASTNode]:
907
+ while True:
908
+ self._skip_whitespace_and_comments()
909
+ if self.token.is_right_parenthesis():
910
+ break
911
+ expr = self._parse_expression()
912
+ if expr is not None:
913
+ yield expr
914
+ self._skip_whitespace_and_comments()
915
+ if not self.token.is_comma():
916
+ break
917
+ self.set_next_token()
918
+
919
+ def _parse_json(self) -> Optional[ASTNode]:
920
+ if self.token.is_opening_brace():
921
+ return self._parse_associative_array()
922
+ elif self.token.is_opening_bracket():
923
+ return self._parse_json_array()
924
+ return None
925
+
926
+ def _parse_associative_array(self) -> AssociativeArray:
927
+ if not self.token.is_opening_brace():
928
+ raise ValueError("Expected opening brace")
929
+ self.set_next_token()
930
+ array = AssociativeArray()
931
+ while True:
932
+ self._skip_whitespace_and_comments()
933
+ if self.token.is_closing_brace():
934
+ break
935
+ if not self.token.is_identifier() and not self.token.is_string() and not self.token.is_keyword():
936
+ raise ValueError("Expected key identifier or string")
937
+ key = self.token.value or ""
938
+ self.set_next_token()
939
+ self._skip_whitespace_and_comments()
940
+ if not self.token.is_colon():
941
+ raise ValueError("Expected colon")
942
+ self.set_next_token()
943
+ self._skip_whitespace_and_comments()
944
+ value = self._parse_expression()
945
+ if value is None:
946
+ raise ValueError("Expected value")
947
+ array.add_key_value(KeyValuePair(key, value))
948
+ self._skip_whitespace_and_comments()
949
+ if not self.token.is_comma():
950
+ break
951
+ self.set_next_token()
952
+ if not self.token.is_closing_brace():
953
+ raise ValueError("Expected closing brace")
954
+ self.set_next_token()
955
+ return array
956
+
957
+ def _parse_json_array(self) -> JSONArray:
958
+ if not self.token.is_opening_bracket():
959
+ raise ValueError("Expected opening bracket")
960
+ self.set_next_token()
961
+ array = JSONArray()
962
+ while True:
963
+ self._skip_whitespace_and_comments()
964
+ if self.token.is_closing_bracket():
965
+ break
966
+ value = self._parse_expression()
967
+ if value is None:
968
+ break
969
+ array.add_value(value)
970
+ self._skip_whitespace_and_comments()
971
+ if not self.token.is_comma():
972
+ break
973
+ self.set_next_token()
974
+ if not self.token.is_closing_bracket():
975
+ raise ValueError("Expected closing bracket")
976
+ self.set_next_token()
977
+ return array
978
+
979
+ def _parse_f_string(self) -> Optional[FString]:
980
+ if not self.token.is_f_string():
981
+ return None
982
+ f_string = FString()
983
+ while self.token.is_f_string() or self.token.is_opening_brace():
984
+ if self.token.is_f_string():
985
+ f_string.add_child(String(self.token.value or ""))
986
+ self.set_next_token()
987
+ elif self.token.is_opening_brace():
988
+ self.set_next_token()
989
+ expr = self._parse_expression()
990
+ if expr is not None:
991
+ f_string.add_child(expr)
992
+ if self.token.is_closing_brace():
993
+ self.set_next_token()
994
+ return f_string
995
+
996
+ def _skip_whitespace_and_comments(self) -> bool:
997
+ skipped: bool = self.previous_token.is_whitespace_or_comment() if self.previous_token else False
998
+ while self.token.is_whitespace_or_comment():
999
+ self.set_next_token()
1000
+ skipped = True
1001
+ return skipped
1002
+
1003
+ def _expect_and_skip_whitespace_and_comments(self) -> None:
1004
+ skipped = self._skip_whitespace_and_comments()
1005
+ if not skipped:
1006
+ raise ValueError("Expected whitespace")
1007
+
1008
+ def _expect_previous_token_to_be_whitespace_or_comment(self) -> None:
1009
+ if not self.previous_token.is_whitespace_or_comment():
1010
+ raise ValueError("Expected previous token to be whitespace or comment")