dbrownell-parserlib 0.8.1__tar.gz → 0.8.3__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.8.1 → dbrownell_parserlib-0.8.3}/PKG-INFO +146 -3
- {dbrownell_parserlib-0.8.1 → dbrownell_parserlib-0.8.3}/README.md +145 -2
- {dbrownell_parserlib-0.8.1 → dbrownell_parserlib-0.8.3}/pyproject.toml +1 -1
- {dbrownell_parserlib-0.8.1 → dbrownell_parserlib-0.8.3}/src/dbrownell_ParserLib/antlr/antlr_parser.py +1 -1
- {dbrownell_parserlib-0.8.1 → dbrownell_parserlib-0.8.3}/src/dbrownell_ParserLib/antlr/antlr_visitor_mixins.py +57 -72
- {dbrownell_parserlib-0.8.1 → dbrownell_parserlib-0.8.3}/src/dbrownell_ParserLib/__init__.py +0 -0
- {dbrownell_parserlib-0.8.1 → dbrownell_parserlib-0.8.3}/src/dbrownell_ParserLib/antlr/__init__.py +0 -0
- {dbrownell_parserlib-0.8.1 → dbrownell_parserlib-0.8.3}/src/dbrownell_ParserLib/antlr/build_antlr_grammar/__init__.py +0 -0
- {dbrownell_parserlib-0.8.1 → dbrownell_parserlib-0.8.3}/src/dbrownell_ParserLib/antlr/build_antlr_grammar/antlr-4.13.2-complete.jar +0 -0
- {dbrownell_parserlib-0.8.1 → dbrownell_parserlib-0.8.3}/src/dbrownell_ParserLib/errors.py +0 -0
- {dbrownell_parserlib-0.8.1 → dbrownell_parserlib-0.8.3}/src/dbrownell_ParserLib/expression.py +0 -0
- {dbrownell_parserlib-0.8.1 → dbrownell_parserlib-0.8.3}/src/dbrownell_ParserLib/location.py +0 -0
- {dbrownell_parserlib-0.8.1 → dbrownell_parserlib-0.8.3}/src/dbrownell_ParserLib/py.typed +0 -0
- {dbrownell_parserlib-0.8.1 → dbrownell_parserlib-0.8.3}/src/dbrownell_ParserLib/region.py +0 -0
- {dbrownell_parserlib-0.8.1 → dbrownell_parserlib-0.8.3}/src/dbrownell_ParserLib/terminal_expression.py +0 -0
- {dbrownell_parserlib-0.8.1 → dbrownell_parserlib-0.8.3}/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.8.
|
|
3
|
+
Version: 0.8.3
|
|
4
4
|
Summary: Functionality useful when creating parsers.
|
|
5
5
|
Author: Dave Brownell
|
|
6
6
|
Author-email: Dave Brownell <github@DavidBrownell.com>
|
|
@@ -47,10 +47,153 @@ Description-Content-Type: text/markdown
|
|
|
47
47
|
- [License](#license)
|
|
48
48
|
|
|
49
49
|
## Overview
|
|
50
|
-
|
|
50
|
+
`dbrownell_ParserLib` is a Python library that provides utility functionality for creating parsers, with a primary focus on [ANTLR](https://antlr.org) (Another Tool for Language Recognition) integration. The library simplifies the process of building parsers that produce strongly-typed abstract syntax trees (ASTs) with comprehensive source location tracking.
|
|
51
|
+
|
|
52
|
+
### Key Features
|
|
53
|
+
- **ANTLR Integration**: Streamlined wrapper around ANTLR for Python, handling grammar compilation and parser generation
|
|
54
|
+
- **AST Representation**: Expression-based AST nodes with automatic parent-child relationship management
|
|
55
|
+
- **Source Location Tracking**: Every AST node tracks its source file location (filename, line, column ranges) for detailed error reporting
|
|
56
|
+
- **Visitor Pattern**: Flexible visitor pattern implementation with fine-grained traversal control
|
|
57
|
+
- **Error Handling**: Rich error objects that associate messages with source locations and extract Python exception tracebacks
|
|
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)
|
|
51
61
|
|
|
52
62
|
### How to use `dbrownell_ParserLib`
|
|
53
|
-
|
|
63
|
+
|
|
64
|
+
The typical workflow for creating a parser with this library involves:
|
|
65
|
+
|
|
66
|
+
#### 1. Define an ANTLR Grammar
|
|
67
|
+
Create a `.g4` grammar file defining your language syntax:
|
|
68
|
+
```antlr
|
|
69
|
+
grammar Calculator;
|
|
70
|
+
|
|
71
|
+
expr : expr op=('*'|'/') expr # BinaryOp
|
|
72
|
+
| expr op=('+'|'-') expr # BinaryOp
|
|
73
|
+
| INT # Number
|
|
74
|
+
;
|
|
75
|
+
|
|
76
|
+
INT : [0-9]+ ;
|
|
77
|
+
WS : [ \t\n\r]+ -> skip ;
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
#### 2. Build the Grammar
|
|
81
|
+
Use `BuildAntlrGrammar` to generate the lexer and parser:
|
|
82
|
+
```python
|
|
83
|
+
from dbrownell_ParserLib import BuildAntlrGrammar
|
|
84
|
+
from pathlib import Path
|
|
85
|
+
|
|
86
|
+
BuildAntlrGrammar(
|
|
87
|
+
dm, # DoneManager instance
|
|
88
|
+
Path("Calculator.g4"),
|
|
89
|
+
Path("output_dir"),
|
|
90
|
+
)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### 3. Create Custom Visitor
|
|
94
|
+
Implement a visitor that converts ANTLR parse trees to Expression objects:
|
|
95
|
+
```python
|
|
96
|
+
from dbrownell_ParserLib import (
|
|
97
|
+
Expression,
|
|
98
|
+
InsignificantWhitespaceAntlrVisitorMixin,
|
|
99
|
+
TerminalExpression,
|
|
100
|
+
)
|
|
101
|
+
from dataclasses import dataclass
|
|
102
|
+
|
|
103
|
+
from CalculatorVisitor import CalculatorVisitor as GeneratedVisitor
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass(eq=False)
|
|
108
|
+
class BinaryExpression(Expression):
|
|
109
|
+
left: Expression
|
|
110
|
+
operator: TerminalExpression[str]
|
|
111
|
+
right: Expression
|
|
112
|
+
|
|
113
|
+
def _GenerateAcceptDetails(self):
|
|
114
|
+
yield "left", self.left
|
|
115
|
+
yield "operator", self.operator
|
|
116
|
+
yield "right", self.right
|
|
117
|
+
|
|
118
|
+
class CalculatorVisitor(InsignificantWhitespaceAntlrVisitorMixin, GeneratedVisitor):
|
|
119
|
+
|
|
120
|
+
def visitBinaryOp(self, ctx):
|
|
121
|
+
children = self.GetChildren(ctx)
|
|
122
|
+
self._stack.append(
|
|
123
|
+
BinaryExpression(
|
|
124
|
+
self.CreateRegion(ctx),
|
|
125
|
+
children[0],
|
|
126
|
+
TerminalExpression[str](self.CreateRegion(ctx.children[1]), ctx.children[1].getText()),
|
|
127
|
+
children[1],
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
def visitNumber(self, ctx):
|
|
132
|
+
self._stack.append(
|
|
133
|
+
TerminalExpression(self.CreateRegion(ctx), int(ctx.INT().getText()))
|
|
134
|
+
)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
#### 4. Create Parser Instance
|
|
138
|
+
Use `CreateAntlrParser` to build a callable parser:
|
|
139
|
+
```python
|
|
140
|
+
from dbrownell_ParserLib import CreateAntlrParser
|
|
141
|
+
|
|
142
|
+
parser = CreateAntlrParser(
|
|
143
|
+
CalculatorLexer,
|
|
144
|
+
CalculatorParser,
|
|
145
|
+
CalculatorVisitor,
|
|
146
|
+
lambda p: p.expr(), # Entry point rule
|
|
147
|
+
)
|
|
148
|
+
```
|
|
149
|
+
from pathlib import Path
|
|
150
|
+
|
|
151
|
+
from dbrownell_ParserLib import Error
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
#### 5. Parse Files
|
|
156
|
+
Parse single files, multiple files, or entire workspaces:
|
|
157
|
+
```python
|
|
158
|
+
# Parse a single file
|
|
159
|
+
results = parser(dm, Path("input.txt"), None)
|
|
160
|
+
|
|
161
|
+
# Parse multiple files
|
|
162
|
+
results = parser(dm, [Path("file1.txt"), Path("file2.txt")], None)
|
|
163
|
+
|
|
164
|
+
# Check results
|
|
165
|
+
for filepath, result in results.items():
|
|
166
|
+
if isinstance(result, Error):
|
|
167
|
+
print(f"Parse error in {filepath}: {result}")
|
|
168
|
+
else:
|
|
169
|
+
# result is the visitor containing the parsed AST
|
|
170
|
+
ast = result._stack[0]
|
|
171
|
+
# Process the AST
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
from contextlib import contextmanager
|
|
175
|
+
|
|
176
|
+
#### 6. Traverse the AST
|
|
177
|
+
Use the visitor pattern to process your AST:
|
|
178
|
+
```python
|
|
179
|
+
@contextmanager
|
|
180
|
+
|
|
181
|
+
from dbrownell_ParserLib import ExpressionVisitorHelper, VisitResult
|
|
182
|
+
|
|
183
|
+
class EvaluatorVisitor(ExpressionVisitorHelper):
|
|
184
|
+
def OnBinaryExpression(self, expression):
|
|
185
|
+
@contextmanager
|
|
186
|
+
|
|
187
|
+
# Process binary expressions
|
|
188
|
+
yield VisitResult.Continue
|
|
189
|
+
|
|
190
|
+
# Process terminal values
|
|
191
|
+
yield VisitResult.Continue
|
|
192
|
+
|
|
193
|
+
ast.Accept(EvaluatorVisitor())
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
For detailed examples, see the test files in the repository, particularly the end-to-end tests that demonstrate complete parsing workflows.
|
|
54
197
|
|
|
55
198
|
<!-- Content below this delimiter will be copied to the generated README.md file. DO NOT REMOVE THIS COMMENT, as it will cause regeneration to fail. -->
|
|
56
199
|
|
|
@@ -25,10 +25,153 @@
|
|
|
25
25
|
- [License](#license)
|
|
26
26
|
|
|
27
27
|
## Overview
|
|
28
|
-
|
|
28
|
+
`dbrownell_ParserLib` is a Python library that provides utility functionality for creating parsers, with a primary focus on [ANTLR](https://antlr.org) (Another Tool for Language Recognition) integration. The library simplifies the process of building parsers that produce strongly-typed abstract syntax trees (ASTs) with comprehensive source location tracking.
|
|
29
|
+
|
|
30
|
+
### Key Features
|
|
31
|
+
- **ANTLR Integration**: Streamlined wrapper around ANTLR for Python, handling grammar compilation and parser generation
|
|
32
|
+
- **AST Representation**: Expression-based AST nodes with automatic parent-child relationship management
|
|
33
|
+
- **Source Location Tracking**: Every AST node tracks its source file location (filename, line, column ranges) for detailed error reporting
|
|
34
|
+
- **Visitor Pattern**: Flexible visitor pattern implementation with fine-grained traversal control
|
|
35
|
+
- **Error Handling**: Rich error objects that associate messages with source locations and extract Python exception tracebacks
|
|
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)
|
|
29
39
|
|
|
30
40
|
### How to use `dbrownell_ParserLib`
|
|
31
|
-
|
|
41
|
+
|
|
42
|
+
The typical workflow for creating a parser with this library involves:
|
|
43
|
+
|
|
44
|
+
#### 1. Define an ANTLR Grammar
|
|
45
|
+
Create a `.g4` grammar file defining your language syntax:
|
|
46
|
+
```antlr
|
|
47
|
+
grammar Calculator;
|
|
48
|
+
|
|
49
|
+
expr : expr op=('*'|'/') expr # BinaryOp
|
|
50
|
+
| expr op=('+'|'-') expr # BinaryOp
|
|
51
|
+
| INT # Number
|
|
52
|
+
;
|
|
53
|
+
|
|
54
|
+
INT : [0-9]+ ;
|
|
55
|
+
WS : [ \t\n\r]+ -> skip ;
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
#### 2. Build the Grammar
|
|
59
|
+
Use `BuildAntlrGrammar` to generate the lexer and parser:
|
|
60
|
+
```python
|
|
61
|
+
from dbrownell_ParserLib import BuildAntlrGrammar
|
|
62
|
+
from pathlib import Path
|
|
63
|
+
|
|
64
|
+
BuildAntlrGrammar(
|
|
65
|
+
dm, # DoneManager instance
|
|
66
|
+
Path("Calculator.g4"),
|
|
67
|
+
Path("output_dir"),
|
|
68
|
+
)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
#### 3. Create Custom Visitor
|
|
72
|
+
Implement a visitor that converts ANTLR parse trees to Expression objects:
|
|
73
|
+
```python
|
|
74
|
+
from dbrownell_ParserLib import (
|
|
75
|
+
Expression,
|
|
76
|
+
InsignificantWhitespaceAntlrVisitorMixin,
|
|
77
|
+
TerminalExpression,
|
|
78
|
+
)
|
|
79
|
+
from dataclasses import dataclass
|
|
80
|
+
|
|
81
|
+
from CalculatorVisitor import CalculatorVisitor as GeneratedVisitor
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass(eq=False)
|
|
86
|
+
class BinaryExpression(Expression):
|
|
87
|
+
left: Expression
|
|
88
|
+
operator: TerminalExpression[str]
|
|
89
|
+
right: Expression
|
|
90
|
+
|
|
91
|
+
def _GenerateAcceptDetails(self):
|
|
92
|
+
yield "left", self.left
|
|
93
|
+
yield "operator", self.operator
|
|
94
|
+
yield "right", self.right
|
|
95
|
+
|
|
96
|
+
class CalculatorVisitor(InsignificantWhitespaceAntlrVisitorMixin, GeneratedVisitor):
|
|
97
|
+
|
|
98
|
+
def visitBinaryOp(self, ctx):
|
|
99
|
+
children = self.GetChildren(ctx)
|
|
100
|
+
self._stack.append(
|
|
101
|
+
BinaryExpression(
|
|
102
|
+
self.CreateRegion(ctx),
|
|
103
|
+
children[0],
|
|
104
|
+
TerminalExpression[str](self.CreateRegion(ctx.children[1]), ctx.children[1].getText()),
|
|
105
|
+
children[1],
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def visitNumber(self, ctx):
|
|
110
|
+
self._stack.append(
|
|
111
|
+
TerminalExpression(self.CreateRegion(ctx), int(ctx.INT().getText()))
|
|
112
|
+
)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
#### 4. Create Parser Instance
|
|
116
|
+
Use `CreateAntlrParser` to build a callable parser:
|
|
117
|
+
```python
|
|
118
|
+
from dbrownell_ParserLib import CreateAntlrParser
|
|
119
|
+
|
|
120
|
+
parser = CreateAntlrParser(
|
|
121
|
+
CalculatorLexer,
|
|
122
|
+
CalculatorParser,
|
|
123
|
+
CalculatorVisitor,
|
|
124
|
+
lambda p: p.expr(), # Entry point rule
|
|
125
|
+
)
|
|
126
|
+
```
|
|
127
|
+
from pathlib import Path
|
|
128
|
+
|
|
129
|
+
from dbrownell_ParserLib import Error
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
#### 5. Parse Files
|
|
134
|
+
Parse single files, multiple files, or entire workspaces:
|
|
135
|
+
```python
|
|
136
|
+
# Parse a single file
|
|
137
|
+
results = parser(dm, Path("input.txt"), None)
|
|
138
|
+
|
|
139
|
+
# Parse multiple files
|
|
140
|
+
results = parser(dm, [Path("file1.txt"), Path("file2.txt")], None)
|
|
141
|
+
|
|
142
|
+
# Check results
|
|
143
|
+
for filepath, result in results.items():
|
|
144
|
+
if isinstance(result, Error):
|
|
145
|
+
print(f"Parse error in {filepath}: {result}")
|
|
146
|
+
else:
|
|
147
|
+
# result is the visitor containing the parsed AST
|
|
148
|
+
ast = result._stack[0]
|
|
149
|
+
# Process the AST
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
from contextlib import contextmanager
|
|
153
|
+
|
|
154
|
+
#### 6. Traverse the AST
|
|
155
|
+
Use the visitor pattern to process your AST:
|
|
156
|
+
```python
|
|
157
|
+
@contextmanager
|
|
158
|
+
|
|
159
|
+
from dbrownell_ParserLib import ExpressionVisitorHelper, VisitResult
|
|
160
|
+
|
|
161
|
+
class EvaluatorVisitor(ExpressionVisitorHelper):
|
|
162
|
+
def OnBinaryExpression(self, expression):
|
|
163
|
+
@contextmanager
|
|
164
|
+
|
|
165
|
+
# Process binary expressions
|
|
166
|
+
yield VisitResult.Continue
|
|
167
|
+
|
|
168
|
+
# Process terminal values
|
|
169
|
+
yield VisitResult.Continue
|
|
170
|
+
|
|
171
|
+
ast.Accept(EvaluatorVisitor())
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
For detailed examples, see the test files in the repository, particularly the end-to-end tests that demonstrate complete parsing workflows.
|
|
32
175
|
|
|
33
176
|
<!-- Content below this delimiter will be copied to the generated README.md file. DO NOT REMOVE THIS COMMENT, as it will cause regeneration to fail. -->
|
|
34
177
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "dbrownell-parserlib"
|
|
3
|
-
version = "0.8.
|
|
3
|
+
version = "0.8.3"
|
|
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:
|
|
@@ -485,7 +485,7 @@ class _ErrorListener(antlr4.DiagnosticErrorListener):
|
|
|
485
485
|
return
|
|
486
486
|
|
|
487
487
|
location = Location(line, column + 1)
|
|
488
|
-
error = Error(msg, Region(self._source, location, location))
|
|
488
|
+
error = Error(f"Syntax error: {msg}", Region(self._source, location, location))
|
|
489
489
|
ex = AntlrParserException(error)
|
|
490
490
|
|
|
491
491
|
if e is None:
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""Various ANTLR visitor mixins that can be used to add functionality to the generated visitor classes."""
|
|
2
2
|
|
|
3
|
+
import re
|
|
4
|
+
|
|
3
5
|
from abc import ABC, abstractmethod
|
|
4
6
|
from typing import cast, override, TYPE_CHECKING
|
|
5
7
|
|
|
@@ -35,14 +37,36 @@ class AntlrVisitorMixinBase(ABC):
|
|
|
35
37
|
self._current_line: int = 0
|
|
36
38
|
|
|
37
39
|
# ----------------------------------------------------------------------
|
|
38
|
-
@abstractmethod
|
|
39
40
|
def CreateRegion(
|
|
40
41
|
self,
|
|
41
|
-
ctx: antlr4.ParserRuleContext,
|
|
42
|
+
ctx: antlr4.TerminalNode | antlr4.ParserRuleContext,
|
|
42
43
|
) -> Region:
|
|
43
44
|
"""Create a `Region` from the given context."""
|
|
44
45
|
|
|
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
|
|
46
70
|
|
|
47
71
|
# ----------------------------------------------------------------------
|
|
48
72
|
def GetChildren(self, ctx: antlr4.ParserRuleContext) -> list[object]:
|
|
@@ -64,49 +88,28 @@ class AntlrVisitorMixinBase(ABC):
|
|
|
64
88
|
self._current_line = end_line
|
|
65
89
|
self._on_progress_func(end_line)
|
|
66
90
|
|
|
91
|
+
# ----------------------------------------------------------------------
|
|
92
|
+
@abstractmethod
|
|
93
|
+
def _CreateRegionFromRule(self, ctx: antlr4.ParserRuleContext) -> Region: ...
|
|
94
|
+
|
|
67
95
|
|
|
68
96
|
# ----------------------------------------------------------------------
|
|
69
97
|
class InsignificantWhitespaceAntlrVisitorMixin(AntlrVisitorMixinBase):
|
|
70
98
|
"""Mixin that adds functionality used for grammars that ignore insignificant whitespace."""
|
|
71
99
|
|
|
72
100
|
# ----------------------------------------------------------------------
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
ctx: antlr4.ParserRuleContext | antlr4.TerminalNode,
|
|
77
|
-
) -> Region:
|
|
78
|
-
"""Create a `Region` from the given context."""
|
|
79
|
-
|
|
80
|
-
if isinstance(ctx, antlr4.ParserRuleContext):
|
|
81
|
-
start_token = ctx.start
|
|
82
|
-
stop_token = ctx.stop
|
|
83
|
-
|
|
84
|
-
if start_token is None or stop_token is None:
|
|
85
|
-
msg = "Context does not have start or stop token"
|
|
86
|
-
raise ValueError(msg)
|
|
101
|
+
def _CreateRegionFromRule(self, ctx: antlr4.ParserRuleContext) -> Region:
|
|
102
|
+
start_token = ctx.start
|
|
103
|
+
stop_token = ctx.stop
|
|
87
104
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
end_col = stop_token.column + 1
|
|
92
|
-
|
|
93
|
-
if start_line == end_line:
|
|
94
|
-
end_col += len(stop_token.text)
|
|
95
|
-
else:
|
|
96
|
-
end_col += 1
|
|
97
|
-
|
|
98
|
-
elif isinstance(ctx, antlr4.TerminalNode):
|
|
99
|
-
token = cast(antlr4.Token, ctx.symbol) # ty: ignore[unresolved-attribute]
|
|
100
|
-
|
|
101
|
-
assert token.column is not None
|
|
102
|
-
|
|
103
|
-
start_line = token.line
|
|
104
|
-
start_col = token.column + 1
|
|
105
|
-
end_line = token.line
|
|
106
|
-
end_col = token.column + 1 + len(token.text)
|
|
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)
|
|
107
108
|
|
|
108
|
-
|
|
109
|
-
|
|
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)
|
|
110
113
|
|
|
111
114
|
assert start_line is not None
|
|
112
115
|
assert end_line is not None
|
|
@@ -133,52 +136,34 @@ class SignificantWhitespaceAntlrVisitorMixin(AntlrVisitorMixinBase):
|
|
|
133
136
|
self._newline_token = newline_token
|
|
134
137
|
self._newline_token_string = newline_token_string
|
|
135
138
|
|
|
139
|
+
self._newline_indent_regex = re.compile(r"^\r?\n[ \t]*$")
|
|
140
|
+
|
|
136
141
|
# ----------------------------------------------------------------------
|
|
137
142
|
@override
|
|
138
|
-
def
|
|
143
|
+
def _CreateRegionFromRule(
|
|
139
144
|
self,
|
|
140
145
|
ctx: antlr4.ParserRuleContext,
|
|
141
146
|
) -> Region:
|
|
142
147
|
assert isinstance(ctx.start, antlr4.Token), ctx.start
|
|
143
148
|
assert isinstance(ctx.stop, antlr4.Token), ctx.stop
|
|
144
149
|
|
|
145
|
-
if ctx.stop.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if ctx.stop.line == ctx.start.line:
|
|
151
|
-
# This is the scenario where the statement is followed by a dedent followed by another
|
|
152
|
-
# statement. We don't want the range of this item to overlap with the range of the
|
|
153
|
-
# next item, so use the values as they are, even though it means that a statement
|
|
154
|
-
# that terminates with a newline will not have the newline here.
|
|
155
|
-
stop_line = ctx.stop.line
|
|
156
|
-
stop_col = ctx.stop.column
|
|
157
|
-
else:
|
|
158
|
-
stop_line = ctx.stop.line
|
|
159
|
-
stop_col = ctx.stop.column if ctx.stop.column == 0 else ctx.start.column
|
|
160
|
-
|
|
161
|
-
else:
|
|
162
|
-
stop_line = ctx.stop.line
|
|
163
|
-
|
|
164
|
-
content = ctx.stop.text
|
|
165
|
-
lines = content.split("\n")
|
|
166
|
-
|
|
167
|
-
if ctx.stop.type == self._newline_token:
|
|
168
|
-
assert content.startswith("\n"), content
|
|
169
|
-
assert len(lines) == 2, lines # noqa: PLR2004
|
|
170
|
-
|
|
171
|
-
stop_col = len(lines[-1])
|
|
172
|
-
|
|
173
|
-
if stop_line == ctx.stop.line:
|
|
174
|
-
stop_col += ctx.stop.column
|
|
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
|
+
)
|
|
175
155
|
|
|
176
|
-
stop_line
|
|
156
|
+
stop_line = ctx.start.line
|
|
157
|
+
stop_column = ctx.start.column + 1 + len(ctx.start.text)
|
|
177
158
|
|
|
178
|
-
|
|
159
|
+
return Region(
|
|
160
|
+
self.filename,
|
|
161
|
+
Location(ctx.start.line, ctx.start.column + 1),
|
|
162
|
+
Location(stop_line, stop_column),
|
|
163
|
+
)
|
|
179
164
|
|
|
180
165
|
return Region(
|
|
181
166
|
self.filename,
|
|
182
167
|
Location(ctx.start.line, ctx.start.column + 1),
|
|
183
|
-
Location(
|
|
168
|
+
Location(ctx.stop.line, ctx.stop.column + 1),
|
|
184
169
|
)
|
|
File without changes
|
{dbrownell_parserlib-0.8.1 → dbrownell_parserlib-0.8.3}/src/dbrownell_ParserLib/antlr/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dbrownell_parserlib-0.8.1 → dbrownell_parserlib-0.8.3}/src/dbrownell_ParserLib/expression.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|