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.
Files changed (29) hide show
  1. Typhon/Driver/translate.py +2 -1
  2. Typhon/Grammar/_typhon_parser.py +1638 -1649
  3. Typhon/Grammar/syntax_errors.py +11 -0
  4. Typhon/Grammar/typhon_ast.py +405 -55
  5. Typhon/Grammar/unparse_custom.py +25 -0
  6. Typhon/SourceMap/datatype.py +264 -264
  7. Typhon/Transform/const_member_to_final.py +1 -1
  8. Typhon/Transform/extended_patterns.py +139 -0
  9. Typhon/Transform/forbidden_statements.py +24 -0
  10. Typhon/Transform/if_while_let.py +122 -11
  11. Typhon/Transform/inline_statement_block_capture.py +22 -15
  12. Typhon/Transform/placeholder_to_function.py +0 -1
  13. Typhon/Transform/record_to_dataclass.py +22 -238
  14. Typhon/Transform/scope_check_rename.py +65 -11
  15. Typhon/Transform/transform.py +16 -12
  16. Typhon/Transform/type_abbrev_desugar.py +1 -1
  17. Typhon/Transform/utils/__init__.py +0 -0
  18. Typhon/Transform/utils/imports.py +48 -0
  19. Typhon/Transform/{utils.py → utils/jump_away.py} +2 -38
  20. Typhon/Transform/utils/make_class.py +140 -0
  21. Typhon/Typing/pyright.py +143 -144
  22. Typhon/Typing/result_diagnostic.py +1 -1
  23. {typhon_language-0.1.2.dist-info → typhon_language-0.1.3.dist-info}/METADATA +7 -2
  24. typhon_language-0.1.3.dist-info/RECORD +53 -0
  25. typhon_language-0.1.2.dist-info/RECORD +0 -48
  26. {typhon_language-0.1.2.dist-info → typhon_language-0.1.3.dist-info}/WHEEL +0 -0
  27. {typhon_language-0.1.2.dist-info → typhon_language-0.1.3.dist-info}/entry_points.txt +0 -0
  28. {typhon_language-0.1.2.dist-info → typhon_language-0.1.3.dist-info}/licenses/LICENSE +0 -0
  29. {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)
@@ -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
- 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
- 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
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):