dbrownell-parserlib 0.6.0__tar.gz → 0.8.0__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.
Files changed (17) hide show
  1. {dbrownell_parserlib-0.6.0 → dbrownell_parserlib-0.8.0}/PKG-INFO +1 -1
  2. {dbrownell_parserlib-0.6.0 → dbrownell_parserlib-0.8.0}/pyproject.toml +1 -1
  3. dbrownell_parserlib-0.8.0/src/dbrownell_ParserLib/__init__.py +44 -0
  4. {dbrownell_parserlib-0.6.0 → dbrownell_parserlib-0.8.0}/src/dbrownell_ParserLib/antlr/__init__.py +8 -0
  5. dbrownell_parserlib-0.8.0/src/dbrownell_ParserLib/antlr/antlr_visitor_mixins.py +157 -0
  6. dbrownell_parserlib-0.8.0/src/dbrownell_ParserLib/expression.py +288 -0
  7. dbrownell_parserlib-0.8.0/src/dbrownell_ParserLib/terminal_expression.py +21 -0
  8. dbrownell_parserlib-0.8.0/src/dbrownell_ParserLib/visitors.py +160 -0
  9. dbrownell_parserlib-0.6.0/src/dbrownell_ParserLib/__init__.py +0 -25
  10. {dbrownell_parserlib-0.6.0 → dbrownell_parserlib-0.8.0}/README.md +0 -0
  11. {dbrownell_parserlib-0.6.0 → dbrownell_parserlib-0.8.0}/src/dbrownell_ParserLib/antlr/antlr_parser.py +0 -0
  12. {dbrownell_parserlib-0.6.0 → dbrownell_parserlib-0.8.0}/src/dbrownell_ParserLib/antlr/build_antlr_grammar/__init__.py +0 -0
  13. {dbrownell_parserlib-0.6.0 → dbrownell_parserlib-0.8.0}/src/dbrownell_ParserLib/antlr/build_antlr_grammar/antlr-4.13.2-complete.jar +0 -0
  14. {dbrownell_parserlib-0.6.0 → dbrownell_parserlib-0.8.0}/src/dbrownell_ParserLib/errors.py +0 -0
  15. {dbrownell_parserlib-0.6.0 → dbrownell_parserlib-0.8.0}/src/dbrownell_ParserLib/location.py +0 -0
  16. {dbrownell_parserlib-0.6.0 → dbrownell_parserlib-0.8.0}/src/dbrownell_ParserLib/py.typed +0 -0
  17. {dbrownell_parserlib-0.6.0 → dbrownell_parserlib-0.8.0}/src/dbrownell_ParserLib/region.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dbrownell-parserlib
3
- Version: 0.6.0
3
+ Version: 0.8.0
4
4
  Summary: Functionality useful when creating parsers.
5
5
  Author: Dave Brownell
6
6
  Author-email: Dave Brownell <github@DavidBrownell.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dbrownell-parserlib"
3
- version = "0.6.0"
3
+ version = "0.8.0"
4
4
  # ^^^^^
5
5
  # Wheel names will be generated according to this value. Do not manually modify this value; instead
6
6
  # update it according to committed changes by running this command from the root of the repository:
@@ -0,0 +1,44 @@
1
+ # noqa: D104
2
+
3
+ from importlib.metadata import version
4
+
5
+ from dbrownell_ParserLib.antlr import (
6
+ AntlrParser,
7
+ AntlrParserException,
8
+ AntlrVisitorMixinBase,
9
+ BuildAntlrGrammar,
10
+ CreateAntlrParser,
11
+ InsignificantWhitespaceAntlrVisitorMixin,
12
+ SignificantWhitespaceAntlrVisitorMixin,
13
+ )
14
+ from dbrownell_ParserLib.errors import Error, PythonError
15
+ from dbrownell_ParserLib.expression import Expression
16
+ from dbrownell_ParserLib.location import Location
17
+ from dbrownell_ParserLib.region import Region
18
+ from dbrownell_ParserLib.terminal_expression import TerminalExpression
19
+ from dbrownell_ParserLib.visitors import ExpressionVisitor, ExpressionVisitorHelper, VisitResult
20
+
21
+
22
+ # ----------------------------------------------------------------------
23
+ __all__ = [
24
+ "AntlrParser",
25
+ "AntlrParserException",
26
+ "AntlrVisitorMixinBase",
27
+ "BuildAntlrGrammar",
28
+ "CreateAntlrParser",
29
+ "Error",
30
+ "Expression",
31
+ "ExpressionVisitor",
32
+ "ExpressionVisitorHelper",
33
+ "InsignificantWhitespaceAntlrVisitorMixin",
34
+ "Location",
35
+ "PythonError",
36
+ "Region",
37
+ "SignificantWhitespaceAntlrVisitorMixin",
38
+ "TerminalExpression",
39
+ "VisitResult",
40
+ "__version__",
41
+ ]
42
+
43
+
44
+ __version__ = version("dbrownell_ParserLib")
@@ -1,12 +1,20 @@
1
1
  # noqa: D104
2
2
  from dbrownell_ParserLib.antlr.build_antlr_grammar import BuildAntlrGrammar
3
3
  from dbrownell_ParserLib.antlr.antlr_parser import AntlrParser, AntlrParserException, CreateAntlrParser
4
+ from dbrownell_ParserLib.antlr.antlr_visitor_mixins import (
5
+ AntlrVisitorMixinBase,
6
+ InsignificantWhitespaceAntlrVisitorMixin,
7
+ SignificantWhitespaceAntlrVisitorMixin,
8
+ )
4
9
 
5
10
 
6
11
  # ----------------------------------------------------------------------
7
12
  __all__ = [
8
13
  "AntlrParser",
9
14
  "AntlrParserException",
15
+ "AntlrVisitorMixinBase",
10
16
  "BuildAntlrGrammar",
11
17
  "CreateAntlrParser",
18
+ "InsignificantWhitespaceAntlrVisitorMixin",
19
+ "SignificantWhitespaceAntlrVisitorMixin",
12
20
  ]
@@ -0,0 +1,157 @@
1
+ """Various ANTLR visitor mixins that can be used to add functionality to the generated visitor classes."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import cast, override, TYPE_CHECKING
5
+
6
+ import antlr4
7
+
8
+ from dbrownell_ParserLib.region import Location, Region
9
+
10
+ if TYPE_CHECKING:
11
+ from collections.abc import Callable
12
+ from pathlib import Path
13
+
14
+
15
+ # ----------------------------------------------------------------------
16
+ class AntlrVisitorMixinBase(ABC):
17
+ """Base class for visitor mixins. This class is not meant to be used directly, but rather to be inherited by other mixin classes."""
18
+
19
+ # ----------------------------------------------------------------------
20
+ def __init__(
21
+ self,
22
+ filename: Path,
23
+ on_progress_func: Callable[[int], None],
24
+ *,
25
+ is_included_file: bool,
26
+ ) -> None:
27
+ self.filename = filename
28
+ self.is_included_file = is_included_file
29
+
30
+ # Protected data
31
+ self._on_progress_func = on_progress_func
32
+ self._stack: list[object] = []
33
+
34
+ # Private data
35
+ self._current_line: int = 0
36
+
37
+ # ----------------------------------------------------------------------
38
+ @abstractmethod
39
+ def CreateRegion(
40
+ self,
41
+ ctx: antlr4.ParserRuleContext,
42
+ ) -> Region:
43
+ """Create a `Region` from the given context."""
44
+
45
+ raise NotImplementedError() # pragma: no cover
46
+
47
+ # ----------------------------------------------------------------------
48
+ def GetChildren(self, ctx: antlr4.ParserRuleContext) -> list[object]:
49
+ """Return the results of visiting the children of the given context."""
50
+
51
+ prev_num_stack_items = len(self._stack)
52
+
53
+ cast(antlr4.ParseTreeVisitor, self).visitChildren(ctx)
54
+
55
+ results = self._stack[prev_num_stack_items:]
56
+ del self._stack[prev_num_stack_items:]
57
+
58
+ return results
59
+
60
+ # ----------------------------------------------------------------------
61
+ # | Protected Methods
62
+ def _OnProgress(self, end_line: int) -> None:
63
+ if end_line > self._current_line:
64
+ self._current_line = end_line
65
+ self._on_progress_func(end_line)
66
+
67
+
68
+ # ----------------------------------------------------------------------
69
+ class InsignificantWhitespaceAntlrVisitorMixin(AntlrVisitorMixinBase):
70
+ """Mixin that adds functionality used for grammars that ignore insignificant whitespace."""
71
+
72
+ # ----------------------------------------------------------------------
73
+ @override
74
+ def CreateRegion(
75
+ self,
76
+ ctx: antlr4.ParserRuleContext,
77
+ ) -> Region:
78
+ """Create a `Region` from the given context."""
79
+
80
+ start_token = ctx.start
81
+ stop_token = ctx.stop
82
+
83
+ if start_token is None or stop_token is None:
84
+ msg = "Context does not have start or stop token"
85
+ raise ValueError(msg)
86
+
87
+ return Region(
88
+ self.filename,
89
+ Location(start_token.line, start_token.column),
90
+ Location(stop_token.line, stop_token.column + len(stop_token.text)),
91
+ )
92
+
93
+
94
+ # ----------------------------------------------------------------------
95
+ class SignificantWhitespaceAntlrVisitorMixin(AntlrVisitorMixinBase):
96
+ """Mixin that adds functionality used for grammars that treat whitespace as significant."""
97
+
98
+ # ----------------------------------------------------------------------
99
+ def __init__(
100
+ self,
101
+ dedent_token: int,
102
+ newline_token: int,
103
+ newline_token_string: str,
104
+ ) -> None:
105
+ self._dedent_token = dedent_token
106
+ self._newline_token = newline_token
107
+ self._newline_token_string = newline_token_string
108
+
109
+ # ----------------------------------------------------------------------
110
+ @override
111
+ def CreateRegion(
112
+ self,
113
+ ctx: antlr4.ParserRuleContext,
114
+ ) -> Region:
115
+ assert isinstance(ctx.start, antlr4.Token), ctx.start
116
+ assert isinstance(ctx.stop, antlr4.Token), ctx.stop
117
+
118
+ if ctx.stop.type == self._dedent_token:
119
+ stop_line = ctx.stop.line
120
+ stop_col = ctx.stop.column
121
+
122
+ elif ctx.stop.type == self._newline_token and ctx.stop.text == self._newline_token_string:
123
+ if ctx.stop.line == ctx.start.line:
124
+ # This is the scenario where the statement is followed by a dedent followed by another
125
+ # statement. We don't want the range of this item to overlap with the range of the
126
+ # next item, so use the values as they are, even though it means that a statement
127
+ # that terminates with a newline will not have the newline here.
128
+ stop_line = ctx.stop.line
129
+ stop_col = ctx.stop.column
130
+ else:
131
+ stop_line = ctx.stop.line
132
+ stop_col = ctx.stop.column if ctx.stop.column == 0 else ctx.start.column
133
+
134
+ else:
135
+ stop_line = ctx.stop.line
136
+
137
+ content = ctx.stop.text
138
+ lines = content.split("\n")
139
+
140
+ if ctx.stop.type == self._newline_token:
141
+ assert content.startswith("\n"), content
142
+ assert len(lines) == 2, lines # noqa: PLR2004
143
+
144
+ stop_col = len(lines[-1])
145
+
146
+ if stop_line == ctx.stop.line:
147
+ stop_col += ctx.stop.column
148
+
149
+ stop_line += len(lines) - 1
150
+
151
+ self._OnProgress(stop_line)
152
+
153
+ return Region(
154
+ self.filename,
155
+ Location(ctx.start.line, ctx.start.column + 1),
156
+ Location(stop_line, stop_col),
157
+ )
@@ -0,0 +1,288 @@
1
+ # noqa: D100
2
+ from collections.abc import Generator, Iterable
3
+ from contextlib import contextmanager
4
+ from dataclasses import dataclass, field, fields, InitVar
5
+ from typing import Self, TYPE_CHECKING
6
+
7
+ from dbrownell_Common.Types import extension, override
8
+
9
+ from dbrownell_ParserLib.visitors import ExpressionVisitor, ExpressionVisitorHelper, VisitResult
10
+
11
+ if TYPE_CHECKING:
12
+ from collections.abc import Callable
13
+
14
+ from dbrownell_ParserLib.region import Region
15
+
16
+
17
+ # ----------------------------------------------------------------------
18
+ @dataclass(eq=False)
19
+ class Expression:
20
+ """Abstract base class for all expressions encountered during the parsing process."""
21
+
22
+ # ----------------------------------------------------------------------
23
+ # |
24
+ # | Public Data
25
+ # |
26
+ # ----------------------------------------------------------------------
27
+ region__: Region
28
+ finalize: InitVar[bool] = field(kw_only=True, default=True)
29
+
30
+ parent__: Expression | None = field(init=False)
31
+
32
+ _unique_id: tuple[object, ...] | None = field(init=False)
33
+ _disabled: bool = field(init=False)
34
+
35
+ _finalize_func: Callable[[], None] = field(init=False)
36
+
37
+ # ----------------------------------------------------------------------
38
+ # |
39
+ # | Public Methods
40
+ # |
41
+ # ----------------------------------------------------------------------
42
+ def __post_init__(
43
+ self,
44
+ finalize: bool,
45
+ ) -> None:
46
+ self.parent__ = None
47
+
48
+ self._unique_id = None
49
+ self._disabled = False
50
+
51
+ # ----------------------------------------------------------------------
52
+ def Finalize() -> None:
53
+ visitor = _UniqueIdVisitor(self)
54
+
55
+ self.Accept(visitor)
56
+
57
+ self._unique_id = visitor.unique_id
58
+
59
+ # ----------------------------------------------------------------------
60
+
61
+ self._finalize_func = Finalize
62
+
63
+ if finalize:
64
+ self._Finalize()
65
+
66
+ # ----------------------------------------------------------------------
67
+ @property
68
+ def unique_id__(self) -> tuple[object, ...]:
69
+ """A unique identifier for this expression, which can be used for caching and other purposes."""
70
+
71
+ assert self._unique_id is not None
72
+ return self._unique_id
73
+
74
+ @property
75
+ def is_disabled__(self) -> bool:
76
+ """Whether this expression is disabled, which can be used for caching and other purposes."""
77
+
78
+ return self._disabled
79
+
80
+ # ----------------------------------------------------------------------
81
+ def __hash__(self) -> int:
82
+ return hash(self.unique_id__)
83
+
84
+ # ----------------------------------------------------------------------
85
+ def __eq__(self, other: object) -> bool:
86
+ if not isinstance(other, Expression):
87
+ return False
88
+
89
+ return self.unique_id__ == other.unique_id__
90
+
91
+ # ----------------------------------------------------------------------
92
+ def __ne__(self, other: object) -> bool:
93
+ if not isinstance(other, Expression):
94
+ return True
95
+
96
+ return self.unique_id__ != other.unique_id__
97
+
98
+ # ----------------------------------------------------------------------
99
+ def Clone(self, **kwargs) -> Self:
100
+ """Create a copy of this expression, optionally overriding some of its fields."""
101
+
102
+ for field_value in fields(self.__class__):
103
+ if field_value.init and field_value.name != "finalize" and field_value.name not in kwargs:
104
+ kwargs[field_value.name] = getattr(self, field_value.name)
105
+
106
+ return self.__class__(**kwargs)
107
+
108
+ # ----------------------------------------------------------------------
109
+ def Disable(self) -> None:
110
+ """Disable this expression so that it doesn't participate in visitation."""
111
+
112
+ assert self.is_disabled__ is False
113
+ self._disabled = True
114
+
115
+ # ----------------------------------------------------------------------
116
+ def Accept( # noqa: C901
117
+ self,
118
+ visitor: ExpressionVisitor,
119
+ *,
120
+ include_disabled: bool = False,
121
+ ) -> VisitResult:
122
+ """Accept a visitor for visitation of this expression and its children (if any)."""
123
+
124
+ if self.is_disabled__ and not include_disabled:
125
+ return VisitResult.Continue
126
+
127
+ with visitor.OnExpression(self) as expression_visit_result:
128
+ if expression_visit_result & VisitResult.Terminate:
129
+ return expression_visit_result
130
+
131
+ if expression_visit_result & VisitResult.SkipAll:
132
+ return VisitResult.Continue
133
+
134
+ method_name = f"On{self.__class__.__name__}"
135
+
136
+ method = getattr(visitor, method_name, None)
137
+ assert method is not None, method_name
138
+
139
+ with method(self) as method_visit_result:
140
+ if method_visit_result & VisitResult.Terminate:
141
+ return method_visit_result
142
+
143
+ # Details
144
+ if not method_visit_result & VisitResult.SkipDetails:
145
+ all_details = list(self._GenerateAcceptDetails())
146
+
147
+ if all_details:
148
+ with visitor.OnExpressionDetails(self) as details_visit_result:
149
+ if details_visit_result & VisitResult.Terminate:
150
+ return details_visit_result
151
+
152
+ if not details_visit_result & VisitResult.SkipDetails:
153
+ method_name_prefix = f"On{self.__class__.__name__}__"
154
+
155
+ for detail_name, detail_value in all_details:
156
+ method_name = f"{method_name_prefix}{detail_name}"
157
+
158
+ method = getattr(visitor, method_name, None)
159
+ assert method is not None, method_name
160
+
161
+ detail_visit_result = method(
162
+ detail_value, include_disabled=include_disabled
163
+ )
164
+
165
+ if detail_visit_result & VisitResult.Terminate:
166
+ return detail_visit_result
167
+
168
+ if detail_visit_result & VisitResult.SkipDetails:
169
+ break
170
+
171
+ # Children
172
+ if not method_visit_result & VisitResult.SkipChildren:
173
+ children_result = self._GetAcceptChildren()
174
+
175
+ if children_result:
176
+ children_name, children = children_result
177
+
178
+ with visitor.OnExpressionChildren(
179
+ self,
180
+ children_name,
181
+ children,
182
+ ) as children_visit_result:
183
+ if children_visit_result & VisitResult.Terminate:
184
+ return children_visit_result
185
+
186
+ if not children_visit_result & VisitResult.SkipChildren:
187
+ for child in children:
188
+ child_accept_result = child.Accept(
189
+ visitor, include_disabled=include_disabled
190
+ )
191
+
192
+ if child_accept_result & VisitResult.Terminate:
193
+ return child_accept_result
194
+
195
+ if child_accept_result & VisitResult.SkipChildren:
196
+ break
197
+
198
+ return VisitResult.Continue
199
+
200
+ # ----------------------------------------------------------------------
201
+ # |
202
+ # | Protected Types
203
+ # |
204
+ # ----------------------------------------------------------------------
205
+ _GenerateAcceptDetailsResultType = Generator[tuple[str, list[Self]]]
206
+ _GetAcceptChildrenResultType = tuple[str, Iterable[Self]] | None
207
+
208
+ # ----------------------------------------------------------------------
209
+ # |
210
+ # | Protected Methods
211
+ # |
212
+ # ----------------------------------------------------------------------
213
+ def _Finalize(self) -> None:
214
+ self._finalize_func()
215
+
216
+ # ----------------------------------------------------------------------
217
+ # |
218
+ # | Private Methods
219
+ # |
220
+ # ----------------------------------------------------------------------
221
+ @extension
222
+ def _GetTerminalUniqueId(self) -> tuple[object, ...]:
223
+ """Get a unique identifier for this expression that doesn't include any of its children."""
224
+
225
+ msg = "This functionality should be implemented by a terminal expression (i.e. an expression that doesn't have any children)."
226
+ raise Exception(msg)
227
+
228
+ # ----------------------------------------------------------------------
229
+ @extension
230
+ def _GenerateAcceptDetails(self) -> _GenerateAcceptDetailsResultType:
231
+ # Nothing by default
232
+ if False:
233
+ yield
234
+
235
+ # ----------------------------------------------------------------------
236
+ @extension
237
+ def _GetAcceptChildren(self) -> _GetAcceptChildrenResultType:
238
+ # Nothing by default
239
+ return None
240
+
241
+
242
+ # ----------------------------------------------------------------------
243
+ # |
244
+ # | Private Types
245
+ # |
246
+ # ----------------------------------------------------------------------
247
+ class _UniqueIdVisitor(ExpressionVisitorHelper):
248
+ # ----------------------------------------------------------------------
249
+ def __init__(
250
+ self,
251
+ root_expression: Expression,
252
+ ) -> None:
253
+ self._target_expression = root_expression
254
+
255
+ self._child_unique_ids: list[tuple[object, ...]] = []
256
+ self._result: tuple[object, ...] | None = None
257
+
258
+ # ----------------------------------------------------------------------
259
+ @property
260
+ def unique_id(self) -> tuple[object, ...]:
261
+ assert self._result is not None
262
+ return self._result
263
+
264
+ # ----------------------------------------------------------------------
265
+ @override
266
+ @contextmanager
267
+ def OnExpression(
268
+ self,
269
+ expression: Expression,
270
+ ) -> Generator[VisitResult]:
271
+ if expression is self._target_expression:
272
+ yield VisitResult.Continue
273
+
274
+ if not self._child_unique_ids:
275
+ result = expression._GetTerminalUniqueId() # noqa: SLF001
276
+ else:
277
+ result = tuple(self._child_unique_ids)
278
+
279
+ assert self._result is None
280
+ self._result = (type(expression).__name__, *result)
281
+
282
+ return
283
+
284
+ self._child_unique_ids.append(expression.unique_id__)
285
+
286
+ # Do not parse the children of this expression as its unique id already takes that information
287
+ # into account.
288
+ yield VisitResult.SkipChildren
@@ -0,0 +1,21 @@
1
+ # noqa: D100
2
+ from dataclasses import dataclass
3
+ from typing import override
4
+
5
+ from dbrownell_ParserLib.expression import Expression
6
+
7
+
8
+ # ----------------------------------------------------------------------
9
+ @dataclass(eq=False)
10
+ class TerminalExpression[T](Expression):
11
+ """Expression with a single value member, typically used with leaves in an abstract syntax tree."""
12
+
13
+ # ----------------------------------------------------------------------
14
+ value: T
15
+
16
+ # ----------------------------------------------------------------------
17
+ # ----------------------------------------------------------------------
18
+ # ----------------------------------------------------------------------
19
+ @override
20
+ def _GetTerminalUniqueId(self) -> tuple[object, ...]:
21
+ return (self.value,)
@@ -0,0 +1,160 @@
1
+ """Contains types related to Expression visitation."""
2
+
3
+ import types
4
+
5
+ from abc import ABC, abstractmethod
6
+ from contextlib import contextmanager
7
+ from enum import auto, Flag
8
+ from typing import override, TYPE_CHECKING
9
+
10
+ if TYPE_CHECKING:
11
+ from collections.abc import Generator, Iterable
12
+
13
+ from dbrownell_ParserLib.expression import Expression
14
+
15
+
16
+ # ----------------------------------------------------------------------
17
+ class VisitResult(Flag):
18
+ """Result returned during visitation that controls the flow of the visitation process."""
19
+
20
+ Continue = 0
21
+ """Continue visiting the rest of the expressions as normal."""
22
+
23
+ SkipDetails = auto()
24
+ """Skip visiting the details of this expression, but continue visiting the rest of the expressions as normal."""
25
+
26
+ SkipChildren = auto()
27
+ """Skip visiting the children of this expression, but continue visiting the rest of the expressions as normal."""
28
+
29
+ Terminate = auto()
30
+ """Terminate the visitation process immediately."""
31
+
32
+ # Amalgamations
33
+ SkipAll = SkipDetails | SkipChildren
34
+
35
+
36
+ # ----------------------------------------------------------------------
37
+ class ExpressionVisitor(ABC):
38
+ """Abstract base class for a visitor that accepts Expressions."""
39
+
40
+ # ----------------------------------------------------------------------
41
+ @abstractmethod
42
+ @contextmanager
43
+ def OnExpression(
44
+ self,
45
+ expression: Expression,
46
+ ) -> Generator[VisitResult]:
47
+ """Call for every expression."""
48
+
49
+ raise NotImplementedError("Abstract method") # noqa: EM101 # pragma: no cover
50
+
51
+ # ----------------------------------------------------------------------
52
+ @abstractmethod
53
+ @contextmanager
54
+ def OnExpressionDetails(
55
+ self,
56
+ expression: Expression,
57
+ ) -> Generator[VisitResult]:
58
+ """Call when visiting the details of an expression."""
59
+
60
+ raise NotImplementedError("Abstract method") # noqa: EM101 # pragma: no cover
61
+
62
+ # ----------------------------------------------------------------------
63
+ @abstractmethod
64
+ @contextmanager
65
+ def OnExpressionChildren(
66
+ self,
67
+ expression: Expression,
68
+ children_name: str,
69
+ children: Iterable[Expression],
70
+ ) -> Generator[VisitResult]:
71
+ """Call when visiting the children of an expression."""
72
+
73
+ raise NotImplementedError("Abstract method") # noqa: EM101 # pragma: no cover
74
+
75
+ # ----------------------------------------------------------------------
76
+ # Derived classes should implement the following methods:
77
+ #
78
+ # @contextmanager
79
+ # def On<Expression Name>(self, expression: <Expression Name>) -> Generator[VisitResult]:
80
+ # ...
81
+ #
82
+ # def On<Expression Name>__<Detail Name>(self, expression_or_expressions: <Expression Name> | list[<Expression Name>], include_disabled: bool) -> VisitResult:
83
+ # ...
84
+ #
85
+
86
+
87
+ # ----------------------------------------------------------------------
88
+ class ExpressionVisitorHelper(ExpressionVisitor):
89
+ """Base class that makes writing custom visitors easier by providing default behavior for visitation methods."""
90
+
91
+ # ----------------------------------------------------------------------
92
+ @override
93
+ @contextmanager
94
+ def OnExpression(
95
+ self,
96
+ expression: Expression,
97
+ ) -> Generator[VisitResult]:
98
+ yield VisitResult.Continue
99
+
100
+ # ----------------------------------------------------------------------
101
+ @override
102
+ @contextmanager
103
+ def OnExpressionDetails(
104
+ self,
105
+ expression: Expression,
106
+ ) -> Generator[VisitResult]:
107
+ yield VisitResult.Continue
108
+
109
+ # ----------------------------------------------------------------------
110
+ @override
111
+ @contextmanager
112
+ def OnExpressionChildren(
113
+ self,
114
+ expression: Expression,
115
+ children_name: str,
116
+ children: Iterable[Expression],
117
+ ) -> Generator[VisitResult]:
118
+ yield VisitResult.Continue
119
+
120
+ # ----------------------------------------------------------------------
121
+ def __getattr__(
122
+ self,
123
+ method_name: str,
124
+ ) -> object:
125
+ if method_name.endswith("Expression"):
126
+ return self.__class__._DefaultExpressionMethod # noqa: SLF001
127
+
128
+ index = method_name.find("Expression__")
129
+ if index != -1 and index + len("Expression__") + 1 < len(method_name):
130
+ return types.MethodType(self.__class__._DefaultDetailMethod, self) # noqa: SLF001
131
+
132
+ raise AttributeError(method_name)
133
+
134
+ # ----------------------------------------------------------------------
135
+ # ----------------------------------------------------------------------
136
+ # ----------------------------------------------------------------------
137
+ @staticmethod
138
+ @contextmanager
139
+ def _DefaultExpressionMethod(*args, **kwargs) -> Generator[VisitResult]: # noqa: ARG004
140
+ yield VisitResult.Continue
141
+
142
+ # ----------------------------------------------------------------------
143
+ def _DefaultDetailMethod(
144
+ self,
145
+ expression_or_expressions: Expression | list[Expression],
146
+ *,
147
+ include_disabled: bool,
148
+ ) -> VisitResult:
149
+ expressions: list[Expression] = ( # ty: ignore[invalid-assignment]
150
+ expression_or_expressions
151
+ if isinstance(expression_or_expressions, list)
152
+ else [expression_or_expressions]
153
+ )
154
+
155
+ for expression in expressions:
156
+ result = expression.Accept(self, include_disabled=include_disabled)
157
+ if result & VisitResult.Terminate:
158
+ return result
159
+
160
+ return VisitResult.Continue
@@ -1,25 +0,0 @@
1
- # noqa: D104
2
-
3
- from importlib.metadata import version
4
-
5
- from dbrownell_ParserLib.antlr import AntlrParser, AntlrParserException, BuildAntlrGrammar, CreateAntlrParser
6
- from dbrownell_ParserLib.errors import Error, PythonError
7
- from dbrownell_ParserLib.location import Location
8
- from dbrownell_ParserLib.region import Region
9
-
10
-
11
- # ----------------------------------------------------------------------
12
- __all__ = [
13
- "AntlrParser",
14
- "AntlrParserException",
15
- "BuildAntlrGrammar",
16
- "CreateAntlrParser",
17
- "Error",
18
- "Location",
19
- "PythonError",
20
- "Region",
21
- "__version__",
22
- ]
23
-
24
-
25
- __version__ = version("dbrownell_ParserLib")