flowquery 1.0.46 → 1.0.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +0 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
- package/.editorconfig +0 -21
- package/.gitattributes +0 -3
- package/.github/workflows/npm-publish.yml +0 -32
- package/.github/workflows/python-publish.yml +0 -143
- package/.github/workflows/release.yml +0 -107
- package/.husky/pre-commit +0 -28
- package/.prettierrc +0 -22
- package/CODE_OF_CONDUCT.md +0 -10
- package/FlowQueryLogoIcon.png +0 -0
- package/SECURITY.md +0 -14
- package/SUPPORT.md +0 -13
- package/docs/flowquery.min.js +0 -1
- package/docs/index.html +0 -105
- package/flowquery-py/CONTRIBUTING.md +0 -127
- package/flowquery-py/README.md +0 -67
- package/flowquery-py/misc/data/test.json +0 -10
- package/flowquery-py/misc/data/users.json +0 -242
- package/flowquery-py/notebooks/TestFlowQuery.ipynb +0 -440
- package/flowquery-py/pyproject.toml +0 -121
- package/flowquery-py/setup_env.ps1 +0 -92
- package/flowquery-py/setup_env.sh +0 -87
- package/flowquery-py/src/__init__.py +0 -38
- package/flowquery-py/src/__main__.py +0 -10
- package/flowquery-py/src/compute/__init__.py +0 -6
- package/flowquery-py/src/compute/flowquery.py +0 -68
- package/flowquery-py/src/compute/runner.py +0 -64
- package/flowquery-py/src/extensibility.py +0 -52
- package/flowquery-py/src/graph/__init__.py +0 -31
- package/flowquery-py/src/graph/data.py +0 -136
- package/flowquery-py/src/graph/database.py +0 -141
- package/flowquery-py/src/graph/hops.py +0 -43
- package/flowquery-py/src/graph/node.py +0 -143
- package/flowquery-py/src/graph/node_data.py +0 -26
- package/flowquery-py/src/graph/node_reference.py +0 -50
- package/flowquery-py/src/graph/pattern.py +0 -115
- package/flowquery-py/src/graph/pattern_expression.py +0 -67
- package/flowquery-py/src/graph/patterns.py +0 -42
- package/flowquery-py/src/graph/physical_node.py +0 -41
- package/flowquery-py/src/graph/physical_relationship.py +0 -36
- package/flowquery-py/src/graph/relationship.py +0 -193
- package/flowquery-py/src/graph/relationship_data.py +0 -36
- package/flowquery-py/src/graph/relationship_match_collector.py +0 -85
- package/flowquery-py/src/graph/relationship_reference.py +0 -21
- package/flowquery-py/src/io/__init__.py +0 -5
- package/flowquery-py/src/io/command_line.py +0 -108
- package/flowquery-py/src/parsing/__init__.py +0 -17
- package/flowquery-py/src/parsing/alias.py +0 -20
- package/flowquery-py/src/parsing/alias_option.py +0 -11
- package/flowquery-py/src/parsing/ast_node.py +0 -147
- package/flowquery-py/src/parsing/base_parser.py +0 -84
- package/flowquery-py/src/parsing/components/__init__.py +0 -19
- package/flowquery-py/src/parsing/components/csv.py +0 -8
- package/flowquery-py/src/parsing/components/from_.py +0 -12
- package/flowquery-py/src/parsing/components/headers.py +0 -12
- package/flowquery-py/src/parsing/components/json.py +0 -8
- package/flowquery-py/src/parsing/components/null.py +0 -10
- package/flowquery-py/src/parsing/components/post.py +0 -8
- package/flowquery-py/src/parsing/components/text.py +0 -8
- package/flowquery-py/src/parsing/context.py +0 -50
- package/flowquery-py/src/parsing/data_structures/__init__.py +0 -15
- package/flowquery-py/src/parsing/data_structures/associative_array.py +0 -41
- package/flowquery-py/src/parsing/data_structures/json_array.py +0 -30
- package/flowquery-py/src/parsing/data_structures/key_value_pair.py +0 -38
- package/flowquery-py/src/parsing/data_structures/lookup.py +0 -51
- package/flowquery-py/src/parsing/data_structures/range_lookup.py +0 -42
- package/flowquery-py/src/parsing/expressions/__init__.py +0 -61
- package/flowquery-py/src/parsing/expressions/boolean.py +0 -20
- package/flowquery-py/src/parsing/expressions/expression.py +0 -141
- package/flowquery-py/src/parsing/expressions/expression_map.py +0 -26
- package/flowquery-py/src/parsing/expressions/f_string.py +0 -27
- package/flowquery-py/src/parsing/expressions/identifier.py +0 -21
- package/flowquery-py/src/parsing/expressions/number.py +0 -32
- package/flowquery-py/src/parsing/expressions/operator.py +0 -271
- package/flowquery-py/src/parsing/expressions/reference.py +0 -47
- package/flowquery-py/src/parsing/expressions/string.py +0 -27
- package/flowquery-py/src/parsing/functions/__init__.py +0 -127
- package/flowquery-py/src/parsing/functions/aggregate_function.py +0 -60
- package/flowquery-py/src/parsing/functions/async_function.py +0 -65
- package/flowquery-py/src/parsing/functions/avg.py +0 -55
- package/flowquery-py/src/parsing/functions/coalesce.py +0 -43
- package/flowquery-py/src/parsing/functions/collect.py +0 -75
- package/flowquery-py/src/parsing/functions/count.py +0 -79
- package/flowquery-py/src/parsing/functions/date_.py +0 -61
- package/flowquery-py/src/parsing/functions/datetime_.py +0 -62
- package/flowquery-py/src/parsing/functions/duration.py +0 -159
- package/flowquery-py/src/parsing/functions/element_id.py +0 -50
- package/flowquery-py/src/parsing/functions/function.py +0 -68
- package/flowquery-py/src/parsing/functions/function_factory.py +0 -170
- package/flowquery-py/src/parsing/functions/function_metadata.py +0 -148
- package/flowquery-py/src/parsing/functions/functions.py +0 -67
- package/flowquery-py/src/parsing/functions/head.py +0 -39
- package/flowquery-py/src/parsing/functions/id_.py +0 -49
- package/flowquery-py/src/parsing/functions/join.py +0 -49
- package/flowquery-py/src/parsing/functions/keys.py +0 -34
- package/flowquery-py/src/parsing/functions/last.py +0 -39
- package/flowquery-py/src/parsing/functions/localdatetime.py +0 -60
- package/flowquery-py/src/parsing/functions/localtime.py +0 -57
- package/flowquery-py/src/parsing/functions/max_.py +0 -49
- package/flowquery-py/src/parsing/functions/min_.py +0 -49
- package/flowquery-py/src/parsing/functions/nodes.py +0 -48
- package/flowquery-py/src/parsing/functions/predicate_function.py +0 -47
- package/flowquery-py/src/parsing/functions/predicate_sum.py +0 -49
- package/flowquery-py/src/parsing/functions/properties.py +0 -50
- package/flowquery-py/src/parsing/functions/rand.py +0 -28
- package/flowquery-py/src/parsing/functions/range_.py +0 -41
- package/flowquery-py/src/parsing/functions/reducer_element.py +0 -15
- package/flowquery-py/src/parsing/functions/relationships.py +0 -46
- package/flowquery-py/src/parsing/functions/replace.py +0 -39
- package/flowquery-py/src/parsing/functions/round_.py +0 -34
- package/flowquery-py/src/parsing/functions/schema.py +0 -40
- package/flowquery-py/src/parsing/functions/size.py +0 -34
- package/flowquery-py/src/parsing/functions/split.py +0 -54
- package/flowquery-py/src/parsing/functions/string_distance.py +0 -92
- package/flowquery-py/src/parsing/functions/stringify.py +0 -49
- package/flowquery-py/src/parsing/functions/substring.py +0 -76
- package/flowquery-py/src/parsing/functions/sum.py +0 -51
- package/flowquery-py/src/parsing/functions/tail.py +0 -37
- package/flowquery-py/src/parsing/functions/temporal_utils.py +0 -186
- package/flowquery-py/src/parsing/functions/time_.py +0 -57
- package/flowquery-py/src/parsing/functions/timestamp.py +0 -37
- package/flowquery-py/src/parsing/functions/to_float.py +0 -46
- package/flowquery-py/src/parsing/functions/to_integer.py +0 -46
- package/flowquery-py/src/parsing/functions/to_json.py +0 -35
- package/flowquery-py/src/parsing/functions/to_lower.py +0 -37
- package/flowquery-py/src/parsing/functions/to_string.py +0 -41
- package/flowquery-py/src/parsing/functions/trim.py +0 -37
- package/flowquery-py/src/parsing/functions/type_.py +0 -47
- package/flowquery-py/src/parsing/functions/value_holder.py +0 -24
- package/flowquery-py/src/parsing/logic/__init__.py +0 -15
- package/flowquery-py/src/parsing/logic/case.py +0 -28
- package/flowquery-py/src/parsing/logic/else_.py +0 -12
- package/flowquery-py/src/parsing/logic/end.py +0 -8
- package/flowquery-py/src/parsing/logic/then.py +0 -12
- package/flowquery-py/src/parsing/logic/when.py +0 -12
- package/flowquery-py/src/parsing/operations/__init__.py +0 -46
- package/flowquery-py/src/parsing/operations/aggregated_return.py +0 -25
- package/flowquery-py/src/parsing/operations/aggregated_with.py +0 -22
- package/flowquery-py/src/parsing/operations/call.py +0 -73
- package/flowquery-py/src/parsing/operations/create_node.py +0 -35
- package/flowquery-py/src/parsing/operations/create_relationship.py +0 -35
- package/flowquery-py/src/parsing/operations/delete_node.py +0 -29
- package/flowquery-py/src/parsing/operations/delete_relationship.py +0 -29
- package/flowquery-py/src/parsing/operations/group_by.py +0 -148
- package/flowquery-py/src/parsing/operations/limit.py +0 -33
- package/flowquery-py/src/parsing/operations/load.py +0 -148
- package/flowquery-py/src/parsing/operations/match.py +0 -52
- package/flowquery-py/src/parsing/operations/operation.py +0 -69
- package/flowquery-py/src/parsing/operations/order_by.py +0 -114
- package/flowquery-py/src/parsing/operations/projection.py +0 -21
- package/flowquery-py/src/parsing/operations/return_op.py +0 -88
- package/flowquery-py/src/parsing/operations/union.py +0 -115
- package/flowquery-py/src/parsing/operations/union_all.py +0 -17
- package/flowquery-py/src/parsing/operations/unwind.py +0 -42
- package/flowquery-py/src/parsing/operations/where.py +0 -43
- package/flowquery-py/src/parsing/operations/with_op.py +0 -18
- package/flowquery-py/src/parsing/parser.py +0 -1384
- package/flowquery-py/src/parsing/parser_state.py +0 -26
- package/flowquery-py/src/parsing/token_to_node.py +0 -109
- package/flowquery-py/src/tokenization/__init__.py +0 -23
- package/flowquery-py/src/tokenization/keyword.py +0 -54
- package/flowquery-py/src/tokenization/operator.py +0 -29
- package/flowquery-py/src/tokenization/string_walker.py +0 -158
- package/flowquery-py/src/tokenization/symbol.py +0 -19
- package/flowquery-py/src/tokenization/token.py +0 -693
- package/flowquery-py/src/tokenization/token_mapper.py +0 -53
- package/flowquery-py/src/tokenization/token_type.py +0 -21
- package/flowquery-py/src/tokenization/tokenizer.py +0 -214
- package/flowquery-py/src/tokenization/trie.py +0 -125
- package/flowquery-py/src/utils/__init__.py +0 -6
- package/flowquery-py/src/utils/object_utils.py +0 -20
- package/flowquery-py/src/utils/string_utils.py +0 -113
- package/flowquery-py/tests/__init__.py +0 -1
- package/flowquery-py/tests/compute/__init__.py +0 -1
- package/flowquery-py/tests/compute/test_runner.py +0 -4902
- package/flowquery-py/tests/graph/__init__.py +0 -1
- package/flowquery-py/tests/graph/test_create.py +0 -56
- package/flowquery-py/tests/graph/test_data.py +0 -73
- package/flowquery-py/tests/graph/test_match.py +0 -40
- package/flowquery-py/tests/parsing/__init__.py +0 -1
- package/flowquery-py/tests/parsing/test_context.py +0 -34
- package/flowquery-py/tests/parsing/test_expression.py +0 -248
- package/flowquery-py/tests/parsing/test_parser.py +0 -1237
- package/flowquery-py/tests/test_extensibility.py +0 -611
- package/flowquery-py/tests/tokenization/__init__.py +0 -1
- package/flowquery-py/tests/tokenization/test_token_mapper.py +0 -60
- package/flowquery-py/tests/tokenization/test_tokenizer.py +0 -198
- package/flowquery-py/tests/tokenization/test_trie.py +0 -30
- package/flowquery-vscode/.vscode-test.mjs +0 -5
- package/flowquery-vscode/.vscodeignore +0 -13
- package/flowquery-vscode/LICENSE +0 -21
- package/flowquery-vscode/README.md +0 -11
- package/flowquery-vscode/demo/FlowQueryVSCodeDemo.gif +0 -0
- package/flowquery-vscode/eslint.config.mjs +0 -25
- package/flowquery-vscode/extension.js +0 -508
- package/flowquery-vscode/flowQueryEngine/flowquery.min.js +0 -1
- package/flowquery-vscode/flowquery-worker.js +0 -66
- package/flowquery-vscode/images/FlowQueryLogoIcon.png +0 -0
- package/flowquery-vscode/jsconfig.json +0 -13
- package/flowquery-vscode/libs/page.css +0 -53
- package/flowquery-vscode/libs/table.css +0 -13
- package/flowquery-vscode/libs/tabs.css +0 -66
- package/flowquery-vscode/package-lock.json +0 -2917
- package/flowquery-vscode/package.json +0 -51
- package/flowquery-vscode/test/extension.test.js +0 -196
- package/flowquery-vscode/test/worker.test.js +0 -25
- package/flowquery-vscode/vsc-extension-quickstart.md +0 -42
- package/jest.config.js +0 -14
- package/misc/apps/RAG/README.md +0 -29
- package/misc/apps/RAG/data/chats.json +0 -302
- package/misc/apps/RAG/data/emails.json +0 -182
- package/misc/apps/RAG/data/events.json +0 -226
- package/misc/apps/RAG/data/files.json +0 -172
- package/misc/apps/RAG/data/users.json +0 -158
- package/misc/apps/RAG/jest.config.js +0 -21
- package/misc/apps/RAG/package.json +0 -48
- package/misc/apps/RAG/public/index.html +0 -18
- package/misc/apps/RAG/src/App.css +0 -42
- package/misc/apps/RAG/src/App.tsx +0 -50
- package/misc/apps/RAG/src/components/AdaptiveCardRenderer.css +0 -172
- package/misc/apps/RAG/src/components/AdaptiveCardRenderer.tsx +0 -380
- package/misc/apps/RAG/src/components/ApiKeySettings.tsx +0 -245
- package/misc/apps/RAG/src/components/ChatContainer.css +0 -67
- package/misc/apps/RAG/src/components/ChatContainer.tsx +0 -242
- package/misc/apps/RAG/src/components/ChatInput.css +0 -23
- package/misc/apps/RAG/src/components/ChatInput.tsx +0 -76
- package/misc/apps/RAG/src/components/ChatMessage.css +0 -160
- package/misc/apps/RAG/src/components/ChatMessage.tsx +0 -286
- package/misc/apps/RAG/src/components/FlowQueryAgent.ts +0 -708
- package/misc/apps/RAG/src/components/FlowQueryRunner.css +0 -113
- package/misc/apps/RAG/src/components/FlowQueryRunner.tsx +0 -371
- package/misc/apps/RAG/src/components/index.ts +0 -28
- package/misc/apps/RAG/src/graph/index.ts +0 -19
- package/misc/apps/RAG/src/graph/initializeGraph.ts +0 -254
- package/misc/apps/RAG/src/index.tsx +0 -29
- package/misc/apps/RAG/src/prompts/FlowQuerySystemPrompt.ts +0 -327
- package/misc/apps/RAG/src/prompts/index.ts +0 -10
- package/misc/apps/RAG/src/tests/graph.test.ts +0 -35
- package/misc/apps/RAG/src/utils/FlowQueryExecutor.ts +0 -130
- package/misc/apps/RAG/src/utils/FlowQueryExtractor.ts +0 -208
- package/misc/apps/RAG/src/utils/Llm.ts +0 -248
- package/misc/apps/RAG/src/utils/index.ts +0 -12
- package/misc/apps/RAG/tsconfig.json +0 -22
- package/misc/apps/RAG/webpack.config.js +0 -43
- package/misc/apps/README.md +0 -1
- package/misc/queries/analyze_catfacts.cql +0 -75
- package/misc/queries/azure_openai_completions.cql +0 -13
- package/misc/queries/azure_openai_models.cql +0 -9
- package/misc/queries/mock_pipeline.cql +0 -84
- package/misc/queries/openai_completions.cql +0 -15
- package/misc/queries/openai_models.cql +0 -13
- package/misc/queries/test.cql +0 -6
- package/misc/queries/tool_inference.cql +0 -24
- package/misc/queries/wisdom.cql +0 -6
- package/misc/queries/wisdom_letter_histogram.cql +0 -8
- package/src/compute/flowquery.ts +0 -46
- package/src/compute/runner.ts +0 -66
- package/src/extensibility.ts +0 -45
- package/src/graph/data.ts +0 -130
- package/src/graph/database.ts +0 -143
- package/src/graph/hops.ts +0 -22
- package/src/graph/node.ts +0 -122
- package/src/graph/node_data.ts +0 -18
- package/src/graph/node_reference.ts +0 -38
- package/src/graph/pattern.ts +0 -110
- package/src/graph/pattern_expression.ts +0 -48
- package/src/graph/patterns.ts +0 -36
- package/src/graph/physical_node.ts +0 -23
- package/src/graph/physical_relationship.ts +0 -23
- package/src/graph/relationship.ts +0 -167
- package/src/graph/relationship_data.ts +0 -31
- package/src/graph/relationship_match_collector.ts +0 -64
- package/src/graph/relationship_reference.ts +0 -25
- package/src/index.browser.ts +0 -46
- package/src/index.node.ts +0 -55
- package/src/index.ts +0 -12
- package/src/io/command_line.ts +0 -74
- package/src/parsing/alias.ts +0 -23
- package/src/parsing/alias_option.ts +0 -5
- package/src/parsing/ast_node.ts +0 -153
- package/src/parsing/base_parser.ts +0 -98
- package/src/parsing/components/csv.ts +0 -9
- package/src/parsing/components/from.ts +0 -12
- package/src/parsing/components/headers.ts +0 -12
- package/src/parsing/components/json.ts +0 -9
- package/src/parsing/components/null.ts +0 -9
- package/src/parsing/components/post.ts +0 -9
- package/src/parsing/components/text.ts +0 -9
- package/src/parsing/context.ts +0 -54
- package/src/parsing/data_structures/associative_array.ts +0 -43
- package/src/parsing/data_structures/json_array.ts +0 -31
- package/src/parsing/data_structures/key_value_pair.ts +0 -37
- package/src/parsing/data_structures/lookup.ts +0 -44
- package/src/parsing/data_structures/range_lookup.ts +0 -36
- package/src/parsing/expressions/boolean.ts +0 -21
- package/src/parsing/expressions/expression.ts +0 -150
- package/src/parsing/expressions/expression_map.ts +0 -22
- package/src/parsing/expressions/f_string.ts +0 -26
- package/src/parsing/expressions/identifier.ts +0 -22
- package/src/parsing/expressions/number.ts +0 -40
- package/src/parsing/expressions/operator.ts +0 -354
- package/src/parsing/expressions/reference.ts +0 -45
- package/src/parsing/expressions/string.ts +0 -34
- package/src/parsing/functions/aggregate_function.ts +0 -58
- package/src/parsing/functions/async_function.ts +0 -64
- package/src/parsing/functions/avg.ts +0 -47
- package/src/parsing/functions/coalesce.ts +0 -49
- package/src/parsing/functions/collect.ts +0 -54
- package/src/parsing/functions/count.ts +0 -54
- package/src/parsing/functions/date.ts +0 -63
- package/src/parsing/functions/datetime.ts +0 -63
- package/src/parsing/functions/duration.ts +0 -143
- package/src/parsing/functions/element_id.ts +0 -51
- package/src/parsing/functions/function.ts +0 -60
- package/src/parsing/functions/function_factory.ts +0 -195
- package/src/parsing/functions/function_metadata.ts +0 -217
- package/src/parsing/functions/functions.ts +0 -70
- package/src/parsing/functions/head.ts +0 -42
- package/src/parsing/functions/id.ts +0 -51
- package/src/parsing/functions/join.ts +0 -40
- package/src/parsing/functions/keys.ts +0 -29
- package/src/parsing/functions/last.ts +0 -42
- package/src/parsing/functions/localdatetime.ts +0 -63
- package/src/parsing/functions/localtime.ts +0 -58
- package/src/parsing/functions/max.ts +0 -37
- package/src/parsing/functions/min.ts +0 -37
- package/src/parsing/functions/nodes.ts +0 -54
- package/src/parsing/functions/predicate_function.ts +0 -48
- package/src/parsing/functions/predicate_sum.ts +0 -47
- package/src/parsing/functions/properties.ts +0 -56
- package/src/parsing/functions/rand.ts +0 -21
- package/src/parsing/functions/range.ts +0 -37
- package/src/parsing/functions/reducer_element.ts +0 -10
- package/src/parsing/functions/relationships.ts +0 -52
- package/src/parsing/functions/replace.ts +0 -38
- package/src/parsing/functions/round.ts +0 -28
- package/src/parsing/functions/schema.ts +0 -39
- package/src/parsing/functions/size.ts +0 -28
- package/src/parsing/functions/split.ts +0 -45
- package/src/parsing/functions/string_distance.ts +0 -83
- package/src/parsing/functions/stringify.ts +0 -37
- package/src/parsing/functions/substring.ts +0 -68
- package/src/parsing/functions/sum.ts +0 -41
- package/src/parsing/functions/tail.ts +0 -39
- package/src/parsing/functions/temporal_utils.ts +0 -180
- package/src/parsing/functions/time.ts +0 -58
- package/src/parsing/functions/timestamp.ts +0 -37
- package/src/parsing/functions/to_float.ts +0 -50
- package/src/parsing/functions/to_integer.ts +0 -50
- package/src/parsing/functions/to_json.ts +0 -28
- package/src/parsing/functions/to_lower.ts +0 -28
- package/src/parsing/functions/to_string.ts +0 -32
- package/src/parsing/functions/trim.ts +0 -28
- package/src/parsing/functions/type.ts +0 -39
- package/src/parsing/functions/value_holder.ts +0 -13
- package/src/parsing/logic/case.ts +0 -26
- package/src/parsing/logic/else.ts +0 -12
- package/src/parsing/logic/end.ts +0 -9
- package/src/parsing/logic/then.ts +0 -12
- package/src/parsing/logic/when.ts +0 -12
- package/src/parsing/operations/aggregated_return.ts +0 -22
- package/src/parsing/operations/aggregated_with.ts +0 -18
- package/src/parsing/operations/call.ts +0 -69
- package/src/parsing/operations/create_node.ts +0 -39
- package/src/parsing/operations/create_relationship.ts +0 -38
- package/src/parsing/operations/delete_node.ts +0 -33
- package/src/parsing/operations/delete_relationship.ts +0 -32
- package/src/parsing/operations/group_by.ts +0 -137
- package/src/parsing/operations/limit.ts +0 -31
- package/src/parsing/operations/load.ts +0 -146
- package/src/parsing/operations/match.ts +0 -54
- package/src/parsing/operations/operation.ts +0 -69
- package/src/parsing/operations/order_by.ts +0 -126
- package/src/parsing/operations/projection.ts +0 -18
- package/src/parsing/operations/return.ts +0 -76
- package/src/parsing/operations/union.ts +0 -114
- package/src/parsing/operations/union_all.ts +0 -16
- package/src/parsing/operations/unwind.ts +0 -36
- package/src/parsing/operations/where.ts +0 -42
- package/src/parsing/operations/with.ts +0 -20
- package/src/parsing/parser.ts +0 -1641
- package/src/parsing/parser_state.ts +0 -25
- package/src/parsing/token_to_node.ts +0 -114
- package/src/tokenization/keyword.ts +0 -50
- package/src/tokenization/operator.ts +0 -25
- package/src/tokenization/string_walker.ts +0 -197
- package/src/tokenization/symbol.ts +0 -15
- package/src/tokenization/token.ts +0 -764
- package/src/tokenization/token_mapper.ts +0 -53
- package/src/tokenization/token_type.ts +0 -16
- package/src/tokenization/tokenizer.ts +0 -250
- package/src/tokenization/trie.ts +0 -117
- package/src/utils/object_utils.ts +0 -17
- package/src/utils/string_utils.ts +0 -114
- package/tests/compute/runner.test.ts +0 -4559
- package/tests/extensibility.test.ts +0 -643
- package/tests/graph/create.test.ts +0 -36
- package/tests/graph/data.test.ts +0 -58
- package/tests/graph/match.test.ts +0 -29
- package/tests/parsing/context.test.ts +0 -27
- package/tests/parsing/expression.test.ts +0 -303
- package/tests/parsing/parser.test.ts +0 -1327
- package/tests/tokenization/token_mapper.test.ts +0 -47
- package/tests/tokenization/tokenizer.test.ts +0 -191
- package/tests/tokenization/trie.test.ts +0 -20
- package/tsconfig.json +0 -19
- package/typedoc.json +0 -16
- package/vscode-settings.json.recommended +0 -16
- package/webpack.config.js +0 -26
|
@@ -1,1384 +0,0 @@
|
|
|
1
|
-
"""Main parser for FlowQuery statements."""
|
|
2
|
-
|
|
3
|
-
import sys
|
|
4
|
-
from typing import Dict, Iterator, List, Optional, Tuple, cast
|
|
5
|
-
|
|
6
|
-
from ..graph.hops import Hops
|
|
7
|
-
from ..graph.node import Node
|
|
8
|
-
from ..graph.node_reference import NodeReference
|
|
9
|
-
from ..graph.pattern import Pattern
|
|
10
|
-
from ..graph.pattern_expression import PatternExpression
|
|
11
|
-
from ..graph.relationship import Relationship
|
|
12
|
-
from ..graph.relationship_reference import RelationshipReference
|
|
13
|
-
from ..tokenization.token import Token
|
|
14
|
-
from ..utils.object_utils import ObjectUtils
|
|
15
|
-
from .alias import Alias
|
|
16
|
-
from .alias_option import AliasOption
|
|
17
|
-
from .ast_node import ASTNode
|
|
18
|
-
from .base_parser import BaseParser
|
|
19
|
-
from .components.from_ import From
|
|
20
|
-
from .components.headers import Headers
|
|
21
|
-
from .components.null import Null
|
|
22
|
-
from .components.post import Post
|
|
23
|
-
from .data_structures.associative_array import AssociativeArray
|
|
24
|
-
from .data_structures.json_array import JSONArray
|
|
25
|
-
from .data_structures.key_value_pair import KeyValuePair
|
|
26
|
-
from .data_structures.lookup import Lookup
|
|
27
|
-
from .data_structures.range_lookup import RangeLookup
|
|
28
|
-
from .expressions.expression import Expression
|
|
29
|
-
from .expressions.f_string import FString
|
|
30
|
-
from .expressions.identifier import Identifier
|
|
31
|
-
from .expressions.operator import (
|
|
32
|
-
Contains,
|
|
33
|
-
EndsWith,
|
|
34
|
-
In,
|
|
35
|
-
Is,
|
|
36
|
-
IsNot,
|
|
37
|
-
Not,
|
|
38
|
-
NotContains,
|
|
39
|
-
NotEndsWith,
|
|
40
|
-
NotIn,
|
|
41
|
-
NotStartsWith,
|
|
42
|
-
StartsWith,
|
|
43
|
-
)
|
|
44
|
-
from .expressions.reference import Reference
|
|
45
|
-
from .expressions.string import String
|
|
46
|
-
from .functions.aggregate_function import AggregateFunction
|
|
47
|
-
from .functions.async_function import AsyncFunction
|
|
48
|
-
from .functions.function import Function
|
|
49
|
-
from .functions.function_factory import FunctionFactory
|
|
50
|
-
from .functions.predicate_function import PredicateFunction
|
|
51
|
-
from .logic.case import Case
|
|
52
|
-
from .logic.else_ import Else
|
|
53
|
-
from .logic.then import Then
|
|
54
|
-
from .logic.when import When
|
|
55
|
-
from .operations.aggregated_return import AggregatedReturn
|
|
56
|
-
from .operations.aggregated_with import AggregatedWith
|
|
57
|
-
from .operations.call import Call
|
|
58
|
-
from .operations.create_node import CreateNode
|
|
59
|
-
from .operations.create_relationship import CreateRelationship
|
|
60
|
-
from .operations.delete_node import DeleteNode
|
|
61
|
-
from .operations.delete_relationship import DeleteRelationship
|
|
62
|
-
from .operations.limit import Limit
|
|
63
|
-
from .operations.load import Load
|
|
64
|
-
from .operations.match import Match
|
|
65
|
-
from .operations.operation import Operation
|
|
66
|
-
from .operations.order_by import OrderBy, SortField
|
|
67
|
-
from .operations.return_op import Return
|
|
68
|
-
from .operations.union import Union
|
|
69
|
-
from .operations.union_all import UnionAll
|
|
70
|
-
from .operations.unwind import Unwind
|
|
71
|
-
from .operations.where import Where
|
|
72
|
-
from .operations.with_op import With
|
|
73
|
-
from .parser_state import ParserState
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
class Parser(BaseParser):
|
|
77
|
-
"""Main parser for FlowQuery statements.
|
|
78
|
-
|
|
79
|
-
Parses FlowQuery declarative query language statements into an Abstract Syntax Tree (AST).
|
|
80
|
-
Supports operations like WITH, UNWIND, RETURN, LOAD, WHERE, and LIMIT, along with
|
|
81
|
-
expressions, functions, data structures, and logical constructs.
|
|
82
|
-
|
|
83
|
-
Example:
|
|
84
|
-
parser = Parser()
|
|
85
|
-
ast = parser.parse("unwind [1, 2, 3, 4, 5] as num return num")
|
|
86
|
-
"""
|
|
87
|
-
|
|
88
|
-
def __init__(self, tokens: Optional[List[Token]] = None):
|
|
89
|
-
super().__init__(tokens)
|
|
90
|
-
self._state = ParserState()
|
|
91
|
-
|
|
92
|
-
def parse(self, statement: str) -> ASTNode:
|
|
93
|
-
"""Parses a FlowQuery statement into an Abstract Syntax Tree.
|
|
94
|
-
|
|
95
|
-
Args:
|
|
96
|
-
statement: The FlowQuery statement to parse
|
|
97
|
-
|
|
98
|
-
Returns:
|
|
99
|
-
The root AST node containing the parsed structure
|
|
100
|
-
|
|
101
|
-
Raises:
|
|
102
|
-
ValueError: If the statement is malformed or contains syntax errors
|
|
103
|
-
"""
|
|
104
|
-
self.tokenize(statement)
|
|
105
|
-
return self._parse_tokenized()
|
|
106
|
-
|
|
107
|
-
def _parse_tokenized(self, is_sub_query: bool = False) -> ASTNode:
|
|
108
|
-
root = ASTNode()
|
|
109
|
-
previous: Optional[Operation] = None
|
|
110
|
-
operation: Optional[Operation] = None
|
|
111
|
-
|
|
112
|
-
while not self.token.is_eof():
|
|
113
|
-
if root.child_count() > 0:
|
|
114
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
115
|
-
else:
|
|
116
|
-
self._skip_whitespace_and_comments()
|
|
117
|
-
|
|
118
|
-
# UNION separates two query pipelines — break and handle after the loop
|
|
119
|
-
if self.token.is_union():
|
|
120
|
-
break
|
|
121
|
-
|
|
122
|
-
if self.token.is_eof():
|
|
123
|
-
break
|
|
124
|
-
|
|
125
|
-
operation = self._parse_operation()
|
|
126
|
-
if operation is None and not is_sub_query:
|
|
127
|
-
raise ValueError("Expected one of WITH, UNWIND, RETURN, LOAD, OR CALL")
|
|
128
|
-
elif operation is None and is_sub_query:
|
|
129
|
-
return root
|
|
130
|
-
|
|
131
|
-
if self._state.returns > 1:
|
|
132
|
-
raise ValueError("Only one RETURN statement is allowed")
|
|
133
|
-
|
|
134
|
-
if isinstance(previous, Call) and not previous.has_yield:
|
|
135
|
-
raise ValueError(
|
|
136
|
-
"CALL operations must have a YIELD clause unless they are the last operation"
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
if previous is not None:
|
|
140
|
-
previous.add_sibling(operation)
|
|
141
|
-
else:
|
|
142
|
-
root.add_child(operation)
|
|
143
|
-
|
|
144
|
-
where = self._parse_where()
|
|
145
|
-
if where is not None:
|
|
146
|
-
if isinstance(operation, Return):
|
|
147
|
-
operation.where = where
|
|
148
|
-
else:
|
|
149
|
-
operation.add_sibling(where)
|
|
150
|
-
operation = where
|
|
151
|
-
|
|
152
|
-
order_by = self._parse_order_by()
|
|
153
|
-
if order_by is not None:
|
|
154
|
-
if isinstance(operation, Return):
|
|
155
|
-
operation.order_by = order_by
|
|
156
|
-
else:
|
|
157
|
-
operation.add_sibling(order_by)
|
|
158
|
-
operation = order_by
|
|
159
|
-
|
|
160
|
-
limit = self._parse_limit()
|
|
161
|
-
if limit is not None:
|
|
162
|
-
if isinstance(operation, Return):
|
|
163
|
-
operation.limit = limit
|
|
164
|
-
else:
|
|
165
|
-
operation.add_sibling(limit)
|
|
166
|
-
operation = limit
|
|
167
|
-
|
|
168
|
-
previous = operation
|
|
169
|
-
|
|
170
|
-
# Handle UNION: wrap left and right pipelines into a Union node
|
|
171
|
-
if not self.token.is_eof() and self.token.is_union():
|
|
172
|
-
if not isinstance(operation, (Return, Call)):
|
|
173
|
-
raise ValueError(
|
|
174
|
-
"Each side of UNION must end with a RETURN or CALL statement"
|
|
175
|
-
)
|
|
176
|
-
union = self._parse_union()
|
|
177
|
-
assert union is not None
|
|
178
|
-
union.left = root.first_child() # type: ignore[assignment]
|
|
179
|
-
# Save and reset parser state for right-side scope
|
|
180
|
-
state: ParserState = self._state
|
|
181
|
-
self._state = ParserState()
|
|
182
|
-
right_root = self._parse_tokenized(is_sub_query)
|
|
183
|
-
union.right = right_root.first_child() # type: ignore[assignment]
|
|
184
|
-
# Restore parser state
|
|
185
|
-
self._state = state
|
|
186
|
-
new_root = ASTNode()
|
|
187
|
-
new_root.add_child(union)
|
|
188
|
-
return new_root
|
|
189
|
-
|
|
190
|
-
if not isinstance(operation, (Return, Call, CreateNode, CreateRelationship, DeleteNode, DeleteRelationship)):
|
|
191
|
-
raise ValueError("Last statement must be a RETURN, WHERE, CALL, CREATE, or DELETE statement")
|
|
192
|
-
|
|
193
|
-
return root
|
|
194
|
-
|
|
195
|
-
def _parse_operation(self) -> Optional[Operation]:
|
|
196
|
-
return (
|
|
197
|
-
self._parse_with() or
|
|
198
|
-
self._parse_unwind() or
|
|
199
|
-
self._parse_return() or
|
|
200
|
-
self._parse_load() or
|
|
201
|
-
self._parse_call() or
|
|
202
|
-
self._parse_match() or
|
|
203
|
-
self._parse_create() or
|
|
204
|
-
self._parse_delete()
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
def _parse_with(self) -> Optional[With]:
|
|
208
|
-
if not self.token.is_with():
|
|
209
|
-
return None
|
|
210
|
-
self.set_next_token()
|
|
211
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
212
|
-
distinct = False
|
|
213
|
-
if self.token.is_distinct():
|
|
214
|
-
distinct = True
|
|
215
|
-
self.set_next_token()
|
|
216
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
217
|
-
expressions = self._parse_expressions(AliasOption.REQUIRED)
|
|
218
|
-
if len(expressions) == 0:
|
|
219
|
-
raise ValueError("Expected expression")
|
|
220
|
-
if distinct or any(expr.has_reducers() for expr in expressions):
|
|
221
|
-
return AggregatedWith(expressions) # type: ignore[return-value]
|
|
222
|
-
return With(expressions)
|
|
223
|
-
|
|
224
|
-
def _parse_unwind(self) -> Optional[Unwind]:
|
|
225
|
-
if not self.token.is_unwind():
|
|
226
|
-
return None
|
|
227
|
-
self.set_next_token()
|
|
228
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
229
|
-
expression = self._parse_expression()
|
|
230
|
-
if expression is None:
|
|
231
|
-
raise ValueError("Expected expression")
|
|
232
|
-
if not ObjectUtils.is_instance_of_any(
|
|
233
|
-
expression.first_child(),
|
|
234
|
-
[JSONArray, Function, Reference, Lookup, RangeLookup]
|
|
235
|
-
):
|
|
236
|
-
raise ValueError("Expected array, function, reference, or lookup.")
|
|
237
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
238
|
-
alias = self._parse_alias()
|
|
239
|
-
if alias is not None:
|
|
240
|
-
expression.set_alias(alias.get_alias())
|
|
241
|
-
else:
|
|
242
|
-
raise ValueError("Expected alias")
|
|
243
|
-
unwind = Unwind(expression)
|
|
244
|
-
self._state.variables[alias.get_alias()] = unwind
|
|
245
|
-
return unwind
|
|
246
|
-
|
|
247
|
-
def _parse_return(self) -> Optional[Return]:
|
|
248
|
-
if not self.token.is_return():
|
|
249
|
-
return None
|
|
250
|
-
self.set_next_token()
|
|
251
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
252
|
-
distinct = False
|
|
253
|
-
if self.token.is_distinct():
|
|
254
|
-
distinct = True
|
|
255
|
-
self.set_next_token()
|
|
256
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
257
|
-
expressions = self._parse_expressions(AliasOption.OPTIONAL)
|
|
258
|
-
if len(expressions) == 0:
|
|
259
|
-
raise ValueError("Expected expression")
|
|
260
|
-
if distinct or any(expr.has_reducers() for expr in expressions):
|
|
261
|
-
return AggregatedReturn(expressions)
|
|
262
|
-
self._state.increment_returns()
|
|
263
|
-
return Return(expressions)
|
|
264
|
-
|
|
265
|
-
def _parse_where(self) -> Optional[Where]:
|
|
266
|
-
self._skip_whitespace_and_comments()
|
|
267
|
-
if not self.token.is_where():
|
|
268
|
-
return None
|
|
269
|
-
self._expect_previous_token_to_be_whitespace_or_comment()
|
|
270
|
-
self.set_next_token()
|
|
271
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
272
|
-
expression = self._parse_expression()
|
|
273
|
-
if expression is None:
|
|
274
|
-
raise ValueError("Expected expression")
|
|
275
|
-
if ObjectUtils.is_instance_of_any(
|
|
276
|
-
expression.first_child(),
|
|
277
|
-
[JSONArray, AssociativeArray]
|
|
278
|
-
):
|
|
279
|
-
raise ValueError("Expected an expression which can be evaluated to a boolean")
|
|
280
|
-
return Where(expression)
|
|
281
|
-
|
|
282
|
-
def _parse_load(self) -> Optional[Load]:
|
|
283
|
-
if not self.token.is_load():
|
|
284
|
-
return None
|
|
285
|
-
load = Load()
|
|
286
|
-
self.set_next_token()
|
|
287
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
288
|
-
if not (self.token.is_json() or self.token.is_csv() or self.token.is_text()):
|
|
289
|
-
raise ValueError("Expected JSON, CSV, or TEXT")
|
|
290
|
-
load.add_child(self.token.node)
|
|
291
|
-
self.set_next_token()
|
|
292
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
293
|
-
if not self.token.is_from():
|
|
294
|
-
raise ValueError("Expected FROM")
|
|
295
|
-
self.set_next_token()
|
|
296
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
297
|
-
from_node = From()
|
|
298
|
-
load.add_child(from_node)
|
|
299
|
-
|
|
300
|
-
# Check if source is async function
|
|
301
|
-
async_func = self._parse_async_function()
|
|
302
|
-
if async_func is not None:
|
|
303
|
-
from_node.add_child(async_func)
|
|
304
|
-
else:
|
|
305
|
-
expression = self._parse_expression()
|
|
306
|
-
if expression is None:
|
|
307
|
-
raise ValueError("Expected expression or async function")
|
|
308
|
-
from_node.add_child(expression)
|
|
309
|
-
|
|
310
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
311
|
-
if self.token.is_headers():
|
|
312
|
-
headers = Headers()
|
|
313
|
-
self.set_next_token()
|
|
314
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
315
|
-
header = self._parse_expression()
|
|
316
|
-
if header is None:
|
|
317
|
-
raise ValueError("Expected expression")
|
|
318
|
-
headers.add_child(header)
|
|
319
|
-
load.add_child(headers)
|
|
320
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
321
|
-
|
|
322
|
-
if self.token.is_post():
|
|
323
|
-
post = Post()
|
|
324
|
-
self.set_next_token()
|
|
325
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
326
|
-
payload = self._parse_expression()
|
|
327
|
-
if payload is None:
|
|
328
|
-
raise ValueError("Expected expression")
|
|
329
|
-
post.add_child(payload)
|
|
330
|
-
load.add_child(post)
|
|
331
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
332
|
-
|
|
333
|
-
alias = self._parse_alias()
|
|
334
|
-
if alias is not None:
|
|
335
|
-
load.add_child(alias)
|
|
336
|
-
self._state.variables[alias.get_alias()] = load
|
|
337
|
-
else:
|
|
338
|
-
raise ValueError("Expected alias")
|
|
339
|
-
return load
|
|
340
|
-
|
|
341
|
-
def _parse_call(self) -> Optional[Call]:
|
|
342
|
-
if not self.token.is_call():
|
|
343
|
-
return None
|
|
344
|
-
self.set_next_token()
|
|
345
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
346
|
-
async_function = self._parse_async_function()
|
|
347
|
-
if async_function is None:
|
|
348
|
-
raise ValueError("Expected async function")
|
|
349
|
-
call = Call()
|
|
350
|
-
call.function = async_function
|
|
351
|
-
self._skip_whitespace_and_comments()
|
|
352
|
-
if self.token.is_yield():
|
|
353
|
-
self._expect_previous_token_to_be_whitespace_or_comment()
|
|
354
|
-
self.set_next_token()
|
|
355
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
356
|
-
expressions = self._parse_expressions(AliasOption.OPTIONAL)
|
|
357
|
-
if len(expressions) == 0:
|
|
358
|
-
raise ValueError("Expected at least one expression")
|
|
359
|
-
call.yielded = expressions # type: ignore[assignment]
|
|
360
|
-
return call
|
|
361
|
-
|
|
362
|
-
def _parse_match(self) -> Optional[Match]:
|
|
363
|
-
optional = False
|
|
364
|
-
if self.token.is_optional():
|
|
365
|
-
optional = True
|
|
366
|
-
self.set_next_token()
|
|
367
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
368
|
-
if not self.token.is_match():
|
|
369
|
-
if optional:
|
|
370
|
-
raise ValueError("Expected MATCH after OPTIONAL")
|
|
371
|
-
return None
|
|
372
|
-
self.set_next_token()
|
|
373
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
374
|
-
patterns = list(self._parse_patterns())
|
|
375
|
-
if len(patterns) == 0:
|
|
376
|
-
raise ValueError("Expected graph pattern")
|
|
377
|
-
return Match(patterns, optional)
|
|
378
|
-
|
|
379
|
-
def _parse_create(self) -> Optional[Operation]:
|
|
380
|
-
"""Parse CREATE VIRTUAL statement for nodes and relationships."""
|
|
381
|
-
if not self.token.is_create():
|
|
382
|
-
return None
|
|
383
|
-
self.set_next_token()
|
|
384
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
385
|
-
if not self.token.is_virtual():
|
|
386
|
-
raise ValueError("Expected VIRTUAL")
|
|
387
|
-
self.set_next_token()
|
|
388
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
389
|
-
|
|
390
|
-
node = self._parse_node()
|
|
391
|
-
if node is None:
|
|
392
|
-
raise ValueError("Expected node definition")
|
|
393
|
-
|
|
394
|
-
relationship: Optional[Relationship] = None
|
|
395
|
-
if self.token.is_subtract() and self.peek() and self.peek().is_opening_bracket():
|
|
396
|
-
self.set_next_token() # skip -
|
|
397
|
-
self.set_next_token() # skip [
|
|
398
|
-
if not self.token.is_colon():
|
|
399
|
-
raise ValueError("Expected ':' for relationship type")
|
|
400
|
-
self.set_next_token()
|
|
401
|
-
if not self.token.is_identifier_or_keyword():
|
|
402
|
-
raise ValueError("Expected relationship type identifier")
|
|
403
|
-
rel_type = self.token.value or ""
|
|
404
|
-
self.set_next_token()
|
|
405
|
-
if not self.token.is_closing_bracket():
|
|
406
|
-
raise ValueError("Expected closing bracket for relationship definition")
|
|
407
|
-
self.set_next_token()
|
|
408
|
-
if not self.token.is_subtract():
|
|
409
|
-
raise ValueError("Expected '-' for relationship definition")
|
|
410
|
-
self.set_next_token()
|
|
411
|
-
# Skip optional direction indicator '>'
|
|
412
|
-
if self.token.is_greater_than():
|
|
413
|
-
self.set_next_token()
|
|
414
|
-
target = self._parse_node()
|
|
415
|
-
if target is None:
|
|
416
|
-
raise ValueError("Expected target node definition")
|
|
417
|
-
relationship = Relationship()
|
|
418
|
-
relationship.type = rel_type
|
|
419
|
-
relationship.source = node
|
|
420
|
-
relationship.target = target
|
|
421
|
-
|
|
422
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
423
|
-
if not self.token.is_as():
|
|
424
|
-
raise ValueError("Expected AS")
|
|
425
|
-
self.set_next_token()
|
|
426
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
427
|
-
|
|
428
|
-
query = self._parse_sub_query()
|
|
429
|
-
if query is None:
|
|
430
|
-
raise ValueError("Expected sub-query")
|
|
431
|
-
|
|
432
|
-
if relationship is not None:
|
|
433
|
-
return CreateRelationship(relationship, query)
|
|
434
|
-
else:
|
|
435
|
-
return CreateNode(node, query)
|
|
436
|
-
|
|
437
|
-
def _parse_delete(self) -> Optional[Operation]:
|
|
438
|
-
"""Parse DELETE VIRTUAL statement for nodes and relationships."""
|
|
439
|
-
if not self.token.is_delete():
|
|
440
|
-
return None
|
|
441
|
-
self.set_next_token()
|
|
442
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
443
|
-
if not self.token.is_virtual():
|
|
444
|
-
raise ValueError("Expected VIRTUAL")
|
|
445
|
-
self.set_next_token()
|
|
446
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
447
|
-
|
|
448
|
-
node = self._parse_node()
|
|
449
|
-
if node is None:
|
|
450
|
-
raise ValueError("Expected node definition")
|
|
451
|
-
|
|
452
|
-
relationship: Optional[Relationship] = None
|
|
453
|
-
if self.token.is_subtract() and self.peek() and self.peek().is_opening_bracket():
|
|
454
|
-
self.set_next_token() # skip -
|
|
455
|
-
self.set_next_token() # skip [
|
|
456
|
-
if not self.token.is_colon():
|
|
457
|
-
raise ValueError("Expected ':' for relationship type")
|
|
458
|
-
self.set_next_token()
|
|
459
|
-
if not self.token.is_identifier_or_keyword():
|
|
460
|
-
raise ValueError("Expected relationship type identifier")
|
|
461
|
-
rel_type = self.token.value or ""
|
|
462
|
-
self.set_next_token()
|
|
463
|
-
if not self.token.is_closing_bracket():
|
|
464
|
-
raise ValueError("Expected closing bracket for relationship definition")
|
|
465
|
-
self.set_next_token()
|
|
466
|
-
if not self.token.is_subtract():
|
|
467
|
-
raise ValueError("Expected '-' for relationship definition")
|
|
468
|
-
self.set_next_token()
|
|
469
|
-
# Skip optional direction indicator '>'
|
|
470
|
-
if self.token.is_greater_than():
|
|
471
|
-
self.set_next_token()
|
|
472
|
-
target = self._parse_node()
|
|
473
|
-
if target is None:
|
|
474
|
-
raise ValueError("Expected target node definition")
|
|
475
|
-
relationship = Relationship()
|
|
476
|
-
relationship.type = rel_type
|
|
477
|
-
relationship.source = node
|
|
478
|
-
relationship.target = target
|
|
479
|
-
|
|
480
|
-
if relationship is not None:
|
|
481
|
-
return DeleteRelationship(relationship)
|
|
482
|
-
else:
|
|
483
|
-
return DeleteNode(node)
|
|
484
|
-
|
|
485
|
-
def _parse_union(self) -> Optional[Union]:
|
|
486
|
-
"""Parse a UNION or UNION ALL keyword."""
|
|
487
|
-
if not self.token.is_union():
|
|
488
|
-
return None
|
|
489
|
-
self.set_next_token()
|
|
490
|
-
self._skip_whitespace_and_comments()
|
|
491
|
-
if self.token.is_all():
|
|
492
|
-
union: Union = UnionAll()
|
|
493
|
-
self.set_next_token()
|
|
494
|
-
else:
|
|
495
|
-
union = Union()
|
|
496
|
-
return union
|
|
497
|
-
|
|
498
|
-
def _parse_sub_query(self) -> Optional[ASTNode]:
|
|
499
|
-
"""Parse a sub-query enclosed in braces."""
|
|
500
|
-
if not self.token.is_opening_brace():
|
|
501
|
-
return None
|
|
502
|
-
self.set_next_token()
|
|
503
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
504
|
-
query = self._parse_tokenized(is_sub_query=True)
|
|
505
|
-
self._skip_whitespace_and_comments()
|
|
506
|
-
if not self.token.is_closing_brace():
|
|
507
|
-
raise ValueError("Expected closing brace for sub-query")
|
|
508
|
-
self.set_next_token()
|
|
509
|
-
return query
|
|
510
|
-
|
|
511
|
-
def _parse_patterns(self) -> Iterator[Pattern]:
|
|
512
|
-
while True:
|
|
513
|
-
identifier: Optional[str] = None
|
|
514
|
-
if self.token.is_identifier():
|
|
515
|
-
identifier = self.token.value
|
|
516
|
-
self.set_next_token()
|
|
517
|
-
self._skip_whitespace_and_comments()
|
|
518
|
-
if not self.token.is_equals():
|
|
519
|
-
raise ValueError("Expected '=' for pattern assignment")
|
|
520
|
-
self.set_next_token()
|
|
521
|
-
self._skip_whitespace_and_comments()
|
|
522
|
-
pattern = self._parse_pattern()
|
|
523
|
-
if pattern is not None:
|
|
524
|
-
if identifier is not None:
|
|
525
|
-
pattern.identifier = identifier
|
|
526
|
-
self._state.variables[identifier] = pattern
|
|
527
|
-
yield pattern
|
|
528
|
-
else:
|
|
529
|
-
break
|
|
530
|
-
self._skip_whitespace_and_comments()
|
|
531
|
-
if not self.token.is_comma():
|
|
532
|
-
break
|
|
533
|
-
self.set_next_token()
|
|
534
|
-
self._skip_whitespace_and_comments()
|
|
535
|
-
|
|
536
|
-
def _parse_pattern(self) -> Optional[Pattern]:
|
|
537
|
-
if not self.token.is_left_parenthesis():
|
|
538
|
-
return None
|
|
539
|
-
pattern = Pattern()
|
|
540
|
-
node = self._parse_node()
|
|
541
|
-
if node is None:
|
|
542
|
-
raise ValueError("Expected node definition")
|
|
543
|
-
pattern.add_element(node)
|
|
544
|
-
while True:
|
|
545
|
-
relationship = self._parse_relationship()
|
|
546
|
-
if relationship is None:
|
|
547
|
-
break
|
|
548
|
-
pattern.add_element(relationship)
|
|
549
|
-
node = self._parse_node()
|
|
550
|
-
if node is None:
|
|
551
|
-
raise ValueError("Expected target node definition")
|
|
552
|
-
pattern.add_element(node)
|
|
553
|
-
return pattern
|
|
554
|
-
|
|
555
|
-
def _parse_pattern_expression(self) -> Optional[PatternExpression]:
|
|
556
|
-
"""Parse a pattern expression for WHERE clauses.
|
|
557
|
-
|
|
558
|
-
PatternExpression is used to test if a graph pattern exists.
|
|
559
|
-
It must start with a NodeReference (referencing an existing variable).
|
|
560
|
-
"""
|
|
561
|
-
if not self.token.is_left_parenthesis():
|
|
562
|
-
return None
|
|
563
|
-
pattern = PatternExpression()
|
|
564
|
-
node = self._parse_node()
|
|
565
|
-
if node is None:
|
|
566
|
-
raise ValueError("Expected node definition")
|
|
567
|
-
pattern.add_element(node)
|
|
568
|
-
while True:
|
|
569
|
-
relationship = self._parse_relationship()
|
|
570
|
-
if relationship is None:
|
|
571
|
-
break
|
|
572
|
-
if relationship.hops and relationship.hops.multi():
|
|
573
|
-
raise ValueError("PatternExpression does not support variable-length relationships")
|
|
574
|
-
pattern.add_element(relationship)
|
|
575
|
-
node = self._parse_node()
|
|
576
|
-
if node is None:
|
|
577
|
-
raise ValueError("Expected target node definition")
|
|
578
|
-
pattern.add_element(node)
|
|
579
|
-
pattern.verify()
|
|
580
|
-
return pattern
|
|
581
|
-
|
|
582
|
-
def _parse_node(self) -> Optional[Node]:
|
|
583
|
-
if not self.token.is_left_parenthesis():
|
|
584
|
-
return None
|
|
585
|
-
self.set_next_token()
|
|
586
|
-
self._skip_whitespace_and_comments()
|
|
587
|
-
identifier: Optional[str] = None
|
|
588
|
-
if self.token.is_identifier_or_keyword():
|
|
589
|
-
identifier = self.token.value
|
|
590
|
-
self.set_next_token()
|
|
591
|
-
self._skip_whitespace_and_comments()
|
|
592
|
-
label: Optional[str] = None
|
|
593
|
-
peek = self.peek()
|
|
594
|
-
if not self.token.is_colon() and peek is not None and peek.is_identifier_or_keyword():
|
|
595
|
-
raise ValueError("Expected ':' for node label")
|
|
596
|
-
if self.token.is_colon() and (peek is None or not peek.is_identifier_or_keyword()):
|
|
597
|
-
raise ValueError("Expected node label identifier")
|
|
598
|
-
if self.token.is_colon() and peek is not None and peek.is_identifier_or_keyword():
|
|
599
|
-
self.set_next_token()
|
|
600
|
-
label = cast(str, self.token.value) # Guaranteed by is_identifier check
|
|
601
|
-
self.set_next_token()
|
|
602
|
-
self._skip_whitespace_and_comments()
|
|
603
|
-
node = Node()
|
|
604
|
-
node.label = label
|
|
605
|
-
node.properties = dict(self._parse_properties())
|
|
606
|
-
if identifier is not None and identifier in self._state.variables:
|
|
607
|
-
reference = self._state.variables.get(identifier)
|
|
608
|
-
if reference is None or (
|
|
609
|
-
not isinstance(reference, Node)
|
|
610
|
-
and not isinstance(reference, Unwind)
|
|
611
|
-
and not isinstance(reference, Expression)
|
|
612
|
-
):
|
|
613
|
-
raise ValueError(f"Undefined node reference: {identifier}")
|
|
614
|
-
node = NodeReference(node, reference)
|
|
615
|
-
elif identifier is not None:
|
|
616
|
-
node.identifier = identifier
|
|
617
|
-
self._state.variables[identifier] = node
|
|
618
|
-
if not self.token.is_right_parenthesis():
|
|
619
|
-
raise ValueError("Expected closing parenthesis for node definition")
|
|
620
|
-
self.set_next_token()
|
|
621
|
-
return node
|
|
622
|
-
|
|
623
|
-
def _parse_relationship(self) -> Optional[Relationship]:
|
|
624
|
-
direction = "right"
|
|
625
|
-
if self.token.is_less_than() and self.peek() is not None and self.peek().is_subtract():
|
|
626
|
-
direction = "left"
|
|
627
|
-
self.set_next_token()
|
|
628
|
-
self.set_next_token()
|
|
629
|
-
elif self.token.is_subtract():
|
|
630
|
-
self.set_next_token()
|
|
631
|
-
else:
|
|
632
|
-
return None
|
|
633
|
-
if not self.token.is_opening_bracket():
|
|
634
|
-
return None
|
|
635
|
-
self.set_next_token()
|
|
636
|
-
variable: Optional[str] = None
|
|
637
|
-
if self.token.is_identifier_or_keyword():
|
|
638
|
-
variable = self.token.value
|
|
639
|
-
self.set_next_token()
|
|
640
|
-
if not self.token.is_colon():
|
|
641
|
-
raise ValueError("Expected ':' for relationship type")
|
|
642
|
-
self.set_next_token()
|
|
643
|
-
if not self.token.is_identifier_or_keyword():
|
|
644
|
-
raise ValueError("Expected relationship type identifier")
|
|
645
|
-
rel_types: List[str] = [self.token.value or ""]
|
|
646
|
-
self.set_next_token()
|
|
647
|
-
while self.token.is_pipe():
|
|
648
|
-
self.set_next_token()
|
|
649
|
-
if self.token.is_colon():
|
|
650
|
-
self.set_next_token()
|
|
651
|
-
if not self.token.is_identifier_or_keyword():
|
|
652
|
-
raise ValueError("Expected relationship type identifier after '|'")
|
|
653
|
-
rel_types.append(self.token.value or "")
|
|
654
|
-
self.set_next_token()
|
|
655
|
-
hops = self._parse_relationship_hops()
|
|
656
|
-
properties: Dict[str, Expression] = dict(self._parse_properties())
|
|
657
|
-
if not self.token.is_closing_bracket():
|
|
658
|
-
raise ValueError("Expected closing bracket for relationship definition")
|
|
659
|
-
self.set_next_token()
|
|
660
|
-
if not self.token.is_subtract():
|
|
661
|
-
raise ValueError("Expected '-' for relationship definition")
|
|
662
|
-
self.set_next_token()
|
|
663
|
-
if self.token.is_greater_than():
|
|
664
|
-
self.set_next_token()
|
|
665
|
-
relationship = Relationship()
|
|
666
|
-
relationship.direction = direction
|
|
667
|
-
relationship.properties = properties
|
|
668
|
-
if variable is not None and variable in self._state.variables:
|
|
669
|
-
reference = self._state.variables.get(variable)
|
|
670
|
-
# Resolve through Expression -> Reference -> Relationship (e.g., after WITH)
|
|
671
|
-
first = reference.first_child() if isinstance(reference, Expression) else None
|
|
672
|
-
if isinstance(first, Reference):
|
|
673
|
-
inner = first.referred
|
|
674
|
-
if isinstance(inner, Relationship):
|
|
675
|
-
reference = inner
|
|
676
|
-
if reference is None or not isinstance(reference, Relationship):
|
|
677
|
-
raise ValueError(f"Undefined relationship reference: {variable}")
|
|
678
|
-
relationship = RelationshipReference(relationship, reference)
|
|
679
|
-
elif variable is not None:
|
|
680
|
-
relationship.identifier = variable
|
|
681
|
-
self._state.variables[variable] = relationship
|
|
682
|
-
if hops is not None:
|
|
683
|
-
relationship.hops = hops
|
|
684
|
-
relationship.types = rel_types
|
|
685
|
-
return relationship
|
|
686
|
-
|
|
687
|
-
def _parse_properties(self) -> Iterator[Tuple[str, Expression]]:
|
|
688
|
-
parts: int = 0
|
|
689
|
-
while True:
|
|
690
|
-
self._skip_whitespace_and_comments()
|
|
691
|
-
if not self.token.is_opening_brace() and parts == 0:
|
|
692
|
-
return
|
|
693
|
-
elif not self.token.is_opening_brace() and parts > 0:
|
|
694
|
-
raise ValueError("Expected opening brace")
|
|
695
|
-
self.set_next_token()
|
|
696
|
-
self._skip_whitespace_and_comments()
|
|
697
|
-
if not self.token.is_identifier():
|
|
698
|
-
raise ValueError("Expected identifier")
|
|
699
|
-
key: str = self.token.value or ""
|
|
700
|
-
self.set_next_token()
|
|
701
|
-
self._skip_whitespace_and_comments()
|
|
702
|
-
if not self.token.is_colon():
|
|
703
|
-
raise ValueError("Expected colon")
|
|
704
|
-
self.set_next_token()
|
|
705
|
-
self._skip_whitespace_and_comments()
|
|
706
|
-
expression = self._parse_expression()
|
|
707
|
-
if expression is None:
|
|
708
|
-
raise ValueError("Expected expression")
|
|
709
|
-
self._skip_whitespace_and_comments()
|
|
710
|
-
if not self.token.is_closing_brace():
|
|
711
|
-
raise ValueError("Expected closing brace")
|
|
712
|
-
self.set_next_token()
|
|
713
|
-
yield (key, expression)
|
|
714
|
-
self._skip_whitespace_and_comments()
|
|
715
|
-
if not self.token.is_comma():
|
|
716
|
-
break
|
|
717
|
-
self.set_next_token()
|
|
718
|
-
parts += 1
|
|
719
|
-
|
|
720
|
-
def _parse_relationship_hops(self) -> Optional[Hops]:
|
|
721
|
-
if not self.token.is_multiply():
|
|
722
|
-
return None
|
|
723
|
-
hops = Hops()
|
|
724
|
-
self.set_next_token()
|
|
725
|
-
if self.token.is_number():
|
|
726
|
-
hops.min = int(self.token.value or "0")
|
|
727
|
-
self.set_next_token()
|
|
728
|
-
if self.token.is_dot():
|
|
729
|
-
self.set_next_token()
|
|
730
|
-
if not self.token.is_dot():
|
|
731
|
-
raise ValueError("Expected '..' for relationship hops")
|
|
732
|
-
self.set_next_token()
|
|
733
|
-
if not self.token.is_number():
|
|
734
|
-
hops.max = sys.maxsize
|
|
735
|
-
else:
|
|
736
|
-
hops.max = int(self.token.value or "0")
|
|
737
|
-
self.set_next_token()
|
|
738
|
-
else:
|
|
739
|
-
# Just * without numbers means unbounded
|
|
740
|
-
hops.min = 0
|
|
741
|
-
hops.max = sys.maxsize
|
|
742
|
-
return hops
|
|
743
|
-
|
|
744
|
-
def _parse_limit(self) -> Optional[Limit]:
|
|
745
|
-
self._skip_whitespace_and_comments()
|
|
746
|
-
if not self.token.is_limit():
|
|
747
|
-
return None
|
|
748
|
-
self._expect_previous_token_to_be_whitespace_or_comment()
|
|
749
|
-
self.set_next_token()
|
|
750
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
751
|
-
if not self.token.is_number():
|
|
752
|
-
raise ValueError("Expected number")
|
|
753
|
-
limit = Limit(int(self.token.value or "0"))
|
|
754
|
-
self.set_next_token()
|
|
755
|
-
return limit
|
|
756
|
-
|
|
757
|
-
def _parse_order_by(self) -> Optional[OrderBy]:
|
|
758
|
-
self._skip_whitespace_and_comments()
|
|
759
|
-
if not self.token.is_order():
|
|
760
|
-
return None
|
|
761
|
-
self._expect_previous_token_to_be_whitespace_or_comment()
|
|
762
|
-
self.set_next_token()
|
|
763
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
764
|
-
if not self.token.is_by():
|
|
765
|
-
raise ValueError("Expected BY after ORDER")
|
|
766
|
-
self.set_next_token()
|
|
767
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
768
|
-
fields: list[SortField] = []
|
|
769
|
-
while True:
|
|
770
|
-
expression = self._parse_expression()
|
|
771
|
-
if expression is None:
|
|
772
|
-
raise ValueError("Expected expression in ORDER BY")
|
|
773
|
-
self._skip_whitespace_and_comments()
|
|
774
|
-
direction = "asc"
|
|
775
|
-
if self.token.is_asc():
|
|
776
|
-
direction = "asc"
|
|
777
|
-
self.set_next_token()
|
|
778
|
-
self._skip_whitespace_and_comments()
|
|
779
|
-
elif self.token.is_desc():
|
|
780
|
-
direction = "desc"
|
|
781
|
-
self.set_next_token()
|
|
782
|
-
self._skip_whitespace_and_comments()
|
|
783
|
-
fields.append(SortField(expression, direction))
|
|
784
|
-
if self.token.is_comma():
|
|
785
|
-
self.set_next_token()
|
|
786
|
-
self._skip_whitespace_and_comments()
|
|
787
|
-
else:
|
|
788
|
-
break
|
|
789
|
-
return OrderBy(fields)
|
|
790
|
-
|
|
791
|
-
def _parse_expressions(
|
|
792
|
-
self, alias_option: AliasOption = AliasOption.NOT_ALLOWED
|
|
793
|
-
) -> List[Expression]:
|
|
794
|
-
"""Parse a comma-separated list of expressions with deferred variable
|
|
795
|
-
registration. Aliases set by earlier expressions in the same clause
|
|
796
|
-
won't shadow variables needed by later expressions
|
|
797
|
-
(e.g. ``RETURN a.x AS a, a.y AS b``)."""
|
|
798
|
-
parsed = list(self.__parse_expressions(alias_option))
|
|
799
|
-
for expression, variable_name in parsed:
|
|
800
|
-
if variable_name is not None:
|
|
801
|
-
self._state.variables[variable_name] = expression
|
|
802
|
-
return [expression for expression, _ in parsed]
|
|
803
|
-
|
|
804
|
-
def __parse_expressions(
|
|
805
|
-
self, alias_option: AliasOption
|
|
806
|
-
) -> Iterator[Tuple[Expression, Optional[str]]]:
|
|
807
|
-
while True:
|
|
808
|
-
expression = self._parse_expression()
|
|
809
|
-
if expression is not None:
|
|
810
|
-
variable_name: Optional[str] = None
|
|
811
|
-
alias = self._parse_alias()
|
|
812
|
-
if isinstance(expression.first_child(), Reference) and alias is None:
|
|
813
|
-
reference = expression.first_child()
|
|
814
|
-
assert isinstance(reference, Reference) # For type narrowing
|
|
815
|
-
expression.set_alias(reference.identifier)
|
|
816
|
-
variable_name = reference.identifier
|
|
817
|
-
elif (alias_option == AliasOption.REQUIRED and
|
|
818
|
-
alias is None and
|
|
819
|
-
not isinstance(expression.first_child(), Reference)):
|
|
820
|
-
raise ValueError("Alias required")
|
|
821
|
-
elif alias_option == AliasOption.NOT_ALLOWED and alias is not None:
|
|
822
|
-
raise ValueError("Alias not allowed")
|
|
823
|
-
elif alias_option in (AliasOption.OPTIONAL, AliasOption.REQUIRED) and alias is not None:
|
|
824
|
-
expression.set_alias(alias.get_alias())
|
|
825
|
-
variable_name = alias.get_alias()
|
|
826
|
-
yield expression, variable_name
|
|
827
|
-
else:
|
|
828
|
-
break
|
|
829
|
-
self._skip_whitespace_and_comments()
|
|
830
|
-
if not self.token.is_comma():
|
|
831
|
-
break
|
|
832
|
-
self.set_next_token()
|
|
833
|
-
|
|
834
|
-
def _parse_operand(self, expression: Expression) -> bool:
|
|
835
|
-
"""Parse a single operand (without operators). Returns True if an operand was parsed."""
|
|
836
|
-
self._skip_whitespace_and_comments()
|
|
837
|
-
if self.token.is_identifier_or_keyword() and (self.peek() is None or not self.peek().is_left_parenthesis()):
|
|
838
|
-
identifier = self.token.value or ""
|
|
839
|
-
reference = Reference(identifier, self._state.variables.get(identifier))
|
|
840
|
-
self.set_next_token()
|
|
841
|
-
lookup = self._parse_lookup(reference)
|
|
842
|
-
expression.add_node(lookup)
|
|
843
|
-
return True
|
|
844
|
-
elif self.token.is_identifier_or_keyword() and self.peek() is not None and self.peek().is_left_parenthesis():
|
|
845
|
-
func = self._parse_predicate_function() or self._parse_function()
|
|
846
|
-
if func is not None:
|
|
847
|
-
lookup = self._parse_lookup(func)
|
|
848
|
-
expression.add_node(lookup)
|
|
849
|
-
return True
|
|
850
|
-
elif (
|
|
851
|
-
self.token.is_left_parenthesis()
|
|
852
|
-
and self._looks_like_node_pattern()
|
|
853
|
-
):
|
|
854
|
-
# Possible graph pattern expression
|
|
855
|
-
pattern = self._parse_pattern_expression()
|
|
856
|
-
if pattern is not None:
|
|
857
|
-
expression.add_node(pattern)
|
|
858
|
-
return True
|
|
859
|
-
elif self.token.is_operand():
|
|
860
|
-
expression.add_node(self.token.node)
|
|
861
|
-
self.set_next_token()
|
|
862
|
-
return True
|
|
863
|
-
elif self.token.is_f_string():
|
|
864
|
-
f_string = self._parse_f_string()
|
|
865
|
-
if f_string is None:
|
|
866
|
-
raise ValueError("Expected f-string")
|
|
867
|
-
expression.add_node(f_string)
|
|
868
|
-
return True
|
|
869
|
-
elif self.token.is_left_parenthesis():
|
|
870
|
-
self.set_next_token()
|
|
871
|
-
sub = self._parse_expression()
|
|
872
|
-
if sub is None:
|
|
873
|
-
raise ValueError("Expected expression")
|
|
874
|
-
if not self.token.is_right_parenthesis():
|
|
875
|
-
raise ValueError("Expected right parenthesis")
|
|
876
|
-
self.set_next_token()
|
|
877
|
-
lookup = self._parse_lookup(sub)
|
|
878
|
-
expression.add_node(lookup)
|
|
879
|
-
return True
|
|
880
|
-
elif self.token.is_opening_brace() or self.token.is_opening_bracket():
|
|
881
|
-
json = self._parse_json()
|
|
882
|
-
if json is None:
|
|
883
|
-
raise ValueError("Expected JSON object")
|
|
884
|
-
lookup = self._parse_lookup(json)
|
|
885
|
-
expression.add_node(lookup)
|
|
886
|
-
return True
|
|
887
|
-
elif self.token.is_case():
|
|
888
|
-
case = self._parse_case()
|
|
889
|
-
if case is None:
|
|
890
|
-
raise ValueError("Expected CASE statement")
|
|
891
|
-
expression.add_node(case)
|
|
892
|
-
return True
|
|
893
|
-
elif self.token.is_not():
|
|
894
|
-
not_node = Not()
|
|
895
|
-
self.set_next_token()
|
|
896
|
-
# NOT should only bind to the next operand, not the entire expression
|
|
897
|
-
# Create a temporary expression to parse just one operand
|
|
898
|
-
temp_expr = Expression()
|
|
899
|
-
if not self._parse_operand(temp_expr):
|
|
900
|
-
raise ValueError("Expected expression after NOT")
|
|
901
|
-
temp_expr.finish()
|
|
902
|
-
not_node.add_child(temp_expr)
|
|
903
|
-
expression.add_node(not_node)
|
|
904
|
-
return True
|
|
905
|
-
return False
|
|
906
|
-
|
|
907
|
-
def _parse_expression(self) -> Optional[Expression]:
|
|
908
|
-
expression = Expression()
|
|
909
|
-
while True:
|
|
910
|
-
if not self._parse_operand(expression):
|
|
911
|
-
if expression.nodes_added():
|
|
912
|
-
raise ValueError("Expected operand or left parenthesis")
|
|
913
|
-
else:
|
|
914
|
-
break
|
|
915
|
-
self._skip_whitespace_and_comments()
|
|
916
|
-
if self.token.is_operator():
|
|
917
|
-
if self.token.is_is():
|
|
918
|
-
expression.add_node(self._parse_is_operator())
|
|
919
|
-
else:
|
|
920
|
-
expression.add_node(self.token.node)
|
|
921
|
-
elif self.token.is_in():
|
|
922
|
-
expression.add_node(self._parse_in_operator())
|
|
923
|
-
elif self.token.is_contains():
|
|
924
|
-
expression.add_node(self._parse_contains_operator())
|
|
925
|
-
elif self.token.is_starts():
|
|
926
|
-
expression.add_node(self._parse_starts_with_operator())
|
|
927
|
-
elif self.token.is_ends():
|
|
928
|
-
expression.add_node(self._parse_ends_with_operator())
|
|
929
|
-
elif self.token.is_not():
|
|
930
|
-
not_op = self._parse_not_operator()
|
|
931
|
-
if not_op is None:
|
|
932
|
-
break
|
|
933
|
-
expression.add_node(not_op)
|
|
934
|
-
else:
|
|
935
|
-
break
|
|
936
|
-
self.set_next_token()
|
|
937
|
-
|
|
938
|
-
if expression.nodes_added():
|
|
939
|
-
expression.finish()
|
|
940
|
-
return expression
|
|
941
|
-
return None
|
|
942
|
-
|
|
943
|
-
def _looks_like_node_pattern(self) -> bool:
|
|
944
|
-
"""Peek ahead from a left parenthesis to determine whether the
|
|
945
|
-
upcoming tokens form a graph-node pattern (e.g. (n:Label), (n),
|
|
946
|
-
(:Label), ()) rather than a parenthesised expression (e.g.
|
|
947
|
-
(variable.property), (a + b)).
|
|
948
|
-
"""
|
|
949
|
-
saved_index = self._token_index
|
|
950
|
-
self.set_next_token() # skip '('
|
|
951
|
-
self._skip_whitespace_and_comments()
|
|
952
|
-
|
|
953
|
-
if self.token.is_colon() or self.token.is_right_parenthesis():
|
|
954
|
-
self._token_index = saved_index
|
|
955
|
-
return True
|
|
956
|
-
|
|
957
|
-
if self.token.is_identifier_or_keyword():
|
|
958
|
-
self.set_next_token() # skip identifier
|
|
959
|
-
self._skip_whitespace_and_comments()
|
|
960
|
-
result = (
|
|
961
|
-
self.token.is_colon()
|
|
962
|
-
or self.token.is_opening_brace()
|
|
963
|
-
or self.token.is_right_parenthesis()
|
|
964
|
-
)
|
|
965
|
-
self._token_index = saved_index
|
|
966
|
-
return result
|
|
967
|
-
|
|
968
|
-
self._token_index = saved_index
|
|
969
|
-
return False
|
|
970
|
-
|
|
971
|
-
def _parse_is_operator(self) -> ASTNode:
|
|
972
|
-
"""Parse IS or IS NOT operator."""
|
|
973
|
-
# Current token is IS. Look ahead for NOT to produce IS NOT.
|
|
974
|
-
saved_index = self._token_index
|
|
975
|
-
self.set_next_token()
|
|
976
|
-
self._skip_whitespace_and_comments()
|
|
977
|
-
if self.token.is_not():
|
|
978
|
-
return IsNot()
|
|
979
|
-
# Not IS NOT — restore position to IS so the outer loop's set_next_token advances past it.
|
|
980
|
-
self._token_index = saved_index
|
|
981
|
-
return Is()
|
|
982
|
-
|
|
983
|
-
def _parse_in_operator(self) -> In:
|
|
984
|
-
"""Parse IN operator."""
|
|
985
|
-
# Current token is IN. Advance past it so the outer loop's set_next_token moves correctly.
|
|
986
|
-
return In()
|
|
987
|
-
|
|
988
|
-
def _parse_contains_operator(self) -> Contains:
|
|
989
|
-
"""Parse CONTAINS operator."""
|
|
990
|
-
return Contains()
|
|
991
|
-
|
|
992
|
-
def _parse_starts_with_operator(self) -> StartsWith:
|
|
993
|
-
"""Parse STARTS WITH operator."""
|
|
994
|
-
# Current token is STARTS. Look ahead for WITH.
|
|
995
|
-
saved_index = self._token_index
|
|
996
|
-
self.set_next_token()
|
|
997
|
-
self._skip_whitespace_and_comments()
|
|
998
|
-
if self.token.is_with():
|
|
999
|
-
return StartsWith()
|
|
1000
|
-
self._token_index = saved_index
|
|
1001
|
-
raise ValueError("Expected WITH after STARTS")
|
|
1002
|
-
|
|
1003
|
-
def _parse_ends_with_operator(self) -> EndsWith:
|
|
1004
|
-
"""Parse ENDS WITH operator."""
|
|
1005
|
-
# Current token is ENDS. Look ahead for WITH.
|
|
1006
|
-
saved_index = self._token_index
|
|
1007
|
-
self.set_next_token()
|
|
1008
|
-
self._skip_whitespace_and_comments()
|
|
1009
|
-
if self.token.is_with():
|
|
1010
|
-
return EndsWith()
|
|
1011
|
-
self._token_index = saved_index
|
|
1012
|
-
raise ValueError("Expected WITH after ENDS")
|
|
1013
|
-
|
|
1014
|
-
def _parse_not_operator(self) -> NotIn | NotContains | NotStartsWith | NotEndsWith | None:
|
|
1015
|
-
"""Parse NOT IN, NOT CONTAINS, NOT STARTS WITH, or NOT ENDS WITH operator."""
|
|
1016
|
-
saved_index = self._token_index
|
|
1017
|
-
self.set_next_token()
|
|
1018
|
-
self._skip_whitespace_and_comments()
|
|
1019
|
-
if self.token.is_in():
|
|
1020
|
-
return NotIn()
|
|
1021
|
-
if self.token.is_contains():
|
|
1022
|
-
return NotContains()
|
|
1023
|
-
if self.token.is_starts():
|
|
1024
|
-
self.set_next_token()
|
|
1025
|
-
self._skip_whitespace_and_comments()
|
|
1026
|
-
if self.token.is_with():
|
|
1027
|
-
return NotStartsWith()
|
|
1028
|
-
self._token_index = saved_index
|
|
1029
|
-
return None
|
|
1030
|
-
if self.token.is_ends():
|
|
1031
|
-
self.set_next_token()
|
|
1032
|
-
self._skip_whitespace_and_comments()
|
|
1033
|
-
if self.token.is_with():
|
|
1034
|
-
return NotEndsWith()
|
|
1035
|
-
self._token_index = saved_index
|
|
1036
|
-
return None
|
|
1037
|
-
# Not a recognized NOT operator — restore position and let the outer loop break.
|
|
1038
|
-
self._token_index = saved_index
|
|
1039
|
-
return None
|
|
1040
|
-
|
|
1041
|
-
def _parse_lookup(self, node: ASTNode) -> ASTNode:
|
|
1042
|
-
variable = node
|
|
1043
|
-
lookup: Lookup | RangeLookup | None = None
|
|
1044
|
-
while True:
|
|
1045
|
-
if self.token.is_dot():
|
|
1046
|
-
self.set_next_token()
|
|
1047
|
-
if not self.token.is_identifier() and not self.token.is_keyword():
|
|
1048
|
-
raise ValueError("Expected identifier")
|
|
1049
|
-
lookup = Lookup()
|
|
1050
|
-
lookup.index = Identifier(self.token.value or "")
|
|
1051
|
-
lookup.variable = variable
|
|
1052
|
-
self.set_next_token()
|
|
1053
|
-
elif self.token.is_opening_bracket():
|
|
1054
|
-
self.set_next_token()
|
|
1055
|
-
self._skip_whitespace_and_comments()
|
|
1056
|
-
index = self._parse_expression()
|
|
1057
|
-
to = None
|
|
1058
|
-
self._skip_whitespace_and_comments()
|
|
1059
|
-
if self.token.is_colon():
|
|
1060
|
-
self.set_next_token()
|
|
1061
|
-
self._skip_whitespace_and_comments()
|
|
1062
|
-
lookup = RangeLookup()
|
|
1063
|
-
to = self._parse_expression()
|
|
1064
|
-
else:
|
|
1065
|
-
if index is None:
|
|
1066
|
-
raise ValueError("Expected expression")
|
|
1067
|
-
lookup = Lookup()
|
|
1068
|
-
self._skip_whitespace_and_comments()
|
|
1069
|
-
if not self.token.is_closing_bracket():
|
|
1070
|
-
raise ValueError("Expected closing bracket")
|
|
1071
|
-
self.set_next_token()
|
|
1072
|
-
if isinstance(lookup, RangeLookup):
|
|
1073
|
-
lookup.from_ = index or Null()
|
|
1074
|
-
lookup.to = to or Null()
|
|
1075
|
-
elif isinstance(lookup, Lookup) and index is not None:
|
|
1076
|
-
lookup.index = index
|
|
1077
|
-
lookup.variable = variable
|
|
1078
|
-
else:
|
|
1079
|
-
break
|
|
1080
|
-
variable = lookup or variable
|
|
1081
|
-
return variable
|
|
1082
|
-
|
|
1083
|
-
def _parse_case(self) -> Optional[Case]:
|
|
1084
|
-
if not self.token.is_case():
|
|
1085
|
-
return None
|
|
1086
|
-
self.set_next_token()
|
|
1087
|
-
case = Case()
|
|
1088
|
-
parts = 0
|
|
1089
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
1090
|
-
while True:
|
|
1091
|
-
when = self._parse_when()
|
|
1092
|
-
if when is None and parts == 0:
|
|
1093
|
-
raise ValueError("Expected WHEN")
|
|
1094
|
-
elif when is None and parts > 0:
|
|
1095
|
-
break
|
|
1096
|
-
elif when is not None:
|
|
1097
|
-
case.add_child(when)
|
|
1098
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
1099
|
-
then = self._parse_then()
|
|
1100
|
-
if then is None:
|
|
1101
|
-
raise ValueError("Expected THEN")
|
|
1102
|
-
else:
|
|
1103
|
-
case.add_child(then)
|
|
1104
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
1105
|
-
parts += 1
|
|
1106
|
-
else_ = self._parse_else()
|
|
1107
|
-
if else_ is None:
|
|
1108
|
-
raise ValueError("Expected ELSE")
|
|
1109
|
-
else:
|
|
1110
|
-
case.add_child(else_)
|
|
1111
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
1112
|
-
if not self.token.is_end():
|
|
1113
|
-
raise ValueError("Expected END")
|
|
1114
|
-
self.set_next_token()
|
|
1115
|
-
return case
|
|
1116
|
-
|
|
1117
|
-
def _parse_when(self) -> Optional[When]:
|
|
1118
|
-
if not self.token.is_when():
|
|
1119
|
-
return None
|
|
1120
|
-
self.set_next_token()
|
|
1121
|
-
when = When()
|
|
1122
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
1123
|
-
expression = self._parse_expression()
|
|
1124
|
-
if expression is None:
|
|
1125
|
-
raise ValueError("Expected expression")
|
|
1126
|
-
when.add_child(expression)
|
|
1127
|
-
return when
|
|
1128
|
-
|
|
1129
|
-
def _parse_then(self) -> Optional[Then]:
|
|
1130
|
-
if not self.token.is_then():
|
|
1131
|
-
return None
|
|
1132
|
-
self.set_next_token()
|
|
1133
|
-
then = Then()
|
|
1134
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
1135
|
-
expression = self._parse_expression()
|
|
1136
|
-
if expression is None:
|
|
1137
|
-
raise ValueError("Expected expression")
|
|
1138
|
-
then.add_child(expression)
|
|
1139
|
-
return then
|
|
1140
|
-
|
|
1141
|
-
def _parse_else(self) -> Optional[Else]:
|
|
1142
|
-
if not self.token.is_else():
|
|
1143
|
-
return None
|
|
1144
|
-
self.set_next_token()
|
|
1145
|
-
else_ = Else()
|
|
1146
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
1147
|
-
expression = self._parse_expression()
|
|
1148
|
-
if expression is None:
|
|
1149
|
-
raise ValueError("Expected expression")
|
|
1150
|
-
else_.add_child(expression)
|
|
1151
|
-
return else_
|
|
1152
|
-
|
|
1153
|
-
def _parse_alias(self) -> Optional[Alias]:
|
|
1154
|
-
self._skip_whitespace_and_comments()
|
|
1155
|
-
if not self.token.is_as():
|
|
1156
|
-
return None
|
|
1157
|
-
self._expect_previous_token_to_be_whitespace_or_comment()
|
|
1158
|
-
self.set_next_token()
|
|
1159
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
1160
|
-
if not self.token.is_identifier_or_keyword():
|
|
1161
|
-
raise ValueError("Expected identifier")
|
|
1162
|
-
alias = Alias(self.token.value or "")
|
|
1163
|
-
self.set_next_token()
|
|
1164
|
-
return alias
|
|
1165
|
-
|
|
1166
|
-
def _parse_predicate_function(self) -> Optional[PredicateFunction]:
|
|
1167
|
-
"""Parse a predicate function like sum(n in [...] | n where condition)."""
|
|
1168
|
-
# Lookahead: identifier ( identifier in
|
|
1169
|
-
if not self.ahead([
|
|
1170
|
-
Token.IDENTIFIER(""),
|
|
1171
|
-
Token.LEFT_PARENTHESIS(),
|
|
1172
|
-
Token.IDENTIFIER(""),
|
|
1173
|
-
Token.IN(),
|
|
1174
|
-
]):
|
|
1175
|
-
return None
|
|
1176
|
-
if self.token.value is None:
|
|
1177
|
-
raise ValueError("Expected identifier")
|
|
1178
|
-
func = FunctionFactory.create_predicate(self.token.value)
|
|
1179
|
-
self.set_next_token()
|
|
1180
|
-
if not self.token.is_left_parenthesis():
|
|
1181
|
-
raise ValueError("Expected left parenthesis")
|
|
1182
|
-
self.set_next_token()
|
|
1183
|
-
self._skip_whitespace_and_comments()
|
|
1184
|
-
if not self.token.is_identifier():
|
|
1185
|
-
raise ValueError("Expected identifier")
|
|
1186
|
-
reference = Reference(self.token.value)
|
|
1187
|
-
self._state.variables[reference.identifier] = reference
|
|
1188
|
-
func.add_child(reference)
|
|
1189
|
-
self.set_next_token()
|
|
1190
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
1191
|
-
if not self.token.is_in():
|
|
1192
|
-
raise ValueError("Expected IN")
|
|
1193
|
-
self.set_next_token()
|
|
1194
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
1195
|
-
expression = self._parse_expression()
|
|
1196
|
-
if expression is None:
|
|
1197
|
-
raise ValueError("Expected expression")
|
|
1198
|
-
if not ObjectUtils.is_instance_of_any(expression.first_child(), [
|
|
1199
|
-
JSONArray,
|
|
1200
|
-
Reference,
|
|
1201
|
-
Lookup,
|
|
1202
|
-
Function,
|
|
1203
|
-
]):
|
|
1204
|
-
raise ValueError("Expected array or reference")
|
|
1205
|
-
func.add_child(expression)
|
|
1206
|
-
self._skip_whitespace_and_comments()
|
|
1207
|
-
if not self.token.is_pipe():
|
|
1208
|
-
raise ValueError("Expected pipe")
|
|
1209
|
-
self.set_next_token()
|
|
1210
|
-
return_expr = self._parse_expression()
|
|
1211
|
-
if return_expr is None:
|
|
1212
|
-
raise ValueError("Expected expression")
|
|
1213
|
-
func.add_child(return_expr)
|
|
1214
|
-
where = self._parse_where()
|
|
1215
|
-
if where is not None:
|
|
1216
|
-
func.add_child(where)
|
|
1217
|
-
self._skip_whitespace_and_comments()
|
|
1218
|
-
if not self.token.is_right_parenthesis():
|
|
1219
|
-
raise ValueError("Expected right parenthesis")
|
|
1220
|
-
self.set_next_token()
|
|
1221
|
-
del self._state.variables[reference.identifier]
|
|
1222
|
-
return func
|
|
1223
|
-
|
|
1224
|
-
def _parse_function(self) -> Optional[Function]:
|
|
1225
|
-
if not self.token.is_identifier():
|
|
1226
|
-
return None
|
|
1227
|
-
name = self.token.value or ""
|
|
1228
|
-
if not self.peek() or not self.peek().is_left_parenthesis():
|
|
1229
|
-
return None
|
|
1230
|
-
|
|
1231
|
-
try:
|
|
1232
|
-
func = FunctionFactory.create(name)
|
|
1233
|
-
except ValueError:
|
|
1234
|
-
raise ValueError(f"Unknown function: {name}")
|
|
1235
|
-
|
|
1236
|
-
# Check for nested aggregate functions
|
|
1237
|
-
if isinstance(func, AggregateFunction) and self._state.context.contains_type(AggregateFunction):
|
|
1238
|
-
raise ValueError("Aggregate functions cannot be nested")
|
|
1239
|
-
|
|
1240
|
-
self._state.context.push(func)
|
|
1241
|
-
self.set_next_token() # skip function name
|
|
1242
|
-
self.set_next_token() # skip left parenthesis
|
|
1243
|
-
self._skip_whitespace_and_comments()
|
|
1244
|
-
|
|
1245
|
-
# Check for DISTINCT keyword
|
|
1246
|
-
if self.token.is_distinct():
|
|
1247
|
-
func.distinct = True
|
|
1248
|
-
self.set_next_token()
|
|
1249
|
-
self._expect_and_skip_whitespace_and_comments()
|
|
1250
|
-
|
|
1251
|
-
params = list(self._parse_function_parameters())
|
|
1252
|
-
func.parameters = params
|
|
1253
|
-
|
|
1254
|
-
if not self.token.is_right_parenthesis():
|
|
1255
|
-
raise ValueError("Expected right parenthesis")
|
|
1256
|
-
self.set_next_token()
|
|
1257
|
-
self._state.context.pop()
|
|
1258
|
-
return func
|
|
1259
|
-
|
|
1260
|
-
def _parse_async_function(self) -> Optional[AsyncFunction]:
|
|
1261
|
-
if not self.token.is_identifier():
|
|
1262
|
-
return None
|
|
1263
|
-
name = self.token.value or ""
|
|
1264
|
-
if not FunctionFactory.is_async_provider(name):
|
|
1265
|
-
return None
|
|
1266
|
-
self.set_next_token()
|
|
1267
|
-
if not self.token.is_left_parenthesis():
|
|
1268
|
-
raise ValueError("Expected left parenthesis")
|
|
1269
|
-
self.set_next_token()
|
|
1270
|
-
|
|
1271
|
-
func = FunctionFactory.create_async(name)
|
|
1272
|
-
params = list(self._parse_function_parameters())
|
|
1273
|
-
func.parameters = params
|
|
1274
|
-
|
|
1275
|
-
if not self.token.is_right_parenthesis():
|
|
1276
|
-
raise ValueError("Expected right parenthesis")
|
|
1277
|
-
self.set_next_token()
|
|
1278
|
-
return func
|
|
1279
|
-
|
|
1280
|
-
def _parse_function_parameters(self) -> Iterator[ASTNode]:
|
|
1281
|
-
while True:
|
|
1282
|
-
self._skip_whitespace_and_comments()
|
|
1283
|
-
if self.token.is_right_parenthesis():
|
|
1284
|
-
break
|
|
1285
|
-
expr = self._parse_expression()
|
|
1286
|
-
if expr is not None:
|
|
1287
|
-
yield expr
|
|
1288
|
-
self._skip_whitespace_and_comments()
|
|
1289
|
-
if not self.token.is_comma():
|
|
1290
|
-
break
|
|
1291
|
-
self.set_next_token()
|
|
1292
|
-
|
|
1293
|
-
def _parse_json(self) -> Optional[ASTNode]:
|
|
1294
|
-
if self.token.is_opening_brace():
|
|
1295
|
-
return self._parse_associative_array()
|
|
1296
|
-
elif self.token.is_opening_bracket():
|
|
1297
|
-
return self._parse_json_array()
|
|
1298
|
-
return None
|
|
1299
|
-
|
|
1300
|
-
def _parse_associative_array(self) -> AssociativeArray:
|
|
1301
|
-
if not self.token.is_opening_brace():
|
|
1302
|
-
raise ValueError("Expected opening brace")
|
|
1303
|
-
self.set_next_token()
|
|
1304
|
-
array = AssociativeArray()
|
|
1305
|
-
while True:
|
|
1306
|
-
self._skip_whitespace_and_comments()
|
|
1307
|
-
if self.token.is_closing_brace():
|
|
1308
|
-
break
|
|
1309
|
-
if not self.token.is_identifier() and not self.token.is_string() and not self.token.is_keyword():
|
|
1310
|
-
raise ValueError("Expected key identifier or string")
|
|
1311
|
-
key = self.token.value or ""
|
|
1312
|
-
self.set_next_token()
|
|
1313
|
-
self._skip_whitespace_and_comments()
|
|
1314
|
-
if not self.token.is_colon():
|
|
1315
|
-
raise ValueError("Expected colon")
|
|
1316
|
-
self.set_next_token()
|
|
1317
|
-
self._skip_whitespace_and_comments()
|
|
1318
|
-
value = self._parse_expression()
|
|
1319
|
-
if value is None:
|
|
1320
|
-
raise ValueError("Expected value")
|
|
1321
|
-
array.add_key_value(KeyValuePair(key, value))
|
|
1322
|
-
self._skip_whitespace_and_comments()
|
|
1323
|
-
if not self.token.is_comma():
|
|
1324
|
-
break
|
|
1325
|
-
self.set_next_token()
|
|
1326
|
-
if not self.token.is_closing_brace():
|
|
1327
|
-
raise ValueError("Expected closing brace")
|
|
1328
|
-
self.set_next_token()
|
|
1329
|
-
return array
|
|
1330
|
-
|
|
1331
|
-
def _parse_json_array(self) -> JSONArray:
|
|
1332
|
-
if not self.token.is_opening_bracket():
|
|
1333
|
-
raise ValueError("Expected opening bracket")
|
|
1334
|
-
self.set_next_token()
|
|
1335
|
-
array = JSONArray()
|
|
1336
|
-
while True:
|
|
1337
|
-
self._skip_whitespace_and_comments()
|
|
1338
|
-
if self.token.is_closing_bracket():
|
|
1339
|
-
break
|
|
1340
|
-
value = self._parse_expression()
|
|
1341
|
-
if value is None:
|
|
1342
|
-
break
|
|
1343
|
-
array.add_value(value)
|
|
1344
|
-
self._skip_whitespace_and_comments()
|
|
1345
|
-
if not self.token.is_comma():
|
|
1346
|
-
break
|
|
1347
|
-
self.set_next_token()
|
|
1348
|
-
if not self.token.is_closing_bracket():
|
|
1349
|
-
raise ValueError("Expected closing bracket")
|
|
1350
|
-
self.set_next_token()
|
|
1351
|
-
return array
|
|
1352
|
-
|
|
1353
|
-
def _parse_f_string(self) -> Optional[FString]:
|
|
1354
|
-
if not self.token.is_f_string():
|
|
1355
|
-
return None
|
|
1356
|
-
f_string = FString()
|
|
1357
|
-
while self.token.is_f_string() or self.token.is_opening_brace():
|
|
1358
|
-
if self.token.is_f_string():
|
|
1359
|
-
f_string.add_child(String(self.token.value or ""))
|
|
1360
|
-
self.set_next_token()
|
|
1361
|
-
elif self.token.is_opening_brace():
|
|
1362
|
-
self.set_next_token()
|
|
1363
|
-
expr = self._parse_expression()
|
|
1364
|
-
if expr is not None:
|
|
1365
|
-
f_string.add_child(expr)
|
|
1366
|
-
if self.token.is_closing_brace():
|
|
1367
|
-
self.set_next_token()
|
|
1368
|
-
return f_string
|
|
1369
|
-
|
|
1370
|
-
def _skip_whitespace_and_comments(self) -> bool:
|
|
1371
|
-
skipped: bool = self.previous_token.is_whitespace_or_comment() if self.previous_token else False
|
|
1372
|
-
while self.token.is_whitespace_or_comment():
|
|
1373
|
-
self.set_next_token()
|
|
1374
|
-
skipped = True
|
|
1375
|
-
return skipped
|
|
1376
|
-
|
|
1377
|
-
def _expect_and_skip_whitespace_and_comments(self) -> None:
|
|
1378
|
-
skipped = self._skip_whitespace_and_comments()
|
|
1379
|
-
if not skipped:
|
|
1380
|
-
raise ValueError("Expected whitespace")
|
|
1381
|
-
|
|
1382
|
-
def _expect_previous_token_to_be_whitespace_or_comment(self) -> None:
|
|
1383
|
-
if not self.previous_token.is_whitespace_or_comment():
|
|
1384
|
-
raise ValueError("Expected previous token to be whitespace or comment")
|