dbrownell-parserlib 0.10.2__tar.gz → 0.12.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.
- {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.12.0}/PKG-INFO +23 -21
- {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.12.0}/README.md +22 -20
- {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.12.0}/pyproject.toml +1 -1
- {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.12.0}/src/dbrownell_ParserLib/antlr/antlr_parser.py +25 -0
- dbrownell_parserlib-0.12.0/src/dbrownell_ParserLib/antlr/antlr_visitor_mixin.py +150 -0
- dbrownell_parserlib-0.10.2/src/dbrownell_ParserLib/expression.py → dbrownell_parserlib-0.12.0/src/dbrownell_ParserLib/element.py +33 -33
- dbrownell_parserlib-0.10.2/src/dbrownell_ParserLib/terminal_expression.py → dbrownell_parserlib-0.12.0/src/dbrownell_ParserLib/terminal_element.py +3 -3
- {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.12.0}/src/dbrownell_ParserLib/visitors.py +34 -36
- dbrownell_parserlib-0.10.2/src/dbrownell_ParserLib/antlr/antlr_visitor_mixins.py +0 -169
- {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.12.0}/src/dbrownell_ParserLib/__init__.py +0 -0
- {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.12.0}/src/dbrownell_ParserLib/antlr/__init__.py +0 -0
- {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.12.0}/src/dbrownell_ParserLib/antlr/build_antlr_grammar/__init__.py +0 -0
- {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.12.0}/src/dbrownell_ParserLib/antlr/build_antlr_grammar/antlr-4.13.2-complete.jar +0 -0
- {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.12.0}/src/dbrownell_ParserLib/errors.py +0 -0
- {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.12.0}/src/dbrownell_ParserLib/location.py +0 -0
- {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.12.0}/src/dbrownell_ParserLib/multiline_strings.py +0 -0
- {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.12.0}/src/dbrownell_ParserLib/py.typed +0 -0
- {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.12.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.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: Functionality useful when creating parsers.
|
|
5
5
|
Author: Dave Brownell
|
|
6
6
|
Author-email: Dave Brownell <github@DavidBrownell.com>
|
|
@@ -51,13 +51,14 @@ Description-Content-Type: text/markdown
|
|
|
51
51
|
|
|
52
52
|
### Key Features
|
|
53
53
|
- **ANTLR Integration**: Streamlined wrapper around ANTLR for Python, handling grammar compilation and parser generation
|
|
54
|
-
- **AST Representation**:
|
|
54
|
+
- **AST Representation**: Element-based AST nodes with automatic parent-child relationship management
|
|
55
55
|
- **Source Location Tracking**: Every AST node tracks its source file location (filename, line, column ranges) for detailed error reporting
|
|
56
56
|
- **Visitor Pattern**: Flexible visitor pattern implementation with fine-grained traversal control
|
|
57
57
|
- **Error Handling**: Rich error objects that associate messages with source locations and extract Python exception tracebacks
|
|
58
58
|
- **Multi-threaded Parsing**: Built-in support for parsing multiple files concurrently
|
|
59
|
-
- **Workspace Support**: Parse
|
|
60
|
-
- **Whitespace Handling**:
|
|
59
|
+
- **Workspace Support**: Parse translation units organized across multiple workspace roots
|
|
60
|
+
- **Whitespace Handling**: A single visitor mixin supports both insignificant and significant whitespace grammars (e.g., Python-like indentation); grammars that define `INDENT`/`DEDENT` tokens are detected automatically
|
|
61
|
+
- **Multiline String Extraction**: Extract multiline string content that must be vertically aligned with its opening token, with detailed errors on misalignment
|
|
61
62
|
|
|
62
63
|
### How to use `dbrownell_ParserLib`
|
|
63
64
|
|
|
@@ -96,23 +97,23 @@ BuildAntlrGrammar(
|
|
|
96
97
|
```
|
|
97
98
|
|
|
98
99
|
#### 3. Create Custom Visitor
|
|
99
|
-
Implement a visitor that converts ANTLR parse trees to
|
|
100
|
+
Implement a visitor that converts ANTLR parse trees to Element objects:
|
|
100
101
|
|
|
101
102
|
```python
|
|
102
103
|
from dataclasses import dataclass
|
|
103
104
|
|
|
104
|
-
from dbrownell_ParserLib.antlr.
|
|
105
|
-
from dbrownell_ParserLib.
|
|
106
|
-
from dbrownell_ParserLib.
|
|
105
|
+
from dbrownell_ParserLib.antlr.antlr_visitor_mixin import AntlrVisitorMixin
|
|
106
|
+
from dbrownell_ParserLib.element import Element
|
|
107
|
+
from dbrownell_ParserLib.terminal_element import TerminalElement
|
|
107
108
|
|
|
108
109
|
from CalculatorVisitor import CalculatorVisitor as GeneratedVisitor
|
|
109
110
|
|
|
110
111
|
|
|
111
112
|
@dataclass(eq=False)
|
|
112
|
-
class BinaryExpression(
|
|
113
|
-
left:
|
|
114
|
-
operator:
|
|
115
|
-
right:
|
|
113
|
+
class BinaryExpression(Element):
|
|
114
|
+
left: Element
|
|
115
|
+
operator: TerminalElement[str]
|
|
116
|
+
right: Element
|
|
116
117
|
|
|
117
118
|
def _GenerateAcceptDetails(self):
|
|
118
119
|
yield "left", self.left
|
|
@@ -120,8 +121,10 @@ class BinaryExpression(Expression):
|
|
|
120
121
|
yield "right", self.right
|
|
121
122
|
|
|
122
123
|
|
|
123
|
-
class CalculatorVisitor(
|
|
124
|
-
def
|
|
124
|
+
class CalculatorVisitor(AntlrVisitorMixin, GeneratedVisitor):
|
|
125
|
+
def visitBinaryOp(self, ctx):
|
|
126
|
+
operator = TerminalElement[str](self.CreateRegion(ctx.children[1]), ctx.children[1].getText())
|
|
127
|
+
|
|
125
128
|
children = self.GetChildren(ctx)
|
|
126
129
|
assert len(children) == 2, children
|
|
127
130
|
|
|
@@ -129,20 +132,19 @@ class CalculatorVisitor(InsignificantWhitespaceAntlrVisitorMixin, GeneratedVisit
|
|
|
129
132
|
BinaryExpression(
|
|
130
133
|
self.CreateRegion(ctx),
|
|
131
134
|
children[0],
|
|
132
|
-
|
|
133
|
-
|
|
135
|
+
operator,
|
|
134
136
|
children[1],
|
|
135
137
|
)
|
|
136
138
|
)
|
|
137
139
|
|
|
138
140
|
def visitNumber(self, ctx):
|
|
139
141
|
self._stack.append(
|
|
140
|
-
|
|
142
|
+
TerminalElement[int](self.CreateRegion(ctx), int(ctx.getText()))
|
|
141
143
|
)
|
|
142
144
|
```
|
|
143
145
|
|
|
144
146
|
#### 4. Create Parser Instance
|
|
145
|
-
Use `CreateAntlrParser` to build a callable parser
|
|
147
|
+
Use `CreateAntlrParser` to build a callable parser. Grammars that define `INDENT` and `DEDENT` tokens (significant whitespace) are detected automatically; no additional configuration is required.
|
|
146
148
|
|
|
147
149
|
```python
|
|
148
150
|
from dbrownell_ParserLib.antlr.antlr_parser import CreateAntlrParser
|
|
@@ -185,12 +187,12 @@ Use the visitor pattern to process your AST:
|
|
|
185
187
|
```python
|
|
186
188
|
from contextlib import contextmanager
|
|
187
189
|
|
|
188
|
-
from dbrownell_ParserLib.visitors import
|
|
190
|
+
from dbrownell_ParserLib.visitors import ElementVisitorHelper, VisitResult
|
|
189
191
|
|
|
190
192
|
|
|
191
|
-
class EvaluatorVisitor(
|
|
193
|
+
class EvaluatorVisitor(ElementVisitorHelper):
|
|
192
194
|
@contextmanager
|
|
193
|
-
def OnBinaryExpression(self,
|
|
195
|
+
def OnBinaryExpression(self, element):
|
|
194
196
|
# Process binary expressions
|
|
195
197
|
yield VisitResult.Continue
|
|
196
198
|
|
|
@@ -29,13 +29,14 @@
|
|
|
29
29
|
|
|
30
30
|
### Key Features
|
|
31
31
|
- **ANTLR Integration**: Streamlined wrapper around ANTLR for Python, handling grammar compilation and parser generation
|
|
32
|
-
- **AST Representation**:
|
|
32
|
+
- **AST Representation**: Element-based AST nodes with automatic parent-child relationship management
|
|
33
33
|
- **Source Location Tracking**: Every AST node tracks its source file location (filename, line, column ranges) for detailed error reporting
|
|
34
34
|
- **Visitor Pattern**: Flexible visitor pattern implementation with fine-grained traversal control
|
|
35
35
|
- **Error Handling**: Rich error objects that associate messages with source locations and extract Python exception tracebacks
|
|
36
36
|
- **Multi-threaded Parsing**: Built-in support for parsing multiple files concurrently
|
|
37
|
-
- **Workspace Support**: Parse
|
|
38
|
-
- **Whitespace Handling**:
|
|
37
|
+
- **Workspace Support**: Parse translation units organized across multiple workspace roots
|
|
38
|
+
- **Whitespace Handling**: A single visitor mixin supports both insignificant and significant whitespace grammars (e.g., Python-like indentation); grammars that define `INDENT`/`DEDENT` tokens are detected automatically
|
|
39
|
+
- **Multiline String Extraction**: Extract multiline string content that must be vertically aligned with its opening token, with detailed errors on misalignment
|
|
39
40
|
|
|
40
41
|
### How to use `dbrownell_ParserLib`
|
|
41
42
|
|
|
@@ -74,23 +75,23 @@ BuildAntlrGrammar(
|
|
|
74
75
|
```
|
|
75
76
|
|
|
76
77
|
#### 3. Create Custom Visitor
|
|
77
|
-
Implement a visitor that converts ANTLR parse trees to
|
|
78
|
+
Implement a visitor that converts ANTLR parse trees to Element objects:
|
|
78
79
|
|
|
79
80
|
```python
|
|
80
81
|
from dataclasses import dataclass
|
|
81
82
|
|
|
82
|
-
from dbrownell_ParserLib.antlr.
|
|
83
|
-
from dbrownell_ParserLib.
|
|
84
|
-
from dbrownell_ParserLib.
|
|
83
|
+
from dbrownell_ParserLib.antlr.antlr_visitor_mixin import AntlrVisitorMixin
|
|
84
|
+
from dbrownell_ParserLib.element import Element
|
|
85
|
+
from dbrownell_ParserLib.terminal_element import TerminalElement
|
|
85
86
|
|
|
86
87
|
from CalculatorVisitor import CalculatorVisitor as GeneratedVisitor
|
|
87
88
|
|
|
88
89
|
|
|
89
90
|
@dataclass(eq=False)
|
|
90
|
-
class BinaryExpression(
|
|
91
|
-
left:
|
|
92
|
-
operator:
|
|
93
|
-
right:
|
|
91
|
+
class BinaryExpression(Element):
|
|
92
|
+
left: Element
|
|
93
|
+
operator: TerminalElement[str]
|
|
94
|
+
right: Element
|
|
94
95
|
|
|
95
96
|
def _GenerateAcceptDetails(self):
|
|
96
97
|
yield "left", self.left
|
|
@@ -98,8 +99,10 @@ class BinaryExpression(Expression):
|
|
|
98
99
|
yield "right", self.right
|
|
99
100
|
|
|
100
101
|
|
|
101
|
-
class CalculatorVisitor(
|
|
102
|
-
def
|
|
102
|
+
class CalculatorVisitor(AntlrVisitorMixin, GeneratedVisitor):
|
|
103
|
+
def visitBinaryOp(self, ctx):
|
|
104
|
+
operator = TerminalElement[str](self.CreateRegion(ctx.children[1]), ctx.children[1].getText())
|
|
105
|
+
|
|
103
106
|
children = self.GetChildren(ctx)
|
|
104
107
|
assert len(children) == 2, children
|
|
105
108
|
|
|
@@ -107,20 +110,19 @@ class CalculatorVisitor(InsignificantWhitespaceAntlrVisitorMixin, GeneratedVisit
|
|
|
107
110
|
BinaryExpression(
|
|
108
111
|
self.CreateRegion(ctx),
|
|
109
112
|
children[0],
|
|
110
|
-
|
|
111
|
-
|
|
113
|
+
operator,
|
|
112
114
|
children[1],
|
|
113
115
|
)
|
|
114
116
|
)
|
|
115
117
|
|
|
116
118
|
def visitNumber(self, ctx):
|
|
117
119
|
self._stack.append(
|
|
118
|
-
|
|
120
|
+
TerminalElement[int](self.CreateRegion(ctx), int(ctx.getText()))
|
|
119
121
|
)
|
|
120
122
|
```
|
|
121
123
|
|
|
122
124
|
#### 4. Create Parser Instance
|
|
123
|
-
Use `CreateAntlrParser` to build a callable parser
|
|
125
|
+
Use `CreateAntlrParser` to build a callable parser. Grammars that define `INDENT` and `DEDENT` tokens (significant whitespace) are detected automatically; no additional configuration is required.
|
|
124
126
|
|
|
125
127
|
```python
|
|
126
128
|
from dbrownell_ParserLib.antlr.antlr_parser import CreateAntlrParser
|
|
@@ -163,12 +165,12 @@ Use the visitor pattern to process your AST:
|
|
|
163
165
|
```python
|
|
164
166
|
from contextlib import contextmanager
|
|
165
167
|
|
|
166
|
-
from dbrownell_ParserLib.visitors import
|
|
168
|
+
from dbrownell_ParserLib.visitors import ElementVisitorHelper, VisitResult
|
|
167
169
|
|
|
168
170
|
|
|
169
|
-
class EvaluatorVisitor(
|
|
171
|
+
class EvaluatorVisitor(ElementVisitorHelper):
|
|
170
172
|
@contextmanager
|
|
171
|
-
def OnBinaryExpression(self,
|
|
173
|
+
def OnBinaryExpression(self, element):
|
|
172
174
|
# Process binary expressions
|
|
173
175
|
yield VisitResult.Continue
|
|
174
176
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "dbrownell-parserlib"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.12.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:
|
|
@@ -102,6 +102,9 @@ class CreateVisitorFuncType(Protocol):
|
|
|
102
102
|
on_progress_func: Callable[[int], None],
|
|
103
103
|
*,
|
|
104
104
|
is_included_file: bool,
|
|
105
|
+
parser_newline_string: str | None,
|
|
106
|
+
indent_token_type: int | None,
|
|
107
|
+
dedent_token_type: int | None,
|
|
105
108
|
) -> antlr4.ParseTreeVisitor: ...
|
|
106
109
|
|
|
107
110
|
|
|
@@ -117,9 +120,18 @@ def CreateAntlrParser(
|
|
|
117
120
|
get_ast_func: Callable[[antlr4.Parser], antlr4.ParserRuleContext],
|
|
118
121
|
custom_lexer_init_func: Callable[[antlr4.Lexer], None] | None = None,
|
|
119
122
|
custom_parser_init_func: Callable[[antlr4.Parser], None] | None = None,
|
|
123
|
+
parser_newline_string: str | bool | None = None, # noqa: FBT001
|
|
120
124
|
) -> AntlrParser:
|
|
121
125
|
"""Create an `AntlrParser` that uses the given code generated by ANTLR."""
|
|
122
126
|
|
|
127
|
+
indent_token_type = getattr(antlr_parser, "INDENT", None)
|
|
128
|
+
dedent_token_type = getattr(antlr_parser, "DEDENT", None)
|
|
129
|
+
|
|
130
|
+
if parser_newline_string is None and indent_token_type is not None and dedent_token_type is not None:
|
|
131
|
+
parser_newline_string = "newLine"
|
|
132
|
+
elif isinstance(parser_newline_string, bool):
|
|
133
|
+
parser_newline_string = None
|
|
134
|
+
|
|
123
135
|
# ----------------------------------------------------------------------
|
|
124
136
|
def CreateLexer(input_stream: antlr4.InputStream) -> antlr4.Lexer:
|
|
125
137
|
lexer = antlr_lexer(input_stream)
|
|
@@ -145,6 +157,9 @@ def CreateAntlrParser(
|
|
|
145
157
|
CreateParser,
|
|
146
158
|
create_visitor_func,
|
|
147
159
|
get_ast_func,
|
|
160
|
+
indent_token_type,
|
|
161
|
+
dedent_token_type,
|
|
162
|
+
parser_newline_string,
|
|
148
163
|
)
|
|
149
164
|
|
|
150
165
|
|
|
@@ -162,12 +177,19 @@ class _AntlrParser(AntlrParser):
|
|
|
162
177
|
create_parser_func: Callable[[antlr4.CommonTokenStream], antlr4.Parser],
|
|
163
178
|
create_visitor_func: CreateVisitorFuncType,
|
|
164
179
|
get_ast_func: Callable[[antlr4.Parser], antlr4.ParserRuleContext],
|
|
180
|
+
indent_token_type: int | None,
|
|
181
|
+
dedent_token_type: int | None,
|
|
182
|
+
parser_newline_string: str | None,
|
|
165
183
|
) -> None:
|
|
166
184
|
self._create_lexer_func = create_lexer_func
|
|
167
185
|
self._create_parser_func = create_parser_func
|
|
168
186
|
self._create_visitor_func = create_visitor_func
|
|
169
187
|
self._get_ast_func = get_ast_func
|
|
170
188
|
|
|
189
|
+
self._indent_token_type = indent_token_type
|
|
190
|
+
self._dedent_token_type = dedent_token_type
|
|
191
|
+
self._parser_newline_string = parser_newline_string
|
|
192
|
+
|
|
171
193
|
self._execute_state: _AntlrParser._ExecuteState | None = None
|
|
172
194
|
|
|
173
195
|
# ----------------------------------------------------------------------
|
|
@@ -433,6 +455,9 @@ class _AntlrParser(AntlrParser):
|
|
|
433
455
|
fullpath,
|
|
434
456
|
lambda line: cast(None, status.OnProgress(line, None)),
|
|
435
457
|
is_included_file=is_included_file,
|
|
458
|
+
indent_token_type=self._indent_token_type,
|
|
459
|
+
dedent_token_type=self._dedent_token_type,
|
|
460
|
+
parser_newline_string=self._parser_newline_string,
|
|
436
461
|
)
|
|
437
462
|
|
|
438
463
|
ast = self._get_ast_func(parser)
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# noqa: D100
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
from typing import cast, 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 AntlrVisitorMixin:
|
|
17
|
+
"""Mixin that provides common visitation functionality; it is used along side ANTLR-generated visitors."""
|
|
18
|
+
|
|
19
|
+
# ----------------------------------------------------------------------
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
filename: Path,
|
|
23
|
+
on_progress_func: Callable[[int], None],
|
|
24
|
+
*,
|
|
25
|
+
is_included_file: bool,
|
|
26
|
+
parser_newline_string: str | None,
|
|
27
|
+
indent_token_type: int | None,
|
|
28
|
+
dedent_token_type: int | None,
|
|
29
|
+
) -> None:
|
|
30
|
+
self.filename = filename
|
|
31
|
+
self.is_included_file = is_included_file
|
|
32
|
+
|
|
33
|
+
# Protected data
|
|
34
|
+
self._on_progress_func = on_progress_func
|
|
35
|
+
self._parser_newline_string = parser_newline_string
|
|
36
|
+
self._indent_token_type = indent_token_type
|
|
37
|
+
self._dedent_token_type = dedent_token_type
|
|
38
|
+
self._stack: list[object] = []
|
|
39
|
+
|
|
40
|
+
# Private data
|
|
41
|
+
self._current_line: int = 0
|
|
42
|
+
self._stop_token_regex = re.compile(r"^\r?\n[ \t]*$")
|
|
43
|
+
|
|
44
|
+
# ----------------------------------------------------------------------
|
|
45
|
+
def CreateRegion(
|
|
46
|
+
self,
|
|
47
|
+
ctx: antlr4.TerminalNode | antlr4.ParserRuleContext,
|
|
48
|
+
) -> Region:
|
|
49
|
+
"""Create a `Region` from the given context."""
|
|
50
|
+
|
|
51
|
+
if isinstance(ctx, antlr4.TerminalNode):
|
|
52
|
+
start_token = cast(antlr4.Token, ctx.symbol) # ty: ignore[unresolved-attribute]
|
|
53
|
+
end_location = self._CreateEndLocation(start_token)
|
|
54
|
+
else:
|
|
55
|
+
start_token = ctx.start
|
|
56
|
+
stop_token = ctx.stop
|
|
57
|
+
|
|
58
|
+
assert ctx.children is not None
|
|
59
|
+
assert start_token is not None
|
|
60
|
+
assert stop_token is not None
|
|
61
|
+
|
|
62
|
+
end_location: Location | None = None
|
|
63
|
+
|
|
64
|
+
if self._parser_newline_string is not None:
|
|
65
|
+
if self._IsStopToken(stop_token):
|
|
66
|
+
assert len(ctx.children) == 2, ctx.children # noqa: PLR2004
|
|
67
|
+
end_location = self._CreateEndLocation(start_token)
|
|
68
|
+
elif stop_token.type == self._dedent_token_type:
|
|
69
|
+
end_location = Location(stop_token.line, stop_token.column + 1)
|
|
70
|
+
|
|
71
|
+
if end_location is None:
|
|
72
|
+
end_location = self._CreateEndLocation(stop_token)
|
|
73
|
+
|
|
74
|
+
assert start_token.line is not None
|
|
75
|
+
assert start_token.column is not None
|
|
76
|
+
|
|
77
|
+
region = Region(
|
|
78
|
+
self.filename,
|
|
79
|
+
Location(start_token.line, start_token.column + 1),
|
|
80
|
+
end_location,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
self._OnProgress(region.end.line)
|
|
84
|
+
|
|
85
|
+
return region
|
|
86
|
+
|
|
87
|
+
# ----------------------------------------------------------------------
|
|
88
|
+
def GetChildren(self, ctx: antlr4.ParserRuleContext) -> list[object]:
|
|
89
|
+
"""Return the results of visiting the children of the given context."""
|
|
90
|
+
|
|
91
|
+
prev_num_stack_items = len(self._stack)
|
|
92
|
+
|
|
93
|
+
cast(antlr4.ParseTreeVisitor, self).visitChildren(ctx)
|
|
94
|
+
|
|
95
|
+
results = self._stack[prev_num_stack_items:]
|
|
96
|
+
del self._stack[prev_num_stack_items:]
|
|
97
|
+
|
|
98
|
+
return results
|
|
99
|
+
|
|
100
|
+
# ----------------------------------------------------------------------
|
|
101
|
+
# | Protected Methods
|
|
102
|
+
def _OnProgress(self, end_line: int) -> None:
|
|
103
|
+
if end_line > self._current_line:
|
|
104
|
+
self._current_line = end_line
|
|
105
|
+
self._on_progress_func(end_line)
|
|
106
|
+
|
|
107
|
+
# ----------------------------------------------------------------------
|
|
108
|
+
# ----------------------------------------------------------------------
|
|
109
|
+
# ----------------------------------------------------------------------
|
|
110
|
+
@staticmethod
|
|
111
|
+
def _CreateEndLocation(token: antlr4.Token) -> Location:
|
|
112
|
+
assert token.text is not None
|
|
113
|
+
assert token.line is not None
|
|
114
|
+
assert token.column is not None
|
|
115
|
+
|
|
116
|
+
return CreateEndLocation(token.text, token.line, token.column + 1)
|
|
117
|
+
|
|
118
|
+
# ----------------------------------------------------------------------
|
|
119
|
+
def _IsStopToken(self, token: antlr4.Token) -> bool:
|
|
120
|
+
assert token.text is not None
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
token.text == self._parser_newline_string or self._stop_token_regex.match(token.text) is not None
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# ----------------------------------------------------------------------
|
|
128
|
+
def CreateEndLocation(text: str, line: int, column: int) -> Location:
|
|
129
|
+
"""Create a `Location` based on the text and current line and column."""
|
|
130
|
+
|
|
131
|
+
# Note that this function will not be invoked outside of this file; it is implemented as a
|
|
132
|
+
# function so that it can be exercised by automated tests.
|
|
133
|
+
|
|
134
|
+
assert line > 0, line
|
|
135
|
+
assert column > 0, column
|
|
136
|
+
|
|
137
|
+
lines = text.splitlines()
|
|
138
|
+
|
|
139
|
+
last_nonblank_line = None
|
|
140
|
+
|
|
141
|
+
for line_index, line_text in enumerate(lines):
|
|
142
|
+
if line_text.strip():
|
|
143
|
+
last_nonblank_line = line_index
|
|
144
|
+
|
|
145
|
+
assert last_nonblank_line is not None
|
|
146
|
+
|
|
147
|
+
if last_nonblank_line == 0:
|
|
148
|
+
return Location(line, column + len(lines[0]))
|
|
149
|
+
|
|
150
|
+
return Location(line + last_nonblank_line, len(lines[last_nonblank_line]) + 1)
|
|
@@ -8,7 +8,7 @@ from typing import Self, TYPE_CHECKING
|
|
|
8
8
|
|
|
9
9
|
from dbrownell_Common.Types import extension, override
|
|
10
10
|
|
|
11
|
-
from dbrownell_ParserLib.visitors import
|
|
11
|
+
from dbrownell_ParserLib.visitors import ElementVisitor, ElementVisitorHelper, VisitResult
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
14
|
from dbrownell_ParserLib.region import Region
|
|
@@ -16,8 +16,8 @@ if TYPE_CHECKING:
|
|
|
16
16
|
|
|
17
17
|
# ----------------------------------------------------------------------
|
|
18
18
|
@dataclass(eq=False)
|
|
19
|
-
class
|
|
20
|
-
"""Abstract base class for all
|
|
19
|
+
class Element:
|
|
20
|
+
"""Abstract base class for all elements encountered during the parsing process."""
|
|
21
21
|
|
|
22
22
|
# ----------------------------------------------------------------------
|
|
23
23
|
# |
|
|
@@ -27,7 +27,7 @@ class Expression:
|
|
|
27
27
|
region__: Region
|
|
28
28
|
finalize: InitVar[bool] = field(kw_only=True, default=True)
|
|
29
29
|
|
|
30
|
-
parent__: weakref.ref[
|
|
30
|
+
parent__: weakref.ref[Element] | None = field(init=False)
|
|
31
31
|
|
|
32
32
|
_unique_id: tuple[object, ...] | None = field(init=False)
|
|
33
33
|
_disabled: bool = field(init=False)
|
|
@@ -52,14 +52,14 @@ class Expression:
|
|
|
52
52
|
# ----------------------------------------------------------------------
|
|
53
53
|
@property
|
|
54
54
|
def unique_id__(self) -> tuple[object, ...]:
|
|
55
|
-
"""A unique identifier for this
|
|
55
|
+
"""A unique identifier for this element, which can be used for caching and other purposes."""
|
|
56
56
|
|
|
57
57
|
assert self._unique_id is not None
|
|
58
58
|
return self._unique_id
|
|
59
59
|
|
|
60
60
|
@property
|
|
61
61
|
def is_disabled__(self) -> bool:
|
|
62
|
-
"""Whether this
|
|
62
|
+
"""Whether this element is disabled, which can be used for caching and other purposes."""
|
|
63
63
|
|
|
64
64
|
return self._disabled
|
|
65
65
|
|
|
@@ -69,21 +69,21 @@ class Expression:
|
|
|
69
69
|
|
|
70
70
|
# ----------------------------------------------------------------------
|
|
71
71
|
def __eq__(self, other: object) -> bool:
|
|
72
|
-
if not isinstance(other,
|
|
72
|
+
if not isinstance(other, Element):
|
|
73
73
|
return False
|
|
74
74
|
|
|
75
75
|
return self.unique_id__ == other.unique_id__
|
|
76
76
|
|
|
77
77
|
# ----------------------------------------------------------------------
|
|
78
78
|
def __ne__(self, other: object) -> bool:
|
|
79
|
-
if not isinstance(other,
|
|
79
|
+
if not isinstance(other, Element):
|
|
80
80
|
return True
|
|
81
81
|
|
|
82
82
|
return self.unique_id__ != other.unique_id__
|
|
83
83
|
|
|
84
84
|
# ----------------------------------------------------------------------
|
|
85
85
|
def Clone(self, **kwargs) -> Self:
|
|
86
|
-
"""Create a copy of this
|
|
86
|
+
"""Create a copy of this element, optionally overriding some of its fields."""
|
|
87
87
|
|
|
88
88
|
for field_value in fields(self.__class__):
|
|
89
89
|
if field_value.init and field_value.name != "finalize" and field_value.name not in kwargs:
|
|
@@ -93,7 +93,7 @@ class Expression:
|
|
|
93
93
|
|
|
94
94
|
# ----------------------------------------------------------------------
|
|
95
95
|
def Disable(self) -> None:
|
|
96
|
-
"""Disable this
|
|
96
|
+
"""Disable this element so that it doesn't participate in visitation."""
|
|
97
97
|
|
|
98
98
|
assert self.is_disabled__ is False
|
|
99
99
|
self._disabled = True
|
|
@@ -101,20 +101,20 @@ class Expression:
|
|
|
101
101
|
# ----------------------------------------------------------------------
|
|
102
102
|
def Accept( # noqa: C901
|
|
103
103
|
self,
|
|
104
|
-
visitor:
|
|
104
|
+
visitor: ElementVisitor,
|
|
105
105
|
*,
|
|
106
106
|
include_disabled: bool = False,
|
|
107
107
|
) -> VisitResult:
|
|
108
|
-
"""Accept a visitor for visitation of this
|
|
108
|
+
"""Accept a visitor for visitation of this element and its children (if any)."""
|
|
109
109
|
|
|
110
110
|
if self.is_disabled__ and not include_disabled:
|
|
111
111
|
return VisitResult.Continue
|
|
112
112
|
|
|
113
|
-
with visitor.
|
|
114
|
-
if
|
|
115
|
-
return
|
|
113
|
+
with visitor.OnElement(self) as element_visit_result:
|
|
114
|
+
if element_visit_result & VisitResult.Terminate:
|
|
115
|
+
return element_visit_result
|
|
116
116
|
|
|
117
|
-
if
|
|
117
|
+
if element_visit_result & VisitResult.SkipAll:
|
|
118
118
|
return VisitResult.Continue
|
|
119
119
|
|
|
120
120
|
method_name = f"On{self.__class__.__name__}"
|
|
@@ -131,7 +131,7 @@ class Expression:
|
|
|
131
131
|
all_details = list(self._GenerateAcceptDetails())
|
|
132
132
|
|
|
133
133
|
if all_details:
|
|
134
|
-
with visitor.
|
|
134
|
+
with visitor.OnElementDetails(self) as details_visit_result:
|
|
135
135
|
if details_visit_result & VisitResult.Terminate:
|
|
136
136
|
return details_visit_result
|
|
137
137
|
|
|
@@ -161,7 +161,7 @@ class Expression:
|
|
|
161
161
|
if children_result:
|
|
162
162
|
children_name, children = children_result
|
|
163
163
|
|
|
164
|
-
with visitor.
|
|
164
|
+
with visitor.OnElementChildren(
|
|
165
165
|
self,
|
|
166
166
|
children_name,
|
|
167
167
|
children,
|
|
@@ -210,9 +210,9 @@ class Expression:
|
|
|
210
210
|
# ----------------------------------------------------------------------
|
|
211
211
|
@extension
|
|
212
212
|
def _GetTerminalUniqueId(self) -> tuple[object, ...]:
|
|
213
|
-
"""Get a unique identifier for this
|
|
213
|
+
"""Get a unique identifier for this element that doesn't include any of its children."""
|
|
214
214
|
|
|
215
|
-
msg = f"This functionality should be implemented by a terminal
|
|
215
|
+
msg = f"This functionality should be implemented by a terminal element (i.e. an element that doesn't have any children or element details) [{self.__class__.__name__}]"
|
|
216
216
|
raise Exception(msg)
|
|
217
217
|
|
|
218
218
|
# ----------------------------------------------------------------------
|
|
@@ -234,15 +234,15 @@ class Expression:
|
|
|
234
234
|
# | Private Types
|
|
235
235
|
# |
|
|
236
236
|
# ----------------------------------------------------------------------
|
|
237
|
-
class _FinalizeVisitor(
|
|
238
|
-
"""Visitor that visits the children of an
|
|
237
|
+
class _FinalizeVisitor(ElementVisitorHelper):
|
|
238
|
+
"""Visitor that visits the children of an element, but not anything below that; it sets the unique_id__ of the target element and sets its children's parent attribute to the correct value."""
|
|
239
239
|
|
|
240
240
|
# ----------------------------------------------------------------------
|
|
241
241
|
def __init__(
|
|
242
242
|
self,
|
|
243
|
-
|
|
243
|
+
root_element: Element,
|
|
244
244
|
) -> None:
|
|
245
|
-
self.
|
|
245
|
+
self._target_element = root_element
|
|
246
246
|
|
|
247
247
|
self._child_unique_ids: list[tuple[object, ...]] = []
|
|
248
248
|
self._result: tuple[object, ...] | None = None
|
|
@@ -256,27 +256,27 @@ class _FinalizeVisitor(ExpressionVisitorHelper):
|
|
|
256
256
|
# ----------------------------------------------------------------------
|
|
257
257
|
@override
|
|
258
258
|
@contextmanager
|
|
259
|
-
def
|
|
259
|
+
def OnElement(
|
|
260
260
|
self,
|
|
261
|
-
|
|
261
|
+
element: Element,
|
|
262
262
|
) -> Generator[VisitResult]:
|
|
263
|
-
# This code will visit the children of the target
|
|
264
|
-
if
|
|
263
|
+
# This code will visit the children of the target element, but not anything below that.
|
|
264
|
+
if element is self._target_element:
|
|
265
265
|
yield VisitResult.Continue
|
|
266
266
|
|
|
267
267
|
if not self._child_unique_ids:
|
|
268
|
-
result =
|
|
268
|
+
result = element._GetTerminalUniqueId() # noqa: SLF001
|
|
269
269
|
else:
|
|
270
270
|
result = tuple(self._child_unique_ids)
|
|
271
271
|
|
|
272
272
|
assert self._result is None
|
|
273
|
-
self._result = (type(
|
|
273
|
+
self._result = (type(element).__name__, *result)
|
|
274
274
|
|
|
275
275
|
return
|
|
276
276
|
|
|
277
|
-
|
|
277
|
+
element.parent__ = weakref.ref(self._target_element)
|
|
278
278
|
|
|
279
|
-
self._child_unique_ids.append(
|
|
279
|
+
self._child_unique_ids.append(element.unique_id__)
|
|
280
280
|
|
|
281
|
-
# Do not parse the grandchildren of the original target
|
|
281
|
+
# Do not parse the grandchildren of the original target element
|
|
282
282
|
yield VisitResult.SkipChildren
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from typing import override
|
|
4
4
|
|
|
5
|
-
from dbrownell_ParserLib.
|
|
5
|
+
from dbrownell_ParserLib.element import Element
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
# ----------------------------------------------------------------------
|
|
9
9
|
@dataclass(eq=False)
|
|
10
|
-
class
|
|
11
|
-
"""
|
|
10
|
+
class TerminalElement[T](Element):
|
|
11
|
+
"""Element with a single value member, typically used with leaves in an abstract syntax tree."""
|
|
12
12
|
|
|
13
13
|
# ----------------------------------------------------------------------
|
|
14
14
|
value: T
|
{dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.12.0}/src/dbrownell_ParserLib/visitors.py
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Contains types related to
|
|
1
|
+
"""Contains types related to Element visitation."""
|
|
2
2
|
|
|
3
3
|
import types
|
|
4
4
|
|
|
@@ -10,7 +10,7 @@ from typing import override, TYPE_CHECKING
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
11
|
from collections.abc import Generator, Iterable
|
|
12
12
|
|
|
13
|
-
from dbrownell_ParserLib.
|
|
13
|
+
from dbrownell_ParserLib.element import Element
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
# ----------------------------------------------------------------------
|
|
@@ -18,13 +18,13 @@ class VisitResult(Flag):
|
|
|
18
18
|
"""Result returned during visitation that controls the flow of the visitation process."""
|
|
19
19
|
|
|
20
20
|
Continue = 0
|
|
21
|
-
"""Continue visiting the rest of the
|
|
21
|
+
"""Continue visiting the rest of the elements as normal."""
|
|
22
22
|
|
|
23
23
|
SkipDetails = auto()
|
|
24
|
-
"""Skip visiting the details of this
|
|
24
|
+
"""Skip visiting the details of this element, but continue visiting the rest of the elements as normal."""
|
|
25
25
|
|
|
26
26
|
SkipChildren = auto()
|
|
27
|
-
"""Skip visiting the children of this
|
|
27
|
+
"""Skip visiting the children of this element, but continue visiting the rest of the elements as normal."""
|
|
28
28
|
|
|
29
29
|
Terminate = auto()
|
|
30
30
|
"""Terminate the visitation process immediately."""
|
|
@@ -34,41 +34,41 @@ class VisitResult(Flag):
|
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
# ----------------------------------------------------------------------
|
|
37
|
-
class
|
|
38
|
-
"""Abstract base class for a visitor that accepts
|
|
37
|
+
class ElementVisitor(ABC):
|
|
38
|
+
"""Abstract base class for a visitor that accepts Elements."""
|
|
39
39
|
|
|
40
40
|
# ----------------------------------------------------------------------
|
|
41
41
|
@abstractmethod
|
|
42
42
|
@contextmanager
|
|
43
|
-
def
|
|
43
|
+
def OnElement(
|
|
44
44
|
self,
|
|
45
|
-
|
|
45
|
+
element: Element,
|
|
46
46
|
) -> Generator[VisitResult]:
|
|
47
|
-
"""Call for every
|
|
47
|
+
"""Call for every element."""
|
|
48
48
|
|
|
49
49
|
raise NotImplementedError("Abstract method") # noqa: EM101 # pragma: no cover
|
|
50
50
|
|
|
51
51
|
# ----------------------------------------------------------------------
|
|
52
52
|
@abstractmethod
|
|
53
53
|
@contextmanager
|
|
54
|
-
def
|
|
54
|
+
def OnElementDetails(
|
|
55
55
|
self,
|
|
56
|
-
|
|
56
|
+
element: Element,
|
|
57
57
|
) -> Generator[VisitResult]:
|
|
58
|
-
"""Call when visiting the details of an
|
|
58
|
+
"""Call when visiting the details of an element."""
|
|
59
59
|
|
|
60
60
|
raise NotImplementedError("Abstract method") # noqa: EM101 # pragma: no cover
|
|
61
61
|
|
|
62
62
|
# ----------------------------------------------------------------------
|
|
63
63
|
@abstractmethod
|
|
64
64
|
@contextmanager
|
|
65
|
-
def
|
|
65
|
+
def OnElementChildren(
|
|
66
66
|
self,
|
|
67
|
-
|
|
67
|
+
element: Element,
|
|
68
68
|
children_name: str,
|
|
69
|
-
children: Iterable[
|
|
69
|
+
children: Iterable[Element],
|
|
70
70
|
) -> Generator[VisitResult]:
|
|
71
|
-
"""Call when visiting the children of an
|
|
71
|
+
"""Call when visiting the children of an element."""
|
|
72
72
|
|
|
73
73
|
raise NotImplementedError("Abstract method") # noqa: EM101 # pragma: no cover
|
|
74
74
|
|
|
@@ -76,44 +76,44 @@ class ExpressionVisitor(ABC):
|
|
|
76
76
|
# Derived classes should implement the following methods:
|
|
77
77
|
#
|
|
78
78
|
# @contextmanager
|
|
79
|
-
# def On<
|
|
79
|
+
# def On<Element Name>(self, element: <Element Name>) -> Generator[VisitResult]:
|
|
80
80
|
# ...
|
|
81
81
|
#
|
|
82
|
-
# def On<
|
|
82
|
+
# def On<Element Name>__<Detail Name>(self, element_or_elements: <Element Name> | list[<Element Name>], include_disabled: bool) -> VisitResult:
|
|
83
83
|
# ...
|
|
84
84
|
#
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
# ----------------------------------------------------------------------
|
|
88
|
-
class
|
|
88
|
+
class ElementVisitorHelper(ElementVisitor):
|
|
89
89
|
"""Base class that makes writing custom visitors easier by providing default behavior for visitation methods."""
|
|
90
90
|
|
|
91
91
|
# ----------------------------------------------------------------------
|
|
92
92
|
@override
|
|
93
93
|
@contextmanager
|
|
94
|
-
def
|
|
94
|
+
def OnElement(
|
|
95
95
|
self,
|
|
96
|
-
|
|
96
|
+
element: Element,
|
|
97
97
|
) -> Generator[VisitResult]:
|
|
98
98
|
yield VisitResult.Continue
|
|
99
99
|
|
|
100
100
|
# ----------------------------------------------------------------------
|
|
101
101
|
@override
|
|
102
102
|
@contextmanager
|
|
103
|
-
def
|
|
103
|
+
def OnElementDetails(
|
|
104
104
|
self,
|
|
105
|
-
|
|
105
|
+
element: Element,
|
|
106
106
|
) -> Generator[VisitResult]:
|
|
107
107
|
yield VisitResult.Continue
|
|
108
108
|
|
|
109
109
|
# ----------------------------------------------------------------------
|
|
110
110
|
@override
|
|
111
111
|
@contextmanager
|
|
112
|
-
def
|
|
112
|
+
def OnElementChildren(
|
|
113
113
|
self,
|
|
114
|
-
|
|
114
|
+
element: Element,
|
|
115
115
|
children_name: str,
|
|
116
|
-
children: Iterable[
|
|
116
|
+
children: Iterable[Element],
|
|
117
117
|
) -> Generator[VisitResult]:
|
|
118
118
|
yield VisitResult.Continue
|
|
119
119
|
|
|
@@ -128,7 +128,7 @@ class ExpressionVisitorHelper(ExpressionVisitor):
|
|
|
128
128
|
return types.MethodType(self.__class__._DefaultDetailMethod, self) # noqa: SLF001
|
|
129
129
|
|
|
130
130
|
if index == -1:
|
|
131
|
-
return self.__class__.
|
|
131
|
+
return self.__class__._DefaultElementMethod # noqa: SLF001
|
|
132
132
|
|
|
133
133
|
raise AttributeError(method_name)
|
|
134
134
|
|
|
@@ -137,24 +137,22 @@ class ExpressionVisitorHelper(ExpressionVisitor):
|
|
|
137
137
|
# ----------------------------------------------------------------------
|
|
138
138
|
@staticmethod
|
|
139
139
|
@contextmanager
|
|
140
|
-
def
|
|
140
|
+
def _DefaultElementMethod(*args, **kwargs) -> Generator[VisitResult]: # noqa: ARG004
|
|
141
141
|
yield VisitResult.Continue
|
|
142
142
|
|
|
143
143
|
# ----------------------------------------------------------------------
|
|
144
144
|
def _DefaultDetailMethod(
|
|
145
145
|
self,
|
|
146
|
-
|
|
146
|
+
element_or_elements: Element | list[Element],
|
|
147
147
|
*,
|
|
148
148
|
include_disabled: bool,
|
|
149
149
|
) -> VisitResult:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if isinstance(expression_or_expressions, list)
|
|
153
|
-
else [expression_or_expressions]
|
|
150
|
+
elements: list[Element] = ( # ty: ignore[invalid-assignment]
|
|
151
|
+
element_or_elements if isinstance(element_or_elements, list) else [element_or_elements]
|
|
154
152
|
)
|
|
155
153
|
|
|
156
|
-
for
|
|
157
|
-
result =
|
|
154
|
+
for element in elements:
|
|
155
|
+
result = element.Accept(self, include_disabled=include_disabled)
|
|
158
156
|
if result & VisitResult.Terminate:
|
|
159
157
|
return result
|
|
160
158
|
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
"""Various ANTLR visitor mixins that can be used to add functionality to the generated visitor classes."""
|
|
2
|
-
|
|
3
|
-
import re
|
|
4
|
-
|
|
5
|
-
from abc import ABC, abstractmethod
|
|
6
|
-
from typing import cast, override, TYPE_CHECKING
|
|
7
|
-
|
|
8
|
-
import antlr4
|
|
9
|
-
|
|
10
|
-
from dbrownell_ParserLib.region import Location, Region
|
|
11
|
-
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from collections.abc import Callable
|
|
14
|
-
from pathlib import Path
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
# ----------------------------------------------------------------------
|
|
18
|
-
class AntlrVisitorMixinBase(ABC):
|
|
19
|
-
"""Base class for visitor mixins. This class is not meant to be used directly, but rather to be inherited by other mixin classes."""
|
|
20
|
-
|
|
21
|
-
# ----------------------------------------------------------------------
|
|
22
|
-
def __init__(
|
|
23
|
-
self,
|
|
24
|
-
filename: Path,
|
|
25
|
-
on_progress_func: Callable[[int], None],
|
|
26
|
-
*,
|
|
27
|
-
is_included_file: bool,
|
|
28
|
-
) -> None:
|
|
29
|
-
self.filename = filename
|
|
30
|
-
self.is_included_file = is_included_file
|
|
31
|
-
|
|
32
|
-
# Protected data
|
|
33
|
-
self._on_progress_func = on_progress_func
|
|
34
|
-
self._stack: list[object] = []
|
|
35
|
-
|
|
36
|
-
# Private data
|
|
37
|
-
self._current_line: int = 0
|
|
38
|
-
|
|
39
|
-
# ----------------------------------------------------------------------
|
|
40
|
-
def CreateRegion(
|
|
41
|
-
self,
|
|
42
|
-
ctx: antlr4.TerminalNode | antlr4.ParserRuleContext,
|
|
43
|
-
) -> Region:
|
|
44
|
-
"""Create a `Region` from the given context."""
|
|
45
|
-
|
|
46
|
-
if isinstance(ctx, antlr4.TerminalNode):
|
|
47
|
-
token = cast(antlr4.Token, ctx.symbol) # ty: ignore[unresolved-attribute]
|
|
48
|
-
|
|
49
|
-
assert token.column is not None
|
|
50
|
-
|
|
51
|
-
start_line = token.line
|
|
52
|
-
start_col = token.column + 1
|
|
53
|
-
end_line = token.line
|
|
54
|
-
end_col = token.column + 1 + len(token.text)
|
|
55
|
-
|
|
56
|
-
assert start_line is not None
|
|
57
|
-
assert end_line is not None
|
|
58
|
-
|
|
59
|
-
region = Region(
|
|
60
|
-
self.filename,
|
|
61
|
-
Location(start_line, start_col),
|
|
62
|
-
Location(end_line, end_col),
|
|
63
|
-
)
|
|
64
|
-
else:
|
|
65
|
-
region = self._CreateRegionFromRule(ctx)
|
|
66
|
-
|
|
67
|
-
self._OnProgress(region.end.line)
|
|
68
|
-
|
|
69
|
-
return region
|
|
70
|
-
|
|
71
|
-
# ----------------------------------------------------------------------
|
|
72
|
-
def GetChildren(self, ctx: antlr4.ParserRuleContext) -> list[object]:
|
|
73
|
-
"""Return the results of visiting the children of the given context."""
|
|
74
|
-
|
|
75
|
-
prev_num_stack_items = len(self._stack)
|
|
76
|
-
|
|
77
|
-
cast(antlr4.ParseTreeVisitor, self).visitChildren(ctx)
|
|
78
|
-
|
|
79
|
-
results = self._stack[prev_num_stack_items:]
|
|
80
|
-
del self._stack[prev_num_stack_items:]
|
|
81
|
-
|
|
82
|
-
return results
|
|
83
|
-
|
|
84
|
-
# ----------------------------------------------------------------------
|
|
85
|
-
# | Protected Methods
|
|
86
|
-
def _OnProgress(self, end_line: int) -> None:
|
|
87
|
-
if end_line > self._current_line:
|
|
88
|
-
self._current_line = end_line
|
|
89
|
-
self._on_progress_func(end_line)
|
|
90
|
-
|
|
91
|
-
# ----------------------------------------------------------------------
|
|
92
|
-
@abstractmethod
|
|
93
|
-
def _CreateRegionFromRule(self, ctx: antlr4.ParserRuleContext) -> Region: ...
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
# ----------------------------------------------------------------------
|
|
97
|
-
class InsignificantWhitespaceAntlrVisitorMixin(AntlrVisitorMixinBase):
|
|
98
|
-
"""Mixin that adds functionality used for grammars that ignore insignificant whitespace."""
|
|
99
|
-
|
|
100
|
-
# ----------------------------------------------------------------------
|
|
101
|
-
def _CreateRegionFromRule(self, ctx: antlr4.ParserRuleContext) -> Region:
|
|
102
|
-
start_token = ctx.start
|
|
103
|
-
stop_token = ctx.stop
|
|
104
|
-
|
|
105
|
-
if start_token is None or stop_token is None:
|
|
106
|
-
msg = "Context does not have start or stop token"
|
|
107
|
-
raise ValueError(msg)
|
|
108
|
-
|
|
109
|
-
start_line = start_token.line
|
|
110
|
-
start_col = start_token.column + 1
|
|
111
|
-
end_line = stop_token.line
|
|
112
|
-
end_col = stop_token.column + 1 + len(stop_token.text)
|
|
113
|
-
|
|
114
|
-
assert start_line is not None
|
|
115
|
-
assert end_line is not None
|
|
116
|
-
|
|
117
|
-
return Region(
|
|
118
|
-
self.filename,
|
|
119
|
-
Location(start_line, start_col),
|
|
120
|
-
Location(end_line, end_col),
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
# ----------------------------------------------------------------------
|
|
125
|
-
class SignificantWhitespaceAntlrVisitorMixin(AntlrVisitorMixinBase):
|
|
126
|
-
"""Mixin that adds functionality used for grammars that treat whitespace as significant."""
|
|
127
|
-
|
|
128
|
-
# ----------------------------------------------------------------------
|
|
129
|
-
def __init__(
|
|
130
|
-
self,
|
|
131
|
-
dedent_token: int,
|
|
132
|
-
newline_token: int,
|
|
133
|
-
newline_token_string: str,
|
|
134
|
-
) -> None:
|
|
135
|
-
self._dedent_token = dedent_token
|
|
136
|
-
self._newline_token = newline_token
|
|
137
|
-
self._newline_token_string = newline_token_string
|
|
138
|
-
|
|
139
|
-
self._newline_indent_regex = re.compile(r"^\r?\n[ \t]*$")
|
|
140
|
-
|
|
141
|
-
# ----------------------------------------------------------------------
|
|
142
|
-
@override
|
|
143
|
-
def _CreateRegionFromRule(
|
|
144
|
-
self,
|
|
145
|
-
ctx: antlr4.ParserRuleContext,
|
|
146
|
-
) -> Region:
|
|
147
|
-
assert isinstance(ctx.start, antlr4.Token), ctx.start
|
|
148
|
-
assert isinstance(ctx.stop, antlr4.Token), ctx.stop
|
|
149
|
-
|
|
150
|
-
if ctx.stop.text == self._newline_token_string or self._newline_indent_regex.match(ctx.stop.text):
|
|
151
|
-
assert ctx.stop.line == ctx.start.line or ctx.stop.line - 1 == ctx.start.line, (
|
|
152
|
-
ctx.start.line,
|
|
153
|
-
ctx.stop.line,
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
stop_line = ctx.start.line
|
|
157
|
-
stop_column = ctx.start.column + 1 + len(ctx.start.text)
|
|
158
|
-
|
|
159
|
-
return Region(
|
|
160
|
-
self.filename,
|
|
161
|
-
Location(ctx.start.line, ctx.start.column + 1),
|
|
162
|
-
Location(stop_line, stop_column),
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
return Region(
|
|
166
|
-
self.filename,
|
|
167
|
-
Location(ctx.start.line, ctx.start.column + 1),
|
|
168
|
-
Location(ctx.stop.line, ctx.stop.column + 1),
|
|
169
|
-
)
|
{dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.12.0}/src/dbrownell_ParserLib/__init__.py
RENAMED
|
File without changes
|
{dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.12.0}/src/dbrownell_ParserLib/antlr/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.12.0}/src/dbrownell_ParserLib/location.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|