flowquery 1.0.16 → 1.0.17
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/.github/workflows/python-publish.yml +97 -0
- package/dist/compute/runner.d.ts +3 -2
- package/dist/compute/runner.d.ts.map +1 -1
- package/dist/compute/runner.js +7 -7
- package/dist/compute/runner.js.map +1 -1
- package/dist/flowquery.min.js +1 -1
- package/dist/graph/data.d.ts +31 -0
- package/dist/graph/data.d.ts.map +1 -0
- package/dist/graph/data.js +110 -0
- package/dist/graph/data.js.map +1 -0
- package/dist/graph/database.d.ts +20 -0
- package/dist/graph/database.d.ts.map +1 -0
- package/dist/graph/database.js +77 -0
- package/dist/graph/database.js.map +1 -0
- package/dist/graph/hops.d.ts +11 -0
- package/dist/graph/hops.d.ts.map +1 -0
- package/dist/graph/hops.js +25 -0
- package/dist/graph/hops.js.map +1 -0
- package/dist/graph/node.d.ts +35 -0
- package/dist/graph/node.d.ts.map +1 -0
- package/dist/graph/node.js +113 -0
- package/dist/graph/node.js.map +1 -0
- package/dist/graph/node_data.d.ts +11 -0
- package/dist/graph/node_data.d.ts.map +1 -0
- package/dist/graph/node_data.js +20 -0
- package/dist/graph/node_data.js.map +1 -0
- package/dist/graph/node_reference.d.ts +10 -0
- package/dist/graph/node_reference.d.ts.map +1 -0
- package/dist/graph/node_reference.js +52 -0
- package/dist/graph/node_reference.js.map +1 -0
- package/dist/graph/pattern.d.ts +18 -0
- package/dist/graph/pattern.d.ts.map +1 -0
- package/dist/graph/pattern.js +114 -0
- package/dist/graph/pattern.js.map +1 -0
- package/dist/graph/pattern_expression.d.ts +14 -0
- package/dist/graph/pattern_expression.d.ts.map +1 -0
- package/dist/graph/pattern_expression.js +58 -0
- package/dist/graph/pattern_expression.js.map +1 -0
- package/dist/graph/patterns.d.ts +11 -0
- package/dist/graph/patterns.d.ts.map +1 -0
- package/dist/graph/patterns.js +49 -0
- package/dist/graph/patterns.js.map +1 -0
- package/dist/graph/physical_node.d.ts +10 -0
- package/dist/graph/physical_node.d.ts.map +1 -0
- package/dist/graph/physical_node.js +40 -0
- package/dist/graph/physical_node.js.map +1 -0
- package/dist/graph/physical_relationship.d.ts +10 -0
- package/dist/graph/physical_relationship.d.ts.map +1 -0
- package/dist/graph/physical_relationship.js +40 -0
- package/dist/graph/physical_relationship.js.map +1 -0
- package/dist/graph/relationship.d.ts +40 -0
- package/dist/graph/relationship.d.ts.map +1 -0
- package/dist/graph/relationship.js +124 -0
- package/dist/graph/relationship.js.map +1 -0
- package/dist/graph/relationship_data.d.ts +12 -0
- package/dist/graph/relationship_data.d.ts.map +1 -0
- package/dist/graph/relationship_data.js +40 -0
- package/dist/graph/relationship_data.js.map +1 -0
- package/dist/graph/relationship_match_collector.d.ts +19 -0
- package/dist/graph/relationship_match_collector.d.ts.map +1 -0
- package/dist/graph/relationship_match_collector.js +55 -0
- package/dist/graph/relationship_match_collector.js.map +1 -0
- package/dist/graph/relationship_reference.d.ts +8 -0
- package/dist/graph/relationship_reference.d.ts.map +1 -0
- package/dist/graph/relationship_reference.js +37 -0
- package/dist/graph/relationship_reference.js.map +1 -0
- package/dist/parsing/base_parser.d.ts +1 -0
- package/dist/parsing/base_parser.d.ts.map +1 -1
- package/dist/parsing/base_parser.js +4 -1
- package/dist/parsing/base_parser.js.map +1 -1
- package/dist/parsing/context.d.ts +2 -2
- package/dist/parsing/context.js +5 -5
- package/dist/parsing/expressions/boolean.d.ts +8 -0
- package/dist/parsing/expressions/boolean.d.ts.map +1 -0
- package/dist/parsing/expressions/boolean.js +26 -0
- package/dist/parsing/expressions/boolean.js.map +1 -0
- package/dist/parsing/expressions/expression.d.ts +4 -1
- package/dist/parsing/expressions/expression.d.ts.map +1 -1
- package/dist/parsing/expressions/expression.js +15 -8
- package/dist/parsing/expressions/expression.js.map +1 -1
- package/dist/parsing/expressions/operator.d.ts +1 -1
- package/dist/parsing/expressions/operator.d.ts.map +1 -1
- package/dist/parsing/expressions/operator.js.map +1 -1
- package/dist/parsing/functions/function_factory.d.ts +13 -13
- package/dist/parsing/functions/function_factory.d.ts.map +1 -1
- package/dist/parsing/functions/function_factory.js +20 -18
- package/dist/parsing/functions/function_factory.js.map +1 -1
- package/dist/parsing/operations/create_node.d.ts +14 -0
- package/dist/parsing/operations/create_node.d.ts.map +1 -0
- package/dist/parsing/operations/create_node.js +51 -0
- package/dist/parsing/operations/create_node.js.map +1 -0
- package/dist/parsing/operations/create_relationship.d.ts +14 -0
- package/dist/parsing/operations/create_relationship.d.ts.map +1 -0
- package/dist/parsing/operations/create_relationship.js +51 -0
- package/dist/parsing/operations/create_relationship.js.map +1 -0
- package/dist/parsing/operations/match.d.ts +15 -0
- package/dist/parsing/operations/match.d.ts.map +1 -0
- package/dist/parsing/operations/match.js +45 -0
- package/dist/parsing/operations/match.js.map +1 -0
- package/dist/parsing/operations/operation.d.ts +1 -0
- package/dist/parsing/operations/operation.d.ts.map +1 -1
- package/dist/parsing/operations/operation.js +6 -0
- package/dist/parsing/operations/operation.js.map +1 -1
- package/dist/parsing/operations/return.d.ts +1 -0
- package/dist/parsing/operations/return.d.ts.map +1 -1
- package/dist/parsing/operations/return.js +7 -1
- package/dist/parsing/operations/return.js.map +1 -1
- package/dist/parsing/operations/where.d.ts +1 -1
- package/dist/parsing/operations/where.d.ts.map +1 -1
- package/dist/parsing/operations/where.js +4 -0
- package/dist/parsing/operations/where.js.map +1 -1
- package/dist/parsing/parser.d.ts +10 -0
- package/dist/parsing/parser.d.ts.map +1 -1
- package/dist/parsing/parser.js +344 -5
- package/dist/parsing/parser.js.map +1 -1
- package/dist/parsing/token_to_node.d.ts.map +1 -1
- package/dist/parsing/token_to_node.js +7 -0
- package/dist/parsing/token_to_node.js.map +1 -1
- package/dist/tokenization/keyword.d.ts +1 -0
- package/dist/tokenization/keyword.d.ts.map +1 -1
- package/dist/tokenization/keyword.js +1 -0
- package/dist/tokenization/keyword.js.map +1 -1
- package/dist/tokenization/token.d.ts +4 -0
- package/dist/tokenization/token.d.ts.map +1 -1
- package/dist/tokenization/token.js +14 -1
- package/dist/tokenization/token.js.map +1 -1
- package/dist/tokenization/token_type.d.ts +1 -0
- package/dist/tokenization/token_type.d.ts.map +1 -1
- package/dist/tokenization/token_type.js +1 -0
- package/dist/tokenization/token_type.js.map +1 -1
- package/dist/tokenization/tokenizer.d.ts +2 -1
- package/dist/tokenization/tokenizer.d.ts.map +1 -1
- package/dist/tokenization/tokenizer.js +25 -12
- package/dist/tokenization/tokenizer.js.map +1 -1
- package/docs/flowquery.min.js +1 -1
- package/flowquery-py/README.md +166 -0
- package/flowquery-py/pyproject.toml +75 -0
- package/flowquery-py/setup_env.ps1 +92 -0
- package/flowquery-py/setup_env.sh +87 -0
- package/flowquery-py/src/__init__.py +34 -0
- package/flowquery-py/src/__main__.py +10 -0
- package/flowquery-py/src/compute/__init__.py +5 -0
- package/flowquery-py/src/compute/runner.py +60 -0
- package/flowquery-py/src/extensibility.py +52 -0
- package/flowquery-py/src/graph/__init__.py +31 -0
- package/flowquery-py/src/graph/data.py +118 -0
- package/flowquery-py/src/graph/database.py +82 -0
- package/flowquery-py/src/graph/hops.py +43 -0
- package/flowquery-py/src/graph/node.py +112 -0
- package/flowquery-py/src/graph/node_data.py +26 -0
- package/flowquery-py/src/graph/node_reference.py +49 -0
- package/flowquery-py/src/graph/pattern.py +125 -0
- package/flowquery-py/src/graph/pattern_expression.py +62 -0
- package/flowquery-py/src/graph/patterns.py +42 -0
- package/flowquery-py/src/graph/physical_node.py +40 -0
- package/flowquery-py/src/graph/physical_relationship.py +36 -0
- package/flowquery-py/src/graph/relationship.py +135 -0
- package/flowquery-py/src/graph/relationship_data.py +33 -0
- package/flowquery-py/src/graph/relationship_match_collector.py +77 -0
- package/flowquery-py/src/graph/relationship_reference.py +21 -0
- package/flowquery-py/src/io/__init__.py +5 -0
- package/flowquery-py/src/io/command_line.py +67 -0
- package/flowquery-py/src/parsing/__init__.py +17 -0
- package/flowquery-py/src/parsing/alias.py +20 -0
- package/flowquery-py/src/parsing/alias_option.py +11 -0
- package/flowquery-py/src/parsing/ast_node.py +146 -0
- package/flowquery-py/src/parsing/base_parser.py +84 -0
- package/flowquery-py/src/parsing/components/__init__.py +19 -0
- package/flowquery-py/src/parsing/components/csv.py +8 -0
- package/flowquery-py/src/parsing/components/from_.py +10 -0
- package/flowquery-py/src/parsing/components/headers.py +12 -0
- package/flowquery-py/src/parsing/components/json.py +8 -0
- package/flowquery-py/src/parsing/components/null.py +10 -0
- package/flowquery-py/src/parsing/components/post.py +8 -0
- package/flowquery-py/src/parsing/components/text.py +8 -0
- package/flowquery-py/src/parsing/context.py +50 -0
- package/flowquery-py/src/parsing/data_structures/__init__.py +15 -0
- package/flowquery-py/src/parsing/data_structures/associative_array.py +41 -0
- package/flowquery-py/src/parsing/data_structures/json_array.py +30 -0
- package/flowquery-py/src/parsing/data_structures/key_value_pair.py +38 -0
- package/flowquery-py/src/parsing/data_structures/lookup.py +49 -0
- package/flowquery-py/src/parsing/data_structures/range_lookup.py +42 -0
- package/flowquery-py/src/parsing/expressions/__init__.py +57 -0
- package/flowquery-py/src/parsing/expressions/boolean.py +20 -0
- package/flowquery-py/src/parsing/expressions/expression.py +138 -0
- package/flowquery-py/src/parsing/expressions/expression_map.py +26 -0
- package/flowquery-py/src/parsing/expressions/f_string.py +27 -0
- package/flowquery-py/src/parsing/expressions/identifier.py +20 -0
- package/flowquery-py/src/parsing/expressions/number.py +32 -0
- package/flowquery-py/src/parsing/expressions/operator.py +169 -0
- package/flowquery-py/src/parsing/expressions/reference.py +47 -0
- package/flowquery-py/src/parsing/expressions/string.py +27 -0
- package/flowquery-py/src/parsing/functions/__init__.py +75 -0
- package/flowquery-py/src/parsing/functions/aggregate_function.py +60 -0
- package/flowquery-py/src/parsing/functions/async_function.py +62 -0
- package/flowquery-py/src/parsing/functions/avg.py +55 -0
- package/flowquery-py/src/parsing/functions/collect.py +75 -0
- package/flowquery-py/src/parsing/functions/function.py +68 -0
- package/flowquery-py/src/parsing/functions/function_factory.py +173 -0
- package/flowquery-py/src/parsing/functions/function_metadata.py +149 -0
- package/flowquery-py/src/parsing/functions/functions.py +59 -0
- package/flowquery-py/src/parsing/functions/join.py +47 -0
- package/flowquery-py/src/parsing/functions/keys.py +34 -0
- package/flowquery-py/src/parsing/functions/predicate_function.py +46 -0
- package/flowquery-py/src/parsing/functions/predicate_sum.py +47 -0
- package/flowquery-py/src/parsing/functions/rand.py +28 -0
- package/flowquery-py/src/parsing/functions/range_.py +34 -0
- package/flowquery-py/src/parsing/functions/reducer_element.py +15 -0
- package/flowquery-py/src/parsing/functions/replace.py +37 -0
- package/flowquery-py/src/parsing/functions/round_.py +32 -0
- package/flowquery-py/src/parsing/functions/size.py +32 -0
- package/flowquery-py/src/parsing/functions/split.py +47 -0
- package/flowquery-py/src/parsing/functions/stringify.py +47 -0
- package/flowquery-py/src/parsing/functions/sum.py +51 -0
- package/flowquery-py/src/parsing/functions/to_json.py +33 -0
- package/flowquery-py/src/parsing/functions/type_.py +47 -0
- package/flowquery-py/src/parsing/functions/value_holder.py +24 -0
- package/flowquery-py/src/parsing/logic/__init__.py +15 -0
- package/flowquery-py/src/parsing/logic/case.py +29 -0
- package/flowquery-py/src/parsing/logic/else_.py +12 -0
- package/flowquery-py/src/parsing/logic/end.py +8 -0
- package/flowquery-py/src/parsing/logic/then.py +12 -0
- package/flowquery-py/src/parsing/logic/when.py +10 -0
- package/flowquery-py/src/parsing/operations/__init__.py +35 -0
- package/flowquery-py/src/parsing/operations/aggregated_return.py +24 -0
- package/flowquery-py/src/parsing/operations/aggregated_with.py +22 -0
- package/flowquery-py/src/parsing/operations/call.py +74 -0
- package/flowquery-py/src/parsing/operations/create_node.py +34 -0
- package/flowquery-py/src/parsing/operations/create_relationship.py +34 -0
- package/flowquery-py/src/parsing/operations/group_by.py +130 -0
- package/flowquery-py/src/parsing/operations/limit.py +22 -0
- package/flowquery-py/src/parsing/operations/load.py +140 -0
- package/flowquery-py/src/parsing/operations/match.py +29 -0
- package/flowquery-py/src/parsing/operations/operation.py +69 -0
- package/flowquery-py/src/parsing/operations/projection.py +21 -0
- package/flowquery-py/src/parsing/operations/return_op.py +50 -0
- package/flowquery-py/src/parsing/operations/unwind.py +37 -0
- package/flowquery-py/src/parsing/operations/where.py +41 -0
- package/flowquery-py/src/parsing/operations/with_op.py +18 -0
- package/flowquery-py/src/parsing/parser.py +1011 -0
- package/flowquery-py/src/parsing/token_to_node.py +109 -0
- package/flowquery-py/src/tokenization/__init__.py +23 -0
- package/flowquery-py/src/tokenization/keyword.py +48 -0
- package/flowquery-py/src/tokenization/operator.py +29 -0
- package/flowquery-py/src/tokenization/string_walker.py +158 -0
- package/flowquery-py/src/tokenization/symbol.py +19 -0
- package/flowquery-py/src/tokenization/token.py +659 -0
- package/flowquery-py/src/tokenization/token_mapper.py +52 -0
- package/flowquery-py/src/tokenization/token_type.py +21 -0
- package/flowquery-py/src/tokenization/tokenizer.py +214 -0
- package/flowquery-py/src/tokenization/trie.py +124 -0
- package/flowquery-py/src/utils/__init__.py +6 -0
- package/flowquery-py/src/utils/object_utils.py +20 -0
- package/flowquery-py/src/utils/string_utils.py +113 -0
- package/flowquery-py/tests/__init__.py +1 -0
- package/flowquery-py/tests/compute/__init__.py +1 -0
- package/flowquery-py/tests/compute/test_runner.py +1335 -0
- package/flowquery-py/tests/graph/__init__.py +1 -0
- package/flowquery-py/tests/graph/test_create.py +56 -0
- package/flowquery-py/tests/graph/test_data.py +73 -0
- package/flowquery-py/tests/graph/test_match.py +40 -0
- package/flowquery-py/tests/parsing/__init__.py +1 -0
- package/flowquery-py/tests/parsing/test_context.py +34 -0
- package/flowquery-py/tests/parsing/test_expression.py +49 -0
- package/flowquery-py/tests/parsing/test_parser.py +674 -0
- package/flowquery-py/tests/test_extensibility.py +611 -0
- package/flowquery-py/tests/tokenization/__init__.py +1 -0
- package/flowquery-py/tests/tokenization/test_token_mapper.py +60 -0
- package/flowquery-py/tests/tokenization/test_tokenizer.py +164 -0
- package/flowquery-py/tests/tokenization/test_trie.py +30 -0
- package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
- package/misc/apps/RAG/package.json +1 -1
- package/misc/apps/RAG/src/components/AdaptiveCardRenderer.tsx +76 -8
- package/misc/apps/RAG/src/components/index.ts +19 -10
- package/misc/apps/RAG/src/plugins/loaders/MockData.ts +70 -140
- package/misc/apps/RAG/src/prompts/FlowQuerySystemPrompt.ts +12 -0
- package/package.json +1 -1
- package/src/compute/runner.ts +24 -19
- package/src/graph/data.ts +112 -0
- package/src/graph/database.ts +63 -0
- package/src/graph/hops.ts +22 -0
- package/src/graph/node.ts +99 -0
- package/src/graph/node_data.ts +18 -0
- package/src/graph/node_reference.ts +33 -0
- package/src/graph/pattern.ts +101 -0
- package/src/graph/pattern_expression.ts +37 -0
- package/src/graph/patterns.ts +36 -0
- package/src/graph/physical_node.ts +23 -0
- package/src/graph/physical_relationship.ts +23 -0
- package/src/graph/relationship.ts +116 -0
- package/src/graph/relationship_data.ts +27 -0
- package/src/graph/relationship_match_collector.ts +58 -0
- package/src/graph/relationship_reference.ts +24 -0
- package/src/parsing/base_parser.ts +20 -14
- package/src/parsing/context.ts +14 -14
- package/src/parsing/expressions/boolean.ts +21 -0
- package/src/parsing/expressions/expression.ts +34 -26
- package/src/parsing/expressions/operator.ts +19 -1
- package/src/parsing/functions/function_factory.ts +45 -45
- package/src/parsing/operations/create_node.ts +39 -0
- package/src/parsing/operations/create_relationship.ts +38 -0
- package/src/parsing/operations/match.ts +31 -0
- package/src/parsing/operations/operation.ts +3 -0
- package/src/parsing/operations/return.ts +11 -7
- package/src/parsing/operations/where.ts +10 -6
- package/src/parsing/parser.ts +346 -8
- package/src/parsing/token_to_node.ts +6 -0
- package/src/tokenization/keyword.ts +41 -40
- package/src/tokenization/token.ts +21 -1
- package/src/tokenization/token_type.ts +2 -1
- package/src/tokenization/tokenizer.ts +52 -31
- package/tests/compute/runner.test.ts +654 -0
- package/tests/extensibility.test.ts +97 -93
- package/tests/graph/create.test.ts +36 -0
- package/tests/graph/data.test.ts +58 -0
- package/tests/graph/match.test.ts +29 -0
- package/tests/parsing/parser.test.ts +273 -2
- package/tests/tokenization/tokenizer.test.ts +90 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Token type enumeration for FlowQuery tokenization."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TokenType(Enum):
|
|
7
|
+
"""Enumeration of all token types in FlowQuery."""
|
|
8
|
+
|
|
9
|
+
KEYWORD = "KEYWORD"
|
|
10
|
+
BOOLEAN = "BOOLEAN"
|
|
11
|
+
OPERATOR = "OPERATOR"
|
|
12
|
+
UNARY_OPERATOR = "UNARY_OPERATOR"
|
|
13
|
+
IDENTIFIER = "IDENTIFIER"
|
|
14
|
+
STRING = "STRING"
|
|
15
|
+
F_STRING = "F-STRING"
|
|
16
|
+
BACKTICK_STRING = "BACKTICK_STRING"
|
|
17
|
+
NUMBER = "NUMBER"
|
|
18
|
+
SYMBOL = "SYMBOL"
|
|
19
|
+
WHITESPACE = "WHITESPACE"
|
|
20
|
+
COMMENT = "COMMENT"
|
|
21
|
+
EOF = "EOF"
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Tokenizes FlowQuery input strings into a sequence of tokens."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional, Iterator, Callable
|
|
4
|
+
|
|
5
|
+
from ..utils.string_utils import StringUtils
|
|
6
|
+
from .keyword import Keyword
|
|
7
|
+
from .operator import Operator
|
|
8
|
+
from .string_walker import StringWalker
|
|
9
|
+
from .symbol import Symbol
|
|
10
|
+
from .token import Token
|
|
11
|
+
from .token_mapper import TokenMapper
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Tokenizer:
|
|
15
|
+
"""Tokenizes FlowQuery input strings into a sequence of tokens.
|
|
16
|
+
|
|
17
|
+
The tokenizer performs lexical analysis, breaking down the input text into
|
|
18
|
+
meaningful tokens such as keywords, identifiers, operators, strings, numbers,
|
|
19
|
+
and symbols. It handles comments, whitespace, and f-strings.
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
tokenizer = Tokenizer("WITH x = 1 RETURN x")
|
|
23
|
+
tokens = tokenizer.tokenize()
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, input_: str):
|
|
27
|
+
"""Creates a new Tokenizer instance for the given input.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
input_: The FlowQuery input string to tokenize
|
|
31
|
+
"""
|
|
32
|
+
self._walker = StringWalker(input_)
|
|
33
|
+
self._keywords = TokenMapper(Keyword)
|
|
34
|
+
self._symbols = TokenMapper(Symbol)
|
|
35
|
+
self._operators = TokenMapper(Operator)
|
|
36
|
+
|
|
37
|
+
def tokenize(self) -> List[Token]:
|
|
38
|
+
"""Tokenizes the input string into an array of tokens.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
An array of Token objects representing the tokenized input
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
ValueError: If an unrecognized token is encountered
|
|
45
|
+
"""
|
|
46
|
+
tokens: List[Token] = []
|
|
47
|
+
last: Optional[Token] = None
|
|
48
|
+
|
|
49
|
+
while not self._walker.is_at_end:
|
|
50
|
+
tokens.extend(self._f_string())
|
|
51
|
+
last = self._get_last_non_whitespace_or_non_comment_token(tokens) or last
|
|
52
|
+
token = self._get_next_token(last)
|
|
53
|
+
if token is None:
|
|
54
|
+
raise ValueError(f"Unrecognized token at position {self._walker.position}")
|
|
55
|
+
token.position = self._walker.position
|
|
56
|
+
tokens.append(token)
|
|
57
|
+
|
|
58
|
+
return tokens
|
|
59
|
+
|
|
60
|
+
def _get_last_non_whitespace_or_non_comment_token(self, tokens: List[Token]) -> Optional[Token]:
|
|
61
|
+
if len(tokens) == 0:
|
|
62
|
+
return None
|
|
63
|
+
if not tokens[-1].is_whitespace_or_comment():
|
|
64
|
+
return tokens[-1]
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
def _get_next_token(self, last: Optional[Token] = None) -> Optional[Token]:
|
|
68
|
+
if self._walker.is_at_end:
|
|
69
|
+
return Token.EOF
|
|
70
|
+
return (
|
|
71
|
+
self._comment() or
|
|
72
|
+
self._whitespace() or
|
|
73
|
+
self._lookup(self._keywords) or
|
|
74
|
+
self._lookup(self._operators, last, self._skip_minus) or
|
|
75
|
+
self._boolean() or
|
|
76
|
+
self._identifier() or
|
|
77
|
+
self._string() or
|
|
78
|
+
self._number() or
|
|
79
|
+
self._lookup(self._symbols)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def _comment(self) -> Optional[Token]:
|
|
83
|
+
start_position = self._walker.position
|
|
84
|
+
if self._walker.check_for_single_comment() or self._walker.check_for_multi_line_comment():
|
|
85
|
+
uncommented = StringUtils.uncomment(self._walker.get_string(start_position))
|
|
86
|
+
return Token.COMMENT(uncommented)
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
def _boolean(self) -> Optional[Token]:
|
|
90
|
+
start_position = self._walker.position
|
|
91
|
+
if self._walker.check_for_string("TRUE"):
|
|
92
|
+
return Token.BOOLEAN(self._walker.get_string(start_position).upper())
|
|
93
|
+
if self._walker.check_for_string("FALSE"):
|
|
94
|
+
return Token.BOOLEAN(self._walker.get_string(start_position).upper())
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
def _identifier(self) -> Optional[Token]:
|
|
98
|
+
start_position = self._walker.position
|
|
99
|
+
if self._walker.check_for_under_score() or self._walker.check_for_letter():
|
|
100
|
+
while (not self._walker.is_at_end and
|
|
101
|
+
(self._walker.check_for_letter() or
|
|
102
|
+
self._walker.check_for_digit() or
|
|
103
|
+
self._walker.check_for_under_score())):
|
|
104
|
+
pass
|
|
105
|
+
return Token.IDENTIFIER(self._walker.get_string(start_position))
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
def _string(self) -> Optional[Token]:
|
|
109
|
+
start_position = self._walker.position
|
|
110
|
+
quote_char = self._walker.check_for_quote()
|
|
111
|
+
if quote_char is None:
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
while not self._walker.is_at_end:
|
|
115
|
+
if self._walker.escaped(quote_char):
|
|
116
|
+
self._walker.move_next()
|
|
117
|
+
self._walker.move_next()
|
|
118
|
+
continue
|
|
119
|
+
if self._walker.check_for_string(quote_char):
|
|
120
|
+
value = self._walker.get_string(start_position)
|
|
121
|
+
if quote_char == Symbol.BACKTICK.value:
|
|
122
|
+
return Token.BACKTICK_STRING(value, quote_char)
|
|
123
|
+
return Token.STRING(value, quote_char)
|
|
124
|
+
self._walker.move_next()
|
|
125
|
+
|
|
126
|
+
raise ValueError(f"Unterminated string at position {start_position}")
|
|
127
|
+
|
|
128
|
+
def _f_string(self) -> Iterator[Token]:
|
|
129
|
+
if not self._walker.check_for_f_string_start():
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
self._walker.move_next() # skip the f
|
|
133
|
+
position = self._walker.position
|
|
134
|
+
quote_char = self._walker.check_for_quote()
|
|
135
|
+
if quote_char is None:
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
while not self._walker.is_at_end:
|
|
139
|
+
if self._walker.escaped(quote_char) or self._walker.escaped_brace():
|
|
140
|
+
self._walker.move_next()
|
|
141
|
+
self._walker.move_next()
|
|
142
|
+
continue
|
|
143
|
+
|
|
144
|
+
if self._walker.opening_brace():
|
|
145
|
+
yield Token.F_STRING(self._walker.get_string(position), quote_char)
|
|
146
|
+
position = self._walker.position
|
|
147
|
+
yield Token.OPENING_BRACE
|
|
148
|
+
self._walker.move_next() # skip the opening brace
|
|
149
|
+
position = self._walker.position
|
|
150
|
+
|
|
151
|
+
while not self._walker.is_at_end and not self._walker.closing_brace():
|
|
152
|
+
token = self._get_next_token()
|
|
153
|
+
if token is not None:
|
|
154
|
+
yield token
|
|
155
|
+
else:
|
|
156
|
+
break
|
|
157
|
+
if self._walker.closing_brace():
|
|
158
|
+
yield Token.CLOSING_BRACE
|
|
159
|
+
self._walker.move_next() # skip the closing brace
|
|
160
|
+
position = self._walker.position
|
|
161
|
+
break
|
|
162
|
+
|
|
163
|
+
if self._walker.check_for_string(quote_char):
|
|
164
|
+
yield Token.F_STRING(self._walker.get_string(position), quote_char)
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
self._walker.move_next()
|
|
168
|
+
|
|
169
|
+
def _whitespace(self) -> Optional[Token]:
|
|
170
|
+
found_whitespace = False
|
|
171
|
+
while not self._walker.is_at_end and self._walker.check_for_whitespace():
|
|
172
|
+
self._walker.move_next()
|
|
173
|
+
found_whitespace = True
|
|
174
|
+
return Token.WHITESPACE if found_whitespace else None
|
|
175
|
+
|
|
176
|
+
def _number(self) -> Optional[Token]:
|
|
177
|
+
start_position = self._walker.position
|
|
178
|
+
if self._walker.check_for_string("-") or self._walker.check_for_digit():
|
|
179
|
+
while not self._walker.is_at_end and self._walker.check_for_digit():
|
|
180
|
+
pass
|
|
181
|
+
if self._walker.check_for_string(Symbol.DOT.value):
|
|
182
|
+
decimal_digits = 0
|
|
183
|
+
while not self._walker.is_at_end and self._walker.check_for_digit():
|
|
184
|
+
decimal_digits += 1
|
|
185
|
+
if decimal_digits == 0:
|
|
186
|
+
self._walker.move_previous()
|
|
187
|
+
number_str = self._walker.get_string(start_position)
|
|
188
|
+
return Token.NUMBER(number_str)
|
|
189
|
+
return None
|
|
190
|
+
|
|
191
|
+
def _lookup(
|
|
192
|
+
self,
|
|
193
|
+
mapper: TokenMapper,
|
|
194
|
+
last: Optional[Token] = None,
|
|
195
|
+
skip: Optional[Callable[[Optional[Token], Token], bool]] = None
|
|
196
|
+
) -> Optional[Token]:
|
|
197
|
+
token = mapper.map(self._walker.get_remaining_string())
|
|
198
|
+
if token is not None and token.value is not None:
|
|
199
|
+
if token.can_be_identifier and self._walker.word_continuation(token.value):
|
|
200
|
+
return None
|
|
201
|
+
if skip and last and skip(last, token):
|
|
202
|
+
return None
|
|
203
|
+
self._walker.move_by(len(token.value))
|
|
204
|
+
if mapper.last_found is not None:
|
|
205
|
+
token.case_sensitive_value = mapper.last_found
|
|
206
|
+
return token
|
|
207
|
+
return None
|
|
208
|
+
|
|
209
|
+
def _skip_minus(self, last: Optional[Token], current: Token) -> bool:
|
|
210
|
+
if last is None:
|
|
211
|
+
return False
|
|
212
|
+
if (last.is_keyword() or last.is_comma() or last.is_colon()) and current.is_negation():
|
|
213
|
+
return True
|
|
214
|
+
return False
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Trie (prefix tree) data structure for efficient keyword and operator lookup."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from typing import TYPE_CHECKING, Optional
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from .token import Token
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TrieNode:
|
|
11
|
+
"""Represents a node in a Trie data structure.
|
|
12
|
+
|
|
13
|
+
Each node can have children nodes (one per character) and may contain a token
|
|
14
|
+
if the path to this node represents a complete word.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self._children: dict[str, TrieNode] = {}
|
|
19
|
+
self._token: Optional[Token] = None
|
|
20
|
+
|
|
21
|
+
def map(self, char: str) -> TrieNode:
|
|
22
|
+
if char not in self._children:
|
|
23
|
+
self._children[char] = TrieNode()
|
|
24
|
+
return self._children[char]
|
|
25
|
+
|
|
26
|
+
def retrieve(self, char: str) -> Optional[TrieNode]:
|
|
27
|
+
return self._children.get(char)
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def token(self) -> Optional[Token]:
|
|
31
|
+
return self._token
|
|
32
|
+
|
|
33
|
+
@token.setter
|
|
34
|
+
def token(self, token: Token) -> None:
|
|
35
|
+
self._token = token
|
|
36
|
+
|
|
37
|
+
def is_end_of_word(self) -> bool:
|
|
38
|
+
return self._token is not None
|
|
39
|
+
|
|
40
|
+
def no_children(self) -> bool:
|
|
41
|
+
return len(self._children) == 0
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Trie:
|
|
45
|
+
"""Trie (prefix tree) data structure for efficient keyword and operator lookup.
|
|
46
|
+
|
|
47
|
+
Used during tokenization to quickly match input strings against known keywords
|
|
48
|
+
and operators. Supports case-insensitive matching and tracks the longest match found.
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
trie = Trie()
|
|
52
|
+
trie.insert(Token.WITH)
|
|
53
|
+
found = trie.find("WITH")
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(self):
|
|
57
|
+
self._root = TrieNode()
|
|
58
|
+
self._max_length = 0
|
|
59
|
+
self._last_found: Optional[str] = None
|
|
60
|
+
|
|
61
|
+
def insert(self, token: Token) -> None:
|
|
62
|
+
"""Inserts a token into the trie.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
token: The token to insert
|
|
66
|
+
|
|
67
|
+
Raises:
|
|
68
|
+
ValueError: If the token value is None or empty
|
|
69
|
+
"""
|
|
70
|
+
if token.value is None or len(token.value) == 0:
|
|
71
|
+
raise ValueError("Token value cannot be null or empty")
|
|
72
|
+
|
|
73
|
+
current_node = self._root
|
|
74
|
+
for char in token.value:
|
|
75
|
+
current_node = current_node.map(char.lower())
|
|
76
|
+
|
|
77
|
+
if len(token.value) > self._max_length:
|
|
78
|
+
self._max_length = len(token.value)
|
|
79
|
+
|
|
80
|
+
current_node.token = token
|
|
81
|
+
|
|
82
|
+
def find(self, value: str) -> Optional[Token]:
|
|
83
|
+
"""Finds a token by searching for the longest matching prefix in the trie.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
value: The string value to search for
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
The token if found, None otherwise
|
|
90
|
+
"""
|
|
91
|
+
if len(value) == 0:
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
index = 0
|
|
95
|
+
current: Optional[TrieNode] = None
|
|
96
|
+
found: Optional[Token] = None
|
|
97
|
+
self._last_found = None
|
|
98
|
+
|
|
99
|
+
while True:
|
|
100
|
+
next_node = (current or self._root).retrieve(value[index].lower())
|
|
101
|
+
if next_node is None:
|
|
102
|
+
break
|
|
103
|
+
current = next_node
|
|
104
|
+
if current.is_end_of_word():
|
|
105
|
+
found = current.token
|
|
106
|
+
self._last_found = value[:index + 1]
|
|
107
|
+
index += 1
|
|
108
|
+
if index >= len(value) or index > self._max_length:
|
|
109
|
+
break
|
|
110
|
+
|
|
111
|
+
if current is not None and current.is_end_of_word():
|
|
112
|
+
found = current.token
|
|
113
|
+
self._last_found = value[:index]
|
|
114
|
+
|
|
115
|
+
return found
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def last_found(self) -> Optional[str]:
|
|
119
|
+
"""Gets the last matched string from the most recent find operation.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
The last found string, or None if no match was found
|
|
123
|
+
"""
|
|
124
|
+
return self._last_found
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Utility class for object-related operations."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, List, Type
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ObjectUtils:
|
|
7
|
+
"""Utility class for object-related operations."""
|
|
8
|
+
|
|
9
|
+
@staticmethod
|
|
10
|
+
def is_instance_of_any(obj: Any, classes: List[Type]) -> bool:
|
|
11
|
+
"""Checks if an object is an instance of any of the provided classes.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
obj: The object to check
|
|
15
|
+
classes: Array of class constructors to test against
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
True if the object is an instance of any class, False otherwise
|
|
19
|
+
"""
|
|
20
|
+
return any(isinstance(obj, cls) for cls in classes)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Utility class for string manipulation and validation."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class StringUtils:
|
|
5
|
+
"""Utility class for string manipulation and validation.
|
|
6
|
+
|
|
7
|
+
Provides methods for handling quoted strings, comments, escape sequences,
|
|
8
|
+
and identifier validation.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
quotes = ['"', "'", '`']
|
|
12
|
+
letters = 'abcdefghijklmnopqrstuvwxyz'
|
|
13
|
+
digits = '0123456789'
|
|
14
|
+
whitespace = ' \t\n\r'
|
|
15
|
+
word_valid_chars = letters + letters.upper() + digits + '_'
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def unquote(s: str) -> str:
|
|
19
|
+
"""Removes surrounding quotes from a string.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
s: The string to unquote
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
The unquoted string
|
|
26
|
+
"""
|
|
27
|
+
if len(s) == 0:
|
|
28
|
+
return s
|
|
29
|
+
if len(s) == 1 and s in StringUtils.quotes:
|
|
30
|
+
return ''
|
|
31
|
+
first = s[0]
|
|
32
|
+
last = s[-1]
|
|
33
|
+
if first in StringUtils.quotes and first == last:
|
|
34
|
+
return s[1:-1]
|
|
35
|
+
if last in StringUtils.quotes and first != last:
|
|
36
|
+
return s[:-1]
|
|
37
|
+
if first in StringUtils.quotes and first != last:
|
|
38
|
+
return s[1:]
|
|
39
|
+
return s
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def uncomment(s: str) -> str:
|
|
43
|
+
"""Removes comment markers from a string.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
s: The comment string
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
The string without comment markers
|
|
50
|
+
"""
|
|
51
|
+
if len(s) < 2:
|
|
52
|
+
return s
|
|
53
|
+
if s[0] == '/' and s[1] == '/':
|
|
54
|
+
return s[2:]
|
|
55
|
+
if s[0] == '/' and s[1] == '*' and s[-2] == '*' and s[-1] == '/':
|
|
56
|
+
return s[2:-2]
|
|
57
|
+
return s
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def remove_escaped_quotes(s: str, quote_char: str) -> str:
|
|
61
|
+
"""Removes escape sequences before quotes in a string.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
s: The string to process
|
|
65
|
+
quote_char: The quote character that was escaped
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
The string with escape sequences removed
|
|
69
|
+
"""
|
|
70
|
+
unescaped = ''
|
|
71
|
+
i = 0
|
|
72
|
+
while i < len(s):
|
|
73
|
+
if i < len(s) - 1 and s[i] == '\\' and s[i + 1] == quote_char:
|
|
74
|
+
i += 1
|
|
75
|
+
unescaped += s[i]
|
|
76
|
+
i += 1
|
|
77
|
+
return unescaped
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def remove_escaped_braces(s: str) -> str:
|
|
81
|
+
"""Removes escaped braces ({{ and }}) from f-strings.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
s: The string to process
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
The string with escaped braces resolved
|
|
88
|
+
"""
|
|
89
|
+
unescaped = ''
|
|
90
|
+
i = 0
|
|
91
|
+
while i < len(s):
|
|
92
|
+
if i < len(s) - 1 and ((s[i] == '{' and s[i + 1] == '{') or (s[i] == '}' and s[i + 1] == '}')):
|
|
93
|
+
i += 1
|
|
94
|
+
unescaped += s[i]
|
|
95
|
+
i += 1
|
|
96
|
+
return unescaped
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def can_be_identifier(s: str) -> bool:
|
|
100
|
+
"""Checks if a string is a valid identifier.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
s: The string to validate
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
True if the string can be used as an identifier, false otherwise
|
|
107
|
+
"""
|
|
108
|
+
lower = s.lower()
|
|
109
|
+
if len(lower) == 0:
|
|
110
|
+
return False
|
|
111
|
+
if lower[0] not in StringUtils.letters and lower[0] != '_':
|
|
112
|
+
return False
|
|
113
|
+
return all(char in StringUtils.word_valid_chars for char in lower)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Tests package for FlowQuery."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Compute tests package."""
|