Typhon-Language 0.1.2__py3-none-any.whl → 0.1.3__py3-none-any.whl
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.
- Typhon/Driver/translate.py +2 -1
- Typhon/Grammar/_typhon_parser.py +1638 -1649
- Typhon/Grammar/syntax_errors.py +11 -0
- Typhon/Grammar/typhon_ast.py +405 -55
- Typhon/Grammar/unparse_custom.py +25 -0
- Typhon/SourceMap/datatype.py +264 -264
- Typhon/Transform/const_member_to_final.py +1 -1
- Typhon/Transform/extended_patterns.py +139 -0
- Typhon/Transform/forbidden_statements.py +24 -0
- Typhon/Transform/if_while_let.py +122 -11
- Typhon/Transform/inline_statement_block_capture.py +22 -15
- Typhon/Transform/placeholder_to_function.py +0 -1
- Typhon/Transform/record_to_dataclass.py +22 -238
- Typhon/Transform/scope_check_rename.py +65 -11
- Typhon/Transform/transform.py +16 -12
- Typhon/Transform/type_abbrev_desugar.py +1 -1
- Typhon/Transform/utils/__init__.py +0 -0
- Typhon/Transform/utils/imports.py +48 -0
- Typhon/Transform/{utils.py → utils/jump_away.py} +2 -38
- Typhon/Transform/utils/make_class.py +140 -0
- Typhon/Typing/pyright.py +143 -144
- Typhon/Typing/result_diagnostic.py +1 -1
- {typhon_language-0.1.2.dist-info → typhon_language-0.1.3.dist-info}/METADATA +7 -2
- typhon_language-0.1.3.dist-info/RECORD +53 -0
- typhon_language-0.1.2.dist-info/RECORD +0 -48
- {typhon_language-0.1.2.dist-info → typhon_language-0.1.3.dist-info}/WHEEL +0 -0
- {typhon_language-0.1.2.dist-info → typhon_language-0.1.3.dist-info}/entry_points.txt +0 -0
- {typhon_language-0.1.2.dist-info → typhon_language-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {typhon_language-0.1.2.dist-info → typhon_language-0.1.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# type: ignore[all]
|
|
2
|
+
# TODO: Never forget implementation here is temporal hack.
|
|
3
|
+
import ast
|
|
4
|
+
from .typhon_ast import get_type_ignore_comment
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Hack the ast._Unparser to create our CustomUnparser.
|
|
8
|
+
# DO make new class from scratch when we need to change more things.
|
|
9
|
+
class CustomUnparser(ast._Unparser):
|
|
10
|
+
def __init__(self):
|
|
11
|
+
super().__init__()
|
|
12
|
+
|
|
13
|
+
def visit_match_case(self, node):
|
|
14
|
+
self.fill("case ")
|
|
15
|
+
self.traverse(node.pattern)
|
|
16
|
+
if node.guard:
|
|
17
|
+
self.write(" if ")
|
|
18
|
+
self.traverse(node.guard)
|
|
19
|
+
with self.block(extra=get_type_ignore_comment(node)):
|
|
20
|
+
self.traverse(node.body)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def unparse_custom(node: ast.AST) -> str:
|
|
24
|
+
unparser = CustomUnparser()
|
|
25
|
+
return unparser.visit(node)
|
Typhon/SourceMap/datatype.py
CHANGED
|
@@ -1,264 +1,264 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from ..Grammar.typhon_ast import PosAttributes, PosRange, get_pos_attributes_if_exists
|
|
3
|
-
from ..Driver.debugging import debug_verbose_print
|
|
4
|
-
from intervaltree import IntervalTree, Interval
|
|
5
|
-
from typing import Iterable
|
|
6
|
-
import ast
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@dataclass(frozen=True, unsafe_hash=True, order=True)
|
|
10
|
-
class Pos:
|
|
11
|
-
line: int
|
|
12
|
-
column: int
|
|
13
|
-
|
|
14
|
-
@staticmethod
|
|
15
|
-
def from_start_pos_attributes(attr: PosAttributes) -> "Pos":
|
|
16
|
-
return Pos(line=attr["lineno"], column=attr["col_offset"])
|
|
17
|
-
|
|
18
|
-
@staticmethod
|
|
19
|
-
def from_end_pos_attributes(attr: PosAttributes) -> "Pos | None":
|
|
20
|
-
if attr["end_lineno"] is None or attr["end_col_offset"] is None:
|
|
21
|
-
return None
|
|
22
|
-
return Pos(line=attr["end_lineno"], column=attr["end_col_offset"])
|
|
23
|
-
|
|
24
|
-
def col_back(self) -> "Pos":
|
|
25
|
-
if self.column == 0:
|
|
26
|
-
return self
|
|
27
|
-
else:
|
|
28
|
-
return Pos(line=self.line, column=self.column - 1)
|
|
29
|
-
|
|
30
|
-
def col_forward(self) -> "Pos":
|
|
31
|
-
return Pos(line=self.line, column=self.column + 1)
|
|
32
|
-
|
|
33
|
-
def calc_offset(self, other: "Pos") -> "Pos":
|
|
34
|
-
line_offset = other.line - self.line
|
|
35
|
-
if line_offset == 0:
|
|
36
|
-
column_offset = other.column - self.column
|
|
37
|
-
else:
|
|
38
|
-
column_offset = other.column
|
|
39
|
-
return Pos(line=line_offset, column=column_offset)
|
|
40
|
-
|
|
41
|
-
def apply_offset(self, offset: "Range") -> "Range":
|
|
42
|
-
# offset is (start_offset, offset_from_start_to_end)
|
|
43
|
-
start_column_offset = 0
|
|
44
|
-
if offset.start.line == 0:
|
|
45
|
-
start_column_offset = self.column + offset.start.column
|
|
46
|
-
new_start_column = start_column_offset
|
|
47
|
-
end_column_start = 0
|
|
48
|
-
if offset.end.line == 0:
|
|
49
|
-
end_column_start = new_start_column
|
|
50
|
-
debug_verbose_print(
|
|
51
|
-
f"Applying offset: pos={self}, offset={offset} -> new_start_column={
|
|
52
|
-
new_start_column
|
|
53
|
-
}, end_column_start={end_column_start} = {
|
|
54
|
-
Range(
|
|
55
|
-
Pos(self.line + offset.start.line, new_start_column),
|
|
56
|
-
Pos(
|
|
57
|
-
self.line + offset.start.line + offset.end.line,
|
|
58
|
-
end_column_start + offset.end.column,
|
|
59
|
-
),
|
|
60
|
-
)
|
|
61
|
-
}"
|
|
62
|
-
)
|
|
63
|
-
return Range(
|
|
64
|
-
Pos(self.line + offset.start.line, new_start_column),
|
|
65
|
-
Pos(
|
|
66
|
-
self.line + offset.start.line + offset.end.line,
|
|
67
|
-
end_column_start + offset.end.column,
|
|
68
|
-
),
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
# Common central datatype for source range
|
|
73
|
-
@dataclass(frozen=True, unsafe_hash=True, order=True)
|
|
74
|
-
class Range:
|
|
75
|
-
start: Pos
|
|
76
|
-
end: Pos
|
|
77
|
-
|
|
78
|
-
def contains(self, pos: Pos) -> bool:
|
|
79
|
-
return self.start <= pos < self.end
|
|
80
|
-
|
|
81
|
-
def includes(self, other: "Range") -> bool:
|
|
82
|
-
return self.start <= other.start and other.end <= self.end
|
|
83
|
-
|
|
84
|
-
def calc_offset(self, other: "Range") -> "Range":
|
|
85
|
-
offset = self.start.calc_offset(other.start)
|
|
86
|
-
end_offset = other.start.calc_offset(other.end)
|
|
87
|
-
return Range(offset, end_offset)
|
|
88
|
-
|
|
89
|
-
@staticmethod
|
|
90
|
-
def from_positions(
|
|
91
|
-
start_line: int, start_column: int, end_line: int, end_column: int
|
|
92
|
-
) -> "Range":
|
|
93
|
-
return Range(
|
|
94
|
-
start=Pos(line=start_line, column=start_column),
|
|
95
|
-
end=Pos(line=end_line, column=end_column),
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
@staticmethod
|
|
99
|
-
def from_pos_range(pos_range: PosRange) -> "Range":
|
|
100
|
-
return Range(
|
|
101
|
-
start=Pos(line=pos_range["lineno"], column=pos_range["col_offset"]),
|
|
102
|
-
end=Pos(line=pos_range["end_lineno"], column=pos_range["end_col_offset"]),
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
@staticmethod
|
|
106
|
-
def from_pos_attr(attr: PosAttributes) -> "Range | None":
|
|
107
|
-
if attr["end_lineno"] is None or attr["end_col_offset"] is None:
|
|
108
|
-
return None
|
|
109
|
-
return Range(
|
|
110
|
-
start=Pos(line=attr["lineno"], column=attr["col_offset"]),
|
|
111
|
-
end=Pos(line=attr["end_lineno"], column=attr["end_col_offset"]),
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
@staticmethod
|
|
115
|
-
def from_pos_attr_may_not_end(attr: PosAttributes) -> "Range":
|
|
116
|
-
start_line = attr["lineno"]
|
|
117
|
-
start_column = attr["col_offset"]
|
|
118
|
-
end_line = (
|
|
119
|
-
attr["end_lineno"] if attr["end_lineno"] is not None else attr["lineno"]
|
|
120
|
-
)
|
|
121
|
-
end_column = (
|
|
122
|
-
attr["end_col_offset"]
|
|
123
|
-
if attr["end_col_offset"] is not None
|
|
124
|
-
else attr["col_offset"] + 1
|
|
125
|
-
)
|
|
126
|
-
if end_line == start_line and start_column == end_column:
|
|
127
|
-
end_column += 1 # Ensure non-zero length
|
|
128
|
-
return Range(
|
|
129
|
-
start=Pos(line=start_line, column=start_column),
|
|
130
|
-
end=Pos(line=end_line, column=end_column),
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
@staticmethod
|
|
134
|
-
def from_interval(interval: Interval) -> "Range":
|
|
135
|
-
return Range(start=interval.begin, end=interval.end) # type: ignore[misc]
|
|
136
|
-
|
|
137
|
-
@staticmethod
|
|
138
|
-
def from_ast_node(node: ast.AST) -> "Range | None":
|
|
139
|
-
attr = get_pos_attributes_if_exists(node)
|
|
140
|
-
if attr is None:
|
|
141
|
-
return None
|
|
142
|
-
return Range.from_pos_attr(attr)
|
|
143
|
-
|
|
144
|
-
@staticmethod
|
|
145
|
-
def from_syntax_error(e: SyntaxError) -> "Range":
|
|
146
|
-
start_line = e.lineno or 0
|
|
147
|
-
start_column = e.offset or 0
|
|
148
|
-
end_line = e.end_lineno or start_line
|
|
149
|
-
end_column = e.end_offset or (start_column + 1)
|
|
150
|
-
return Range(
|
|
151
|
-
start=Pos(line=start_line, column=start_column),
|
|
152
|
-
end=Pos(line=end_line, column=end_column),
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
@staticmethod
|
|
156
|
-
def merge_ranges(ranges: Iterable["Range"]) -> "Range | None":
|
|
157
|
-
if not ranges:
|
|
158
|
-
return None
|
|
159
|
-
|
|
160
|
-
ranges = list(ranges)
|
|
161
|
-
start = min(r.start for r in ranges)
|
|
162
|
-
end = max([r.end for r in ranges] + [start])
|
|
163
|
-
|
|
164
|
-
return Range(start=start, end=end)
|
|
165
|
-
|
|
166
|
-
def of_string(self, source: str) -> str:
|
|
167
|
-
lines = source.splitlines()
|
|
168
|
-
return self.of_lines(lines)
|
|
169
|
-
|
|
170
|
-
def of_lines(self, lines: list[str]) -> str:
|
|
171
|
-
# Range is 1 based. Convert to 0 based for string slicing.
|
|
172
|
-
start_line = self.start.line - 1
|
|
173
|
-
end_line = self.end.line - 1
|
|
174
|
-
start_column = self.start.column - 1
|
|
175
|
-
end_column = self.end.column - 1
|
|
176
|
-
|
|
177
|
-
def get_in_line(line: str, start_col: int, end_col: int) -> str:
|
|
178
|
-
return line[start_col : min(end_col, len(line))]
|
|
179
|
-
|
|
180
|
-
if start_line >= len(lines) or end_line >= len(lines):
|
|
181
|
-
return ""
|
|
182
|
-
if start_line == end_line:
|
|
183
|
-
the_line = lines[start_line]
|
|
184
|
-
return get_in_line(the_line, start_column, end_column)
|
|
185
|
-
else:
|
|
186
|
-
result_lines: list[str] = []
|
|
187
|
-
result_lines.append(lines[start_line][start_column:])
|
|
188
|
-
for line_num in range(start_line, end_line):
|
|
189
|
-
result_lines.append(lines[line_num])
|
|
190
|
-
result_lines.append(get_in_line(lines[end_line], 0, end_column))
|
|
191
|
-
return "\n".join(result_lines)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
type RangeInterval[T] = tuple[Range, T]
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
# Wrapper around IntervalTree for Range keys
|
|
198
|
-
class RangeIntervalTree[T]:
|
|
199
|
-
def __init__(self):
|
|
200
|
-
self.interval_tree = IntervalTree()
|
|
201
|
-
|
|
202
|
-
def add(self, range: Range, data: T):
|
|
203
|
-
self.interval_tree.addi( # type: ignore[misc]
|
|
204
|
-
range.start,
|
|
205
|
-
range.end,
|
|
206
|
-
data,
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
def at(self, pos: Pos) -> list[RangeInterval[T]]:
|
|
210
|
-
intervals = self.interval_tree.at(pos) # type: ignore[misc]
|
|
211
|
-
return [
|
|
212
|
-
(Range.from_interval(interval), interval.data) # type: ignore[misc]
|
|
213
|
-
for interval in intervals # type: ignore[misc]
|
|
214
|
-
]
|
|
215
|
-
|
|
216
|
-
def overlap(self, range: Range) -> list[RangeInterval[T]]:
|
|
217
|
-
intervals = self.interval_tree.overlap(range.start, range.end) # type: ignore[misc]
|
|
218
|
-
return [
|
|
219
|
-
(Range.from_interval(interval), interval.data) # type: ignore[misc]
|
|
220
|
-
for interval in intervals # type: ignore[misc]
|
|
221
|
-
]
|
|
222
|
-
|
|
223
|
-
def envelop(self, range: Range) -> list[RangeInterval[T]]:
|
|
224
|
-
intervals = self.interval_tree.envelop(range.start, range.end) # type: ignore[misc]
|
|
225
|
-
return [
|
|
226
|
-
(Range.from_interval(interval), interval.data) # type: ignore[misc]
|
|
227
|
-
for interval in intervals # type: ignore[misc]
|
|
228
|
-
]
|
|
229
|
-
|
|
230
|
-
def _container_intervals(self, range: Range) -> list[Interval]:
|
|
231
|
-
# TODO: How to write this even IntervalTree doesn't have containers method?
|
|
232
|
-
start_intervals = self.interval_tree.at(range.start) # type: ignore[misc]
|
|
233
|
-
if not start_intervals:
|
|
234
|
-
return []
|
|
235
|
-
return [
|
|
236
|
-
interval # type: ignore[misc]
|
|
237
|
-
for interval in start_intervals # type: ignore[misc]
|
|
238
|
-
if (interval.contains_point(range.end) or interval.end == range.end) # type: ignore[misc]
|
|
239
|
-
]
|
|
240
|
-
|
|
241
|
-
def containers(self, range: Range) -> list[RangeInterval[T]]:
|
|
242
|
-
containers = self._container_intervals(range)
|
|
243
|
-
result: list[RangeInterval[T]] = []
|
|
244
|
-
for interval in containers:
|
|
245
|
-
result.append((Range.from_interval(interval), interval.data)) # type: ignore[misc]
|
|
246
|
-
return result
|
|
247
|
-
|
|
248
|
-
def minimal_containers(self, range: Range) -> list[RangeInterval[T]]:
|
|
249
|
-
containers = self._container_intervals(range)
|
|
250
|
-
if not containers:
|
|
251
|
-
return []
|
|
252
|
-
result: list[RangeInterval[T]] = []
|
|
253
|
-
interval: Interval
|
|
254
|
-
for i, interval in enumerate(containers):
|
|
255
|
-
is_minimal = True
|
|
256
|
-
for j, other_interval in enumerate(containers):
|
|
257
|
-
if (
|
|
258
|
-
i != j and interval.contains_interval(other_interval) # type: ignore[misc]
|
|
259
|
-
):
|
|
260
|
-
is_minimal = False
|
|
261
|
-
break
|
|
262
|
-
if is_minimal:
|
|
263
|
-
result.append((Range.from_interval(interval), interval.data)) # type: ignore[misc]
|
|
264
|
-
return result
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from ..Grammar.typhon_ast import PosAttributes, PosRange, get_pos_attributes_if_exists
|
|
3
|
+
from ..Driver.debugging import debug_verbose_print
|
|
4
|
+
from intervaltree import IntervalTree, Interval
|
|
5
|
+
from typing import Iterable
|
|
6
|
+
import ast
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True, unsafe_hash=True, order=True)
|
|
10
|
+
class Pos:
|
|
11
|
+
line: int
|
|
12
|
+
column: int
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def from_start_pos_attributes(attr: PosAttributes) -> "Pos":
|
|
16
|
+
return Pos(line=attr["lineno"], column=attr["col_offset"])
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def from_end_pos_attributes(attr: PosAttributes) -> "Pos | None":
|
|
20
|
+
if attr["end_lineno"] is None or attr["end_col_offset"] is None:
|
|
21
|
+
return None
|
|
22
|
+
return Pos(line=attr["end_lineno"], column=attr["end_col_offset"])
|
|
23
|
+
|
|
24
|
+
def col_back(self) -> "Pos":
|
|
25
|
+
if self.column == 0:
|
|
26
|
+
return self
|
|
27
|
+
else:
|
|
28
|
+
return Pos(line=self.line, column=self.column - 1)
|
|
29
|
+
|
|
30
|
+
def col_forward(self) -> "Pos":
|
|
31
|
+
return Pos(line=self.line, column=self.column + 1)
|
|
32
|
+
|
|
33
|
+
def calc_offset(self, other: "Pos") -> "Pos":
|
|
34
|
+
line_offset = other.line - self.line
|
|
35
|
+
if line_offset == 0:
|
|
36
|
+
column_offset = other.column - self.column
|
|
37
|
+
else:
|
|
38
|
+
column_offset = other.column
|
|
39
|
+
return Pos(line=line_offset, column=column_offset)
|
|
40
|
+
|
|
41
|
+
def apply_offset(self, offset: "Range") -> "Range":
|
|
42
|
+
# offset is (start_offset, offset_from_start_to_end)
|
|
43
|
+
start_column_offset = 0
|
|
44
|
+
if offset.start.line == 0:
|
|
45
|
+
start_column_offset = self.column + offset.start.column
|
|
46
|
+
new_start_column = start_column_offset
|
|
47
|
+
end_column_start = 0
|
|
48
|
+
if offset.end.line == 0:
|
|
49
|
+
end_column_start = new_start_column
|
|
50
|
+
debug_verbose_print(
|
|
51
|
+
f"Applying offset: pos={self}, offset={offset} -> new_start_column={
|
|
52
|
+
new_start_column
|
|
53
|
+
}, end_column_start={end_column_start} = {
|
|
54
|
+
Range(
|
|
55
|
+
Pos(self.line + offset.start.line, new_start_column),
|
|
56
|
+
Pos(
|
|
57
|
+
self.line + offset.start.line + offset.end.line,
|
|
58
|
+
end_column_start + offset.end.column,
|
|
59
|
+
),
|
|
60
|
+
)
|
|
61
|
+
}"
|
|
62
|
+
)
|
|
63
|
+
return Range(
|
|
64
|
+
Pos(self.line + offset.start.line, new_start_column),
|
|
65
|
+
Pos(
|
|
66
|
+
self.line + offset.start.line + offset.end.line,
|
|
67
|
+
end_column_start + offset.end.column,
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# Common central datatype for source range
|
|
73
|
+
@dataclass(frozen=True, unsafe_hash=True, order=True)
|
|
74
|
+
class Range:
|
|
75
|
+
start: Pos
|
|
76
|
+
end: Pos
|
|
77
|
+
|
|
78
|
+
def contains(self, pos: Pos) -> bool:
|
|
79
|
+
return self.start <= pos < self.end
|
|
80
|
+
|
|
81
|
+
def includes(self, other: "Range") -> bool:
|
|
82
|
+
return self.start <= other.start and other.end <= self.end
|
|
83
|
+
|
|
84
|
+
def calc_offset(self, other: "Range") -> "Range":
|
|
85
|
+
offset = self.start.calc_offset(other.start)
|
|
86
|
+
end_offset = other.start.calc_offset(other.end)
|
|
87
|
+
return Range(offset, end_offset)
|
|
88
|
+
|
|
89
|
+
@staticmethod
|
|
90
|
+
def from_positions(
|
|
91
|
+
start_line: int, start_column: int, end_line: int, end_column: int
|
|
92
|
+
) -> "Range":
|
|
93
|
+
return Range(
|
|
94
|
+
start=Pos(line=start_line, column=start_column),
|
|
95
|
+
end=Pos(line=end_line, column=end_column),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def from_pos_range(pos_range: PosRange) -> "Range":
|
|
100
|
+
return Range(
|
|
101
|
+
start=Pos(line=pos_range["lineno"], column=pos_range["col_offset"]),
|
|
102
|
+
end=Pos(line=pos_range["end_lineno"], column=pos_range["end_col_offset"]),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def from_pos_attr(attr: PosAttributes) -> "Range | None":
|
|
107
|
+
if attr["end_lineno"] is None or attr["end_col_offset"] is None:
|
|
108
|
+
return None
|
|
109
|
+
return Range(
|
|
110
|
+
start=Pos(line=attr["lineno"], column=attr["col_offset"]),
|
|
111
|
+
end=Pos(line=attr["end_lineno"], column=attr["end_col_offset"]),
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
@staticmethod
|
|
115
|
+
def from_pos_attr_may_not_end(attr: PosAttributes) -> "Range":
|
|
116
|
+
start_line = attr["lineno"]
|
|
117
|
+
start_column = attr["col_offset"]
|
|
118
|
+
end_line = (
|
|
119
|
+
attr["end_lineno"] if attr["end_lineno"] is not None else attr["lineno"]
|
|
120
|
+
)
|
|
121
|
+
end_column = (
|
|
122
|
+
attr["end_col_offset"]
|
|
123
|
+
if attr["end_col_offset"] is not None
|
|
124
|
+
else attr["col_offset"] + 1
|
|
125
|
+
)
|
|
126
|
+
if end_line == start_line and start_column == end_column:
|
|
127
|
+
end_column += 1 # Ensure non-zero length
|
|
128
|
+
return Range(
|
|
129
|
+
start=Pos(line=start_line, column=start_column),
|
|
130
|
+
end=Pos(line=end_line, column=end_column),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
@staticmethod
|
|
134
|
+
def from_interval(interval: Interval) -> "Range":
|
|
135
|
+
return Range(start=interval.begin, end=interval.end) # type: ignore[misc]
|
|
136
|
+
|
|
137
|
+
@staticmethod
|
|
138
|
+
def from_ast_node(node: ast.AST) -> "Range | None":
|
|
139
|
+
attr = get_pos_attributes_if_exists(node)
|
|
140
|
+
if attr is None:
|
|
141
|
+
return None
|
|
142
|
+
return Range.from_pos_attr(attr)
|
|
143
|
+
|
|
144
|
+
@staticmethod
|
|
145
|
+
def from_syntax_error(e: SyntaxError) -> "Range":
|
|
146
|
+
start_line = e.lineno or 0
|
|
147
|
+
start_column = e.offset or 0
|
|
148
|
+
end_line = e.end_lineno or start_line
|
|
149
|
+
end_column = e.end_offset or (start_column + 1)
|
|
150
|
+
return Range(
|
|
151
|
+
start=Pos(line=start_line, column=start_column),
|
|
152
|
+
end=Pos(line=end_line, column=end_column),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def merge_ranges(ranges: Iterable["Range"]) -> "Range | None":
|
|
157
|
+
if not ranges:
|
|
158
|
+
return None
|
|
159
|
+
debug_verbose_print("Merging ranges: ", ranges)
|
|
160
|
+
ranges = list(ranges)
|
|
161
|
+
start = min(r.start for r in ranges)
|
|
162
|
+
end = max([r.end for r in ranges] + [start])
|
|
163
|
+
debug_verbose_print(f"Merged range: start={start}, end={end}")
|
|
164
|
+
return Range(start=start, end=end)
|
|
165
|
+
|
|
166
|
+
def of_string(self, source: str) -> str:
|
|
167
|
+
lines = source.splitlines()
|
|
168
|
+
return self.of_lines(lines)
|
|
169
|
+
|
|
170
|
+
def of_lines(self, lines: list[str]) -> str:
|
|
171
|
+
# Range is 1 based. Convert to 0 based for string slicing.
|
|
172
|
+
start_line = self.start.line - 1
|
|
173
|
+
end_line = self.end.line - 1
|
|
174
|
+
start_column = self.start.column - 1
|
|
175
|
+
end_column = self.end.column - 1
|
|
176
|
+
|
|
177
|
+
def get_in_line(line: str, start_col: int, end_col: int) -> str:
|
|
178
|
+
return line[start_col : min(end_col, len(line))]
|
|
179
|
+
|
|
180
|
+
if start_line >= len(lines) or end_line >= len(lines):
|
|
181
|
+
return ""
|
|
182
|
+
if start_line == end_line:
|
|
183
|
+
the_line = lines[start_line]
|
|
184
|
+
return get_in_line(the_line, start_column, end_column)
|
|
185
|
+
else:
|
|
186
|
+
result_lines: list[str] = []
|
|
187
|
+
result_lines.append(lines[start_line][start_column:])
|
|
188
|
+
for line_num in range(start_line, end_line):
|
|
189
|
+
result_lines.append(lines[line_num])
|
|
190
|
+
result_lines.append(get_in_line(lines[end_line], 0, end_column))
|
|
191
|
+
return "\n".join(result_lines)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
type RangeInterval[T] = tuple[Range, T]
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# Wrapper around IntervalTree for Range keys
|
|
198
|
+
class RangeIntervalTree[T]:
|
|
199
|
+
def __init__(self):
|
|
200
|
+
self.interval_tree = IntervalTree()
|
|
201
|
+
|
|
202
|
+
def add(self, range: Range, data: T):
|
|
203
|
+
self.interval_tree.addi( # type: ignore[misc]
|
|
204
|
+
range.start,
|
|
205
|
+
range.end,
|
|
206
|
+
data,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
def at(self, pos: Pos) -> list[RangeInterval[T]]:
|
|
210
|
+
intervals = self.interval_tree.at(pos) # type: ignore[misc]
|
|
211
|
+
return [
|
|
212
|
+
(Range.from_interval(interval), interval.data) # type: ignore[misc]
|
|
213
|
+
for interval in intervals # type: ignore[misc]
|
|
214
|
+
]
|
|
215
|
+
|
|
216
|
+
def overlap(self, range: Range) -> list[RangeInterval[T]]:
|
|
217
|
+
intervals = self.interval_tree.overlap(range.start, range.end) # type: ignore[misc]
|
|
218
|
+
return [
|
|
219
|
+
(Range.from_interval(interval), interval.data) # type: ignore[misc]
|
|
220
|
+
for interval in intervals # type: ignore[misc]
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
def envelop(self, range: Range) -> list[RangeInterval[T]]:
|
|
224
|
+
intervals = self.interval_tree.envelop(range.start, range.end) # type: ignore[misc]
|
|
225
|
+
return [
|
|
226
|
+
(Range.from_interval(interval), interval.data) # type: ignore[misc]
|
|
227
|
+
for interval in intervals # type: ignore[misc]
|
|
228
|
+
]
|
|
229
|
+
|
|
230
|
+
def _container_intervals(self, range: Range) -> list[Interval]:
|
|
231
|
+
# TODO: How to write this even IntervalTree doesn't have containers method?
|
|
232
|
+
start_intervals = self.interval_tree.at(range.start) # type: ignore[misc]
|
|
233
|
+
if not start_intervals:
|
|
234
|
+
return []
|
|
235
|
+
return [
|
|
236
|
+
interval # type: ignore[misc]
|
|
237
|
+
for interval in start_intervals # type: ignore[misc]
|
|
238
|
+
if (interval.contains_point(range.end) or interval.end == range.end) # type: ignore[misc]
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
def containers(self, range: Range) -> list[RangeInterval[T]]:
|
|
242
|
+
containers = self._container_intervals(range)
|
|
243
|
+
result: list[RangeInterval[T]] = []
|
|
244
|
+
for interval in containers:
|
|
245
|
+
result.append((Range.from_interval(interval), interval.data)) # type: ignore[misc]
|
|
246
|
+
return result
|
|
247
|
+
|
|
248
|
+
def minimal_containers(self, range: Range) -> list[RangeInterval[T]]:
|
|
249
|
+
containers = self._container_intervals(range)
|
|
250
|
+
if not containers:
|
|
251
|
+
return []
|
|
252
|
+
result: list[RangeInterval[T]] = []
|
|
253
|
+
interval: Interval
|
|
254
|
+
for i, interval in enumerate(containers):
|
|
255
|
+
is_minimal = True
|
|
256
|
+
for j, other_interval in enumerate(containers):
|
|
257
|
+
if (
|
|
258
|
+
i != j and interval.contains_interval(other_interval) # type: ignore[misc]
|
|
259
|
+
):
|
|
260
|
+
is_minimal = False
|
|
261
|
+
break
|
|
262
|
+
if is_minimal:
|
|
263
|
+
result.append((Range.from_interval(interval), interval.data)) # type: ignore[misc]
|
|
264
|
+
return result
|
|
@@ -6,7 +6,7 @@ from ..Grammar.typhon_ast import (
|
|
|
6
6
|
)
|
|
7
7
|
from .visitor import TyphonASTTransformer, flat_append
|
|
8
8
|
from .name_generator import get_final_name
|
|
9
|
-
from .utils import add_import_for_final
|
|
9
|
+
from .utils.imports import add_import_for_final
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class ConstMemberToFinal(TyphonASTTransformer):
|