dbrownell-parserlib 0.10.2__tar.gz → 0.11.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 (18) hide show
  1. {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.11.0}/PKG-INFO +12 -10
  2. {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.11.0}/README.md +11 -9
  3. {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.11.0}/pyproject.toml +1 -1
  4. {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.11.0}/src/dbrownell_ParserLib/antlr/antlr_parser.py +25 -0
  5. dbrownell_parserlib-0.11.0/src/dbrownell_ParserLib/antlr/antlr_visitor_mixin.py +150 -0
  6. dbrownell_parserlib-0.10.2/src/dbrownell_ParserLib/antlr/antlr_visitor_mixins.py +0 -169
  7. {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.11.0}/src/dbrownell_ParserLib/__init__.py +0 -0
  8. {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.11.0}/src/dbrownell_ParserLib/antlr/__init__.py +0 -0
  9. {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.11.0}/src/dbrownell_ParserLib/antlr/build_antlr_grammar/__init__.py +0 -0
  10. {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.11.0}/src/dbrownell_ParserLib/antlr/build_antlr_grammar/antlr-4.13.2-complete.jar +0 -0
  11. {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.11.0}/src/dbrownell_ParserLib/errors.py +0 -0
  12. {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.11.0}/src/dbrownell_ParserLib/expression.py +0 -0
  13. {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.11.0}/src/dbrownell_ParserLib/location.py +0 -0
  14. {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.11.0}/src/dbrownell_ParserLib/multiline_strings.py +0 -0
  15. {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.11.0}/src/dbrownell_ParserLib/py.typed +0 -0
  16. {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.11.0}/src/dbrownell_ParserLib/region.py +0 -0
  17. {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.11.0}/src/dbrownell_ParserLib/terminal_expression.py +0 -0
  18. {dbrownell_parserlib-0.10.2 → dbrownell_parserlib-0.11.0}/src/dbrownell_ParserLib/visitors.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dbrownell-parserlib
3
- Version: 0.10.2
3
+ Version: 0.11.0
4
4
  Summary: Functionality useful when creating parsers.
5
5
  Author: Dave Brownell
6
6
  Author-email: Dave Brownell <github@DavidBrownell.com>
@@ -56,8 +56,9 @@ Description-Content-Type: text/markdown
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 entire directory trees with customizable file filtering
60
- - **Whitespace Handling**: Mixins for both insignificant and significant whitespace grammars (e.g., Python-like indentation)
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
 
@@ -101,7 +102,7 @@ Implement a visitor that converts ANTLR parse trees to Expression objects:
101
102
  ```python
102
103
  from dataclasses import dataclass
103
104
 
104
- from dbrownell_ParserLib.antlr.antlr_visitor_mixins import InsignificantWhitespaceAntlrVisitorMixin
105
+ from dbrownell_ParserLib.antlr.antlr_visitor_mixin import AntlrVisitorMixin
105
106
  from dbrownell_ParserLib.expression import Expression
106
107
  from dbrownell_ParserLib.terminal_expression import TerminalExpression
107
108
 
@@ -120,8 +121,10 @@ class BinaryExpression(Expression):
120
121
  yield "right", self.right
121
122
 
122
123
 
123
- class CalculatorVisitor(InsignificantWhitespaceAntlrVisitorMixin, GeneratedVisitor):
124
- def visitExpr(self, ctx):
124
+ class CalculatorVisitor(AntlrVisitorMixin, GeneratedVisitor):
125
+ def visitBinaryOp(self, ctx):
126
+ operator = TerminalExpression[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
- TerminalExpression[str](self.CreateRegion(ctx.children[1]), ctx.children[1].getText()),
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
- TerminalExpression(self.CreateRegion(ctx), int(ctx.getText()))
142
+ TerminalExpression[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
@@ -34,8 +34,9 @@
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 entire directory trees with customizable file filtering
38
- - **Whitespace Handling**: Mixins for both insignificant and significant whitespace grammars (e.g., Python-like indentation)
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
 
@@ -79,7 +80,7 @@ Implement a visitor that converts ANTLR parse trees to Expression objects:
79
80
  ```python
80
81
  from dataclasses import dataclass
81
82
 
82
- from dbrownell_ParserLib.antlr.antlr_visitor_mixins import InsignificantWhitespaceAntlrVisitorMixin
83
+ from dbrownell_ParserLib.antlr.antlr_visitor_mixin import AntlrVisitorMixin
83
84
  from dbrownell_ParserLib.expression import Expression
84
85
  from dbrownell_ParserLib.terminal_expression import TerminalExpression
85
86
 
@@ -98,8 +99,10 @@ class BinaryExpression(Expression):
98
99
  yield "right", self.right
99
100
 
100
101
 
101
- class CalculatorVisitor(InsignificantWhitespaceAntlrVisitorMixin, GeneratedVisitor):
102
- def visitExpr(self, ctx):
102
+ class CalculatorVisitor(AntlrVisitorMixin, GeneratedVisitor):
103
+ def visitBinaryOp(self, ctx):
104
+ operator = TerminalExpression[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
- TerminalExpression[str](self.CreateRegion(ctx.children[1]), ctx.children[1].getText()),
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
- TerminalExpression(self.CreateRegion(ctx), int(ctx.getText()))
120
+ TerminalExpression[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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dbrownell-parserlib"
3
- version = "0.10.2"
3
+ version = "0.11.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)
@@ -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
- )