lexcql-parser 1.3.0__tar.gz → 1.3.2__tar.gz
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.
- {lexcql_parser-1.3.0 → lexcql_parser-1.3.2}/PKG-INFO +3 -3
- {lexcql_parser-1.3.0 → lexcql_parser-1.3.2}/README.md +2 -2
- {lexcql_parser-1.3.0 → lexcql_parser-1.3.2}/pyproject.toml +1 -1
- {lexcql_parser-1.3.0 → lexcql_parser-1.3.2}/src/lexcql/__init__.py +3 -3
- {lexcql_parser-1.3.0 → lexcql_parser-1.3.2}/src/lexcql/parser.py +37 -30
- {lexcql_parser-1.3.0 → lexcql_parser-1.3.2}/src/lexcql/validation.py +12 -7
- {lexcql_parser-1.3.0 → lexcql_parser-1.3.2}/src/lexcql/LexLexer.g4 +0 -0
- {lexcql_parser-1.3.0 → lexcql_parser-1.3.2}/src/lexcql/LexLexer.interp +0 -0
- {lexcql_parser-1.3.0 → lexcql_parser-1.3.2}/src/lexcql/LexLexer.py +0 -0
- {lexcql_parser-1.3.0 → lexcql_parser-1.3.2}/src/lexcql/LexLexer.tokens +0 -0
- {lexcql_parser-1.3.0 → lexcql_parser-1.3.2}/src/lexcql/LexParser.g4 +0 -0
- {lexcql_parser-1.3.0 → lexcql_parser-1.3.2}/src/lexcql/LexParser.interp +0 -0
- {lexcql_parser-1.3.0 → lexcql_parser-1.3.2}/src/lexcql/LexParser.py +0 -0
- {lexcql_parser-1.3.0 → lexcql_parser-1.3.2}/src/lexcql/LexParser.tokens +0 -0
- {lexcql_parser-1.3.0 → lexcql_parser-1.3.2}/src/lexcql/LexParserListener.py +0 -0
- {lexcql_parser-1.3.0 → lexcql_parser-1.3.2}/src/lexcql/LexParserVisitor.py +0 -0
- {lexcql_parser-1.3.0 → lexcql_parser-1.3.2}/src/lexcql/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lexcql-parser
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.2
|
|
4
4
|
Summary: LexCQL Query Grammar and Parser
|
|
5
5
|
Keywords: LexCQL,FCS,CQL,Query Parser
|
|
6
6
|
Author: Erik Körner
|
|
@@ -107,7 +107,7 @@ Parsed queries can also be checked against their specification conformance.
|
|
|
107
107
|
|
|
108
108
|
```python
|
|
109
109
|
from lexcql import QueryParser
|
|
110
|
-
from lexcql.validation import LexCQLValidatorV0_3
|
|
110
|
+
from lexcql.validation import LexCQLValidatorV0_3, SpecificationValidationError
|
|
111
111
|
|
|
112
112
|
parser = QueryParser(enableSourceLocations=True)
|
|
113
113
|
|
|
@@ -172,9 +172,9 @@ Run style checks:
|
|
|
172
172
|
# setup environment
|
|
173
173
|
uv sync --extra style
|
|
174
174
|
|
|
175
|
+
uv run isort --check --diff .
|
|
175
176
|
uv run black --check .
|
|
176
177
|
uv run flake8 . --show-source --statistics
|
|
177
|
-
uv run isort --check --diff .
|
|
178
178
|
```
|
|
179
179
|
|
|
180
180
|
Run tests:
|
|
@@ -67,7 +67,7 @@ Parsed queries can also be checked against their specification conformance.
|
|
|
67
67
|
|
|
68
68
|
```python
|
|
69
69
|
from lexcql import QueryParser
|
|
70
|
-
from lexcql.validation import LexCQLValidatorV0_3
|
|
70
|
+
from lexcql.validation import LexCQLValidatorV0_3, SpecificationValidationError
|
|
71
71
|
|
|
72
72
|
parser = QueryParser(enableSourceLocations=True)
|
|
73
73
|
|
|
@@ -132,9 +132,9 @@ Run style checks:
|
|
|
132
132
|
# setup environment
|
|
133
133
|
uv sync --extra style
|
|
134
134
|
|
|
135
|
+
uv run isort --check --diff .
|
|
135
136
|
uv run black --check .
|
|
136
137
|
uv run flake8 . --show-source --statistics
|
|
137
|
-
uv run isort --check --diff .
|
|
138
138
|
```
|
|
139
139
|
|
|
140
140
|
Run tests:
|
|
@@ -56,7 +56,7 @@ def antlr_parse(input: str) -> LexParser.QueryContext:
|
|
|
56
56
|
# ---------------------------------------------------------------------------
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
def parse(input: str, enableSourceLocations: bool = True) -> QueryNode:
|
|
59
|
+
def parse(input: str, *, enableSourceLocations: bool = True) -> QueryNode:
|
|
60
60
|
"""Simple wrapper to generate a `QueryParser` and to parse some
|
|
61
61
|
input string into a `QueryNode`.
|
|
62
62
|
|
|
@@ -75,7 +75,7 @@ def parse(input: str, enableSourceLocations: bool = True) -> QueryNode:
|
|
|
75
75
|
|
|
76
76
|
|
|
77
77
|
def can_parse(input: str):
|
|
78
|
-
"""Simple wrapper to check if the input string can be
|
|
78
|
+
"""Simple wrapper to check if the input string can be successfully parsed.
|
|
79
79
|
|
|
80
80
|
Args:
|
|
81
81
|
input: raw input query string
|
|
@@ -166,7 +166,7 @@ def validate(
|
|
|
166
166
|
return False
|
|
167
167
|
|
|
168
168
|
errors = []
|
|
169
|
-
if not str(ex)
|
|
169
|
+
if not str(ex).startswith("unable to parse query: "):
|
|
170
170
|
errors.append(ErrorDetail(str(ex)))
|
|
171
171
|
errors.extend(parser.errors)
|
|
172
172
|
return errors
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from abc import ABCMeta
|
|
3
|
-
from abc import abstractmethod
|
|
4
3
|
from collections import deque
|
|
5
4
|
from dataclasses import dataclass
|
|
6
5
|
from enum import Enum
|
|
@@ -107,7 +106,7 @@ class QueryVisitor(Generic[_R], metaclass=ABCMeta):
|
|
|
107
106
|
# ----------------------------------------------------
|
|
108
107
|
# same as antlr4.tree.Tree.ParseTreeVisitor
|
|
109
108
|
|
|
110
|
-
def visitChildren(self, node: "QueryNode") -> _R:
|
|
109
|
+
def visitChildren(self, node: "QueryNode") -> Optional[_R]:
|
|
111
110
|
result = self.defaultResult()
|
|
112
111
|
for i in range(node.child_count):
|
|
113
112
|
if not self.shouldVisitNextChild(node, result):
|
|
@@ -121,12 +120,12 @@ class QueryVisitor(Generic[_R], metaclass=ABCMeta):
|
|
|
121
120
|
return result
|
|
122
121
|
|
|
123
122
|
def defaultResult(self) -> Optional[_R]:
|
|
124
|
-
return None
|
|
123
|
+
return None # type: ignore
|
|
125
124
|
|
|
126
|
-
def aggregateResult(self, aggregate: _R, nextResult: _R) -> _R:
|
|
125
|
+
def aggregateResult(self, aggregate: Optional[_R], nextResult: Optional[_R]) -> Optional[_R]:
|
|
127
126
|
return nextResult
|
|
128
127
|
|
|
129
|
-
def shouldVisitNextChild(self, node: "QueryNode", currentResult: _R) -> bool:
|
|
128
|
+
def shouldVisitNextChild(self, node: "QueryNode", currentResult: Optional[_R]) -> bool:
|
|
130
129
|
return True
|
|
131
130
|
|
|
132
131
|
|
|
@@ -236,7 +235,7 @@ class SourceLocation:
|
|
|
236
235
|
return f"{self.start}:{self.stop}"
|
|
237
236
|
|
|
238
237
|
|
|
239
|
-
class QueryNode(metaclass=ABCMeta):
|
|
238
|
+
class QueryNode(Generic[_R], metaclass=ABCMeta):
|
|
240
239
|
"""Base class for LexCQL expression tree nodes."""
|
|
241
240
|
|
|
242
241
|
def __init__(
|
|
@@ -362,9 +361,8 @@ class QueryNode(metaclass=ABCMeta):
|
|
|
362
361
|
strrepr += f"@{self.location.start}:{self.location.stop}"
|
|
363
362
|
return strrepr
|
|
364
363
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
pass
|
|
364
|
+
def accept(self, visitor: QueryVisitor) -> _R:
|
|
365
|
+
return visitor.visit(self)
|
|
368
366
|
|
|
369
367
|
|
|
370
368
|
# ---------------------------------------------------------------------------
|
|
@@ -402,9 +400,6 @@ class Modifier(QueryNode):
|
|
|
402
400
|
parts.append(f"@{self.location.start}:{self.location.stop}")
|
|
403
401
|
return "".join(parts)
|
|
404
402
|
|
|
405
|
-
def accept(self, visitor: QueryVisitor) -> None:
|
|
406
|
-
visitor.visit(self)
|
|
407
|
-
|
|
408
403
|
|
|
409
404
|
class Relation(QueryNode):
|
|
410
405
|
"""A LexCQL expression tree relation node."""
|
|
@@ -445,9 +440,6 @@ class Relation(QueryNode):
|
|
|
445
440
|
parts.append(f"@{self.location.start}:{self.location.stop}")
|
|
446
441
|
return "".join(parts)
|
|
447
442
|
|
|
448
|
-
def accept(self, visitor: QueryVisitor) -> None:
|
|
449
|
-
visitor.visit(self)
|
|
450
|
-
|
|
451
443
|
|
|
452
444
|
class SearchClause(QueryNode):
|
|
453
445
|
"""A LexCQL expression tree search_clause node."""
|
|
@@ -500,9 +492,6 @@ class SearchClause(QueryNode):
|
|
|
500
492
|
parts.append(f"@{self.location.start}:{self.location.stop}")
|
|
501
493
|
return "".join(parts)
|
|
502
494
|
|
|
503
|
-
def accept(self, visitor: QueryVisitor) -> None:
|
|
504
|
-
visitor.visit(self)
|
|
505
|
-
|
|
506
495
|
|
|
507
496
|
class SearchClauseGroup(QueryNode):
|
|
508
497
|
"""A LexCQL expression tree search_clausse_group node."""
|
|
@@ -581,9 +570,6 @@ class SearchClauseGroup(QueryNode):
|
|
|
581
570
|
parts.append(f"@{self.location.start}:{self.location.stop}")
|
|
582
571
|
return "".join(parts)
|
|
583
572
|
|
|
584
|
-
def accept(self, visitor: QueryVisitor) -> None:
|
|
585
|
-
visitor.visit(self)
|
|
586
|
-
|
|
587
573
|
|
|
588
574
|
class Subquery(QueryNode):
|
|
589
575
|
"""A LexCQL expression tree search_clausse_group node."""
|
|
@@ -633,9 +619,6 @@ class Subquery(QueryNode):
|
|
|
633
619
|
parts.append(f"@{self.location.start}:{self.location.stop}")
|
|
634
620
|
return "".join(parts)
|
|
635
621
|
|
|
636
|
-
def accept(self, visitor: QueryVisitor) -> None:
|
|
637
|
-
visitor.visit(self)
|
|
638
|
-
|
|
639
622
|
|
|
640
623
|
# ---------------------------------------------------------------------------
|
|
641
624
|
|
|
@@ -681,10 +664,12 @@ class ErrorListener(antlr4.error.ErrorListener.ErrorListener):
|
|
|
681
664
|
LOGGER.debug("query: %s", self.query)
|
|
682
665
|
LOGGER.debug(" %s^- %s", " " * pos, msg)
|
|
683
666
|
|
|
684
|
-
if isinstance(
|
|
685
|
-
|
|
686
|
-
LOGGER.debug(
|
|
687
|
-
|
|
667
|
+
if isinstance(offendingSymbol, Token):
|
|
668
|
+
lit_name, sym_name = self._get_token_name(recognizer, offendingSymbol)
|
|
669
|
+
LOGGER.debug(
|
|
670
|
+
f"symbol: literal={lit_name!r} symbol={sym_name!r} @token-idx={offendingSymbol.tokenIndex}"
|
|
671
|
+
)
|
|
672
|
+
LOGGER.debug(f"{recognizer=}")
|
|
688
673
|
|
|
689
674
|
if pos is None:
|
|
690
675
|
pos = column
|
|
@@ -698,6 +683,27 @@ class ErrorListener(antlr4.error.ErrorListener.ErrorListener):
|
|
|
698
683
|
)
|
|
699
684
|
)
|
|
700
685
|
|
|
686
|
+
@staticmethod
|
|
687
|
+
def _get_token_name(recognizer: Recognizer, symbol: Optional[Token]):
|
|
688
|
+
if not symbol:
|
|
689
|
+
return (None, None)
|
|
690
|
+
if not isinstance(recognizer, (LexLexer, LexParser)):
|
|
691
|
+
return (None, None)
|
|
692
|
+
if not hasattr(recognizer, "literalNames"):
|
|
693
|
+
return (None, None)
|
|
694
|
+
if not hasattr(recognizer, "symbolicNames"):
|
|
695
|
+
return (None, None)
|
|
696
|
+
|
|
697
|
+
type = symbol.type
|
|
698
|
+
lit_name = sym_name = None
|
|
699
|
+
|
|
700
|
+
if type < len(recognizer.literalNames):
|
|
701
|
+
lit_name = recognizer.literalNames[type]
|
|
702
|
+
if type < len(recognizer.symbolicNames):
|
|
703
|
+
sym_name = recognizer.symbolicNames[type]
|
|
704
|
+
|
|
705
|
+
return (lit_name, sym_name)
|
|
706
|
+
|
|
701
707
|
def has_errors(self) -> bool:
|
|
702
708
|
return bool(self.errors)
|
|
703
709
|
|
|
@@ -1038,7 +1044,7 @@ class ExpressionTreeBuilder(LexParserVisitor):
|
|
|
1038
1044
|
class QueryParser:
|
|
1039
1045
|
"""A LexCQL query parser that produces LexCQL expression trees."""
|
|
1040
1046
|
|
|
1041
|
-
def __init__(self, enableSourceLocations: bool = False):
|
|
1047
|
+
def __init__(self, *, enableSourceLocations: bool = False):
|
|
1042
1048
|
"""[Constructor]
|
|
1043
1049
|
|
|
1044
1050
|
Args:
|
|
@@ -1093,7 +1099,8 @@ class QueryParser:
|
|
|
1093
1099
|
for msg in error_listener.errors:
|
|
1094
1100
|
LOGGER.debug("ERROR: %s", msg)
|
|
1095
1101
|
|
|
1096
|
-
|
|
1102
|
+
first_message = error_listener.errors[0].message if error_listener.errors else "?"
|
|
1103
|
+
raise QueryParserException(f"unable to parse query: {first_message}")
|
|
1097
1104
|
except ExpressionTreeBuilderException as ex:
|
|
1098
1105
|
raise QueryParserException(str(ex)) from ex
|
|
1099
1106
|
except QueryParserException:
|
|
@@ -97,7 +97,7 @@ class Validator(QueryVisitorAdapter[_R], metaclass=ABCMeta):
|
|
|
97
97
|
|
|
98
98
|
Returns:
|
|
99
99
|
bool: ``True`` if query in valid, ``False`` if any error was recorded
|
|
100
|
-
in the ``.errors`` attribute
|
|
100
|
+
in the ``.errors`` attribute (or in ``.warnings`` if ``.warnings_as_errors``)
|
|
101
101
|
"""
|
|
102
102
|
# allows to override the query string here
|
|
103
103
|
if query is not None:
|
|
@@ -105,10 +105,15 @@ class Validator(QueryVisitorAdapter[_R], metaclass=ABCMeta):
|
|
|
105
105
|
|
|
106
106
|
# reset list of errors
|
|
107
107
|
self.errors = []
|
|
108
|
+
self.warnings = []
|
|
108
109
|
|
|
109
110
|
self.visit(node)
|
|
110
111
|
|
|
111
|
-
|
|
112
|
+
num_issues = len(self.errors)
|
|
113
|
+
if self.warnings_as_errors:
|
|
114
|
+
num_issues += len(self.warnings)
|
|
115
|
+
|
|
116
|
+
return num_issues == 0
|
|
112
117
|
|
|
113
118
|
def is_valid(self, node: QueryNode, *, query: Optional[str] = None) -> bool:
|
|
114
119
|
"""Convenience method that simply calls ``.validate()`` and returns
|
|
@@ -205,31 +210,31 @@ class Validator(QueryVisitorAdapter[_R], metaclass=ABCMeta):
|
|
|
205
210
|
return self.stack[-1]
|
|
206
211
|
return None
|
|
207
212
|
|
|
208
|
-
def visit_Subquery(self, node: Subquery) -> _R:
|
|
213
|
+
def visit_Subquery(self, node: Subquery) -> Optional[_R]:
|
|
209
214
|
self.stack.append(node)
|
|
210
215
|
result = super().visit_Subquery(node)
|
|
211
216
|
self.stack.pop()
|
|
212
217
|
return result
|
|
213
218
|
|
|
214
|
-
def visit_SearchClauseGroup(self, node: SearchClauseGroup) -> _R:
|
|
219
|
+
def visit_SearchClauseGroup(self, node: SearchClauseGroup) -> Optional[_R]:
|
|
215
220
|
self.stack.append(node)
|
|
216
221
|
result = super().visit_SearchClauseGroup(node)
|
|
217
222
|
self.stack.pop()
|
|
218
223
|
return result
|
|
219
224
|
|
|
220
|
-
def visit_SearchClause(self, node: SearchClause) -> _R:
|
|
225
|
+
def visit_SearchClause(self, node: SearchClause) -> Optional[_R]:
|
|
221
226
|
self.stack.append(node)
|
|
222
227
|
result = super().visit_SearchClause(node)
|
|
223
228
|
self.stack.pop()
|
|
224
229
|
return result
|
|
225
230
|
|
|
226
|
-
def visit_Relation(self, node: Relation) -> _R:
|
|
231
|
+
def visit_Relation(self, node: Relation) -> Optional[_R]:
|
|
227
232
|
self.stack.append(node)
|
|
228
233
|
result = super().visit_Relation(node)
|
|
229
234
|
self.stack.pop()
|
|
230
235
|
return result
|
|
231
236
|
|
|
232
|
-
def visit_Modifier(self, node: Modifier) -> _R:
|
|
237
|
+
def visit_Modifier(self, node: Modifier) -> Optional[_R]:
|
|
233
238
|
self.stack.append(node)
|
|
234
239
|
result = super().visit_Modifier(node)
|
|
235
240
|
self.stack.pop()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|