Typhon-Language 0.1.2__py3-none-any.whl → 0.1.4__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/configs.py +14 -0
- Typhon/Driver/debugging.py +148 -5
- Typhon/Driver/diagnostic.py +4 -3
- Typhon/Driver/language_server.py +25 -0
- Typhon/Driver/run.py +1 -1
- Typhon/Driver/translate.py +16 -11
- Typhon/Driver/utils.py +39 -1
- Typhon/Grammar/_typhon_parser.py +2920 -2718
- Typhon/Grammar/parser.py +80 -53
- Typhon/Grammar/parser_helper.py +68 -87
- Typhon/Grammar/syntax_errors.py +41 -20
- Typhon/Grammar/token_factory_custom.py +541 -485
- Typhon/Grammar/tokenizer_custom.py +52 -0
- Typhon/Grammar/typhon_ast.py +754 -76
- Typhon/Grammar/typhon_ast_error.py +438 -0
- Typhon/Grammar/unparse_custom.py +25 -0
- Typhon/LanguageServer/__init__.py +3 -0
- Typhon/LanguageServer/client/__init__.py +42 -0
- Typhon/LanguageServer/client/pyrefly.py +115 -0
- Typhon/LanguageServer/client/pyright.py +173 -0
- Typhon/LanguageServer/semantic_tokens.py +446 -0
- Typhon/LanguageServer/server.py +376 -0
- Typhon/LanguageServer/utils.py +65 -0
- Typhon/SourceMap/ast_match_based_map.py +199 -152
- Typhon/SourceMap/ast_matching.py +102 -87
- Typhon/SourceMap/datatype.py +275 -264
- Typhon/SourceMap/defined_name_retrieve.py +145 -0
- Typhon/Transform/comprehension_to_function.py +2 -5
- Typhon/Transform/const_member_to_final.py +12 -7
- Typhon/Transform/extended_patterns.py +139 -0
- Typhon/Transform/forbidden_statements.py +25 -0
- Typhon/Transform/if_while_let.py +122 -11
- Typhon/Transform/inline_statement_block_capture.py +22 -15
- Typhon/Transform/optional_operators_to_checked.py +14 -6
- Typhon/Transform/placeholder_to_function.py +0 -1
- Typhon/Transform/record_to_dataclass.py +22 -238
- Typhon/Transform/scope_check_rename.py +109 -29
- Typhon/Transform/transform.py +16 -12
- Typhon/Transform/type_abbrev_desugar.py +11 -15
- Typhon/Transform/type_annotation_check_expand.py +2 -2
- Typhon/Transform/utils/__init__.py +0 -0
- Typhon/Transform/utils/imports.py +83 -0
- Typhon/Transform/{utils.py → utils/jump_away.py} +2 -38
- Typhon/Transform/utils/make_class.py +135 -0
- Typhon/Transform/visitor.py +25 -0
- Typhon/Typing/pyrefly.py +145 -0
- Typhon/Typing/pyright.py +141 -144
- Typhon/Typing/result_diagnostic.py +1 -1
- Typhon/__main__.py +15 -1
- {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/METADATA +13 -6
- typhon_language-0.1.4.dist-info/RECORD +65 -0
- {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/WHEEL +1 -1
- typhon_language-0.1.4.dist-info/licenses/LICENSE +201 -0
- typhon_language-0.1.2.dist-info/RECORD +0 -48
- typhon_language-0.1.2.dist-info/licenses/LICENSE +0 -21
- {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/entry_points.txt +0 -0
- {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
|
|
3
|
+
from ..Driver.debugging import debug_verbose_print
|
|
4
|
+
from ..Grammar.typhon_ast import (
|
|
5
|
+
set_defined_name,
|
|
6
|
+
get_pos_attributes,
|
|
7
|
+
set_import_from_names,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Assume the base source code is in canonical form by ast.unparse
|
|
12
|
+
class _DefinedNameRetriever(ast.NodeVisitor):
|
|
13
|
+
def __init__(self, unparsed_source_code: str):
|
|
14
|
+
self.unparsed_source_code = unparsed_source_code
|
|
15
|
+
|
|
16
|
+
def _visit_defines(
|
|
17
|
+
self,
|
|
18
|
+
node: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef,
|
|
19
|
+
column_offset: int,
|
|
20
|
+
):
|
|
21
|
+
pos = get_pos_attributes(node)
|
|
22
|
+
start_line = pos["lineno"] # decorator is not included in the original position
|
|
23
|
+
start_col = pos["col_offset"] + column_offset
|
|
24
|
+
debug_verbose_print(
|
|
25
|
+
f'Retrieving defined name "{node.name}" for node: {ast.dump(node)} at line {start_line}, col {start_col}'
|
|
26
|
+
)
|
|
27
|
+
name = ast.Name(
|
|
28
|
+
id=node.name,
|
|
29
|
+
ctx=ast.Store(),
|
|
30
|
+
lineno=start_line,
|
|
31
|
+
col_offset=start_col,
|
|
32
|
+
end_lineno=start_line,
|
|
33
|
+
end_col_offset=start_col + len(node.name),
|
|
34
|
+
)
|
|
35
|
+
set_defined_name(node, name)
|
|
36
|
+
|
|
37
|
+
def visit_FunctionDef(self, node: ast.FunctionDef):
|
|
38
|
+
self._visit_defines(node, len("def "))
|
|
39
|
+
self.generic_visit(node)
|
|
40
|
+
|
|
41
|
+
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef):
|
|
42
|
+
self._visit_defines(node, len("async def "))
|
|
43
|
+
self.generic_visit(node)
|
|
44
|
+
|
|
45
|
+
def visit_ClassDef(self, node: ast.ClassDef):
|
|
46
|
+
self._visit_defines(node, len("class "))
|
|
47
|
+
self.generic_visit(node)
|
|
48
|
+
|
|
49
|
+
def visit_alias(self, node: ast.alias):
|
|
50
|
+
pos = get_pos_attributes(node)
|
|
51
|
+
start_line = pos["lineno"]
|
|
52
|
+
start_col = pos["col_offset"]
|
|
53
|
+
if node.asname:
|
|
54
|
+
if pos["end_col_offset"]:
|
|
55
|
+
start_col = pos["end_col_offset"] - len(node.asname)
|
|
56
|
+
else:
|
|
57
|
+
# Fallback: search 'as' in the source code line
|
|
58
|
+
# TODO: Is this truly necessary? and reliable?
|
|
59
|
+
line_start = self.unparsed_source_code.splitlines()[start_line - 1]
|
|
60
|
+
as_index = line_start.find(" as ", start_col)
|
|
61
|
+
if as_index != -1:
|
|
62
|
+
start_col = as_index + len(" as ")
|
|
63
|
+
name = ast.Name(
|
|
64
|
+
id=node.asname,
|
|
65
|
+
ctx=ast.Store(),
|
|
66
|
+
lineno=start_line,
|
|
67
|
+
col_offset=start_col,
|
|
68
|
+
end_lineno=start_line,
|
|
69
|
+
end_col_offset=start_col + len(node.asname),
|
|
70
|
+
)
|
|
71
|
+
set_defined_name(node, name)
|
|
72
|
+
else:
|
|
73
|
+
# No asname. Stem of imported name is the defined name.
|
|
74
|
+
after_last_dot_index = node.name.rfind(".") + 1
|
|
75
|
+
start_col += after_last_dot_index
|
|
76
|
+
defined_name = node.name[after_last_dot_index:]
|
|
77
|
+
name = ast.Name(
|
|
78
|
+
id=defined_name,
|
|
79
|
+
ctx=ast.Store(),
|
|
80
|
+
lineno=start_line,
|
|
81
|
+
col_offset=start_col,
|
|
82
|
+
end_lineno=start_line,
|
|
83
|
+
end_col_offset=start_col + len(defined_name),
|
|
84
|
+
)
|
|
85
|
+
set_defined_name(node, name)
|
|
86
|
+
|
|
87
|
+
def visit_Attribute(self, node: ast.Attribute):
|
|
88
|
+
pos = get_pos_attributes(node)
|
|
89
|
+
start_line = pos["lineno"]
|
|
90
|
+
end_col = pos["end_col_offset"]
|
|
91
|
+
# The defined name is the attribute name.
|
|
92
|
+
attr_index = (
|
|
93
|
+
end_col - len(node.attr) - 1 if end_col is not None else pos["col_offset"]
|
|
94
|
+
)
|
|
95
|
+
debug_verbose_print(
|
|
96
|
+
f'Retrieving defined name for attribute "{node.attr}" of {ast.dump(node.value)} at line {start_line}, col {attr_index}'
|
|
97
|
+
)
|
|
98
|
+
name = ast.Name(
|
|
99
|
+
id=node.attr,
|
|
100
|
+
ctx=node.ctx,
|
|
101
|
+
lineno=start_line,
|
|
102
|
+
col_offset=attr_index,
|
|
103
|
+
end_lineno=start_line,
|
|
104
|
+
end_col_offset=end_col,
|
|
105
|
+
)
|
|
106
|
+
set_defined_name(node, name)
|
|
107
|
+
self.generic_visit(node)
|
|
108
|
+
|
|
109
|
+
def visit_ImportFrom(self, node: ast.ImportFrom):
|
|
110
|
+
for alias in node.names:
|
|
111
|
+
self.visit(alias)
|
|
112
|
+
column = node.col_offset + len("from ") + node.level * len(".")
|
|
113
|
+
module_names: list[ast.Name] = []
|
|
114
|
+
for mod in node.module.split(".") if node.module else []:
|
|
115
|
+
if mod:
|
|
116
|
+
name = ast.Name(
|
|
117
|
+
id=mod,
|
|
118
|
+
lineno=node.lineno,
|
|
119
|
+
col_offset=column,
|
|
120
|
+
end_lineno=node.lineno,
|
|
121
|
+
end_col_offset=column + len(mod),
|
|
122
|
+
ctx=ast.Load(),
|
|
123
|
+
)
|
|
124
|
+
module_names.append(name)
|
|
125
|
+
column += len(mod) + len(".")
|
|
126
|
+
set_import_from_names(node, module_names)
|
|
127
|
+
|
|
128
|
+
def visit_arg(self, node: ast.arg):
|
|
129
|
+
pos = get_pos_attributes(node)
|
|
130
|
+
start_col = pos["col_offset"]
|
|
131
|
+
name = ast.Name(
|
|
132
|
+
id=node.arg,
|
|
133
|
+
ctx=ast.Store(),
|
|
134
|
+
lineno=pos["lineno"],
|
|
135
|
+
col_offset=start_col,
|
|
136
|
+
end_lineno=pos["end_lineno"],
|
|
137
|
+
end_col_offset=start_col + len(node.arg),
|
|
138
|
+
)
|
|
139
|
+
set_defined_name(node, name)
|
|
140
|
+
self.generic_visit(node)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def defined_name_retrieve(node: ast.AST, unparsed_source_code: str) -> None:
|
|
144
|
+
visitor = _DefinedNameRetriever(unparsed_source_code)
|
|
145
|
+
visitor.visit(node)
|
|
@@ -56,12 +56,9 @@ class _Transform(TyphonASTTransformer):
|
|
|
56
56
|
return result
|
|
57
57
|
|
|
58
58
|
def visit_ControlComprehension(self, node: ControlComprehension):
|
|
59
|
+
clear_is_control_comprehension(node)
|
|
59
60
|
return ast.Call(
|
|
60
|
-
func=
|
|
61
|
-
id=node.id,
|
|
62
|
-
ctx=ast.Load(),
|
|
63
|
-
**get_pos_attributes(node),
|
|
64
|
-
),
|
|
61
|
+
func=node,
|
|
65
62
|
args=[],
|
|
66
63
|
keywords=[],
|
|
67
64
|
**get_pos_attributes(node),
|
|
@@ -4,9 +4,8 @@ from ..Grammar.typhon_ast import (
|
|
|
4
4
|
get_pos_attributes,
|
|
5
5
|
is_let_assign,
|
|
6
6
|
)
|
|
7
|
-
from .visitor import TyphonASTTransformer
|
|
8
|
-
from .
|
|
9
|
-
from .utils import add_import_for_final
|
|
7
|
+
from .visitor import TyphonASTTransformer
|
|
8
|
+
from .utils.imports import add_import_for_final, get_final
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
class ConstMemberToFinal(TyphonASTTransformer):
|
|
@@ -24,7 +23,9 @@ class ConstMemberToFinal(TyphonASTTransformer):
|
|
|
24
23
|
target: ast.Name,
|
|
25
24
|
annotation: ast.expr | None,
|
|
26
25
|
):
|
|
27
|
-
assert is_decl_assign(node),
|
|
26
|
+
assert is_decl_assign(node), (
|
|
27
|
+
f"Unexpected non-decl assign in class def: {ast.dump(node)}"
|
|
28
|
+
)
|
|
28
29
|
if not is_let_assign(node):
|
|
29
30
|
return self.generic_visit(node)
|
|
30
31
|
self.changed = True
|
|
@@ -32,15 +33,17 @@ class ConstMemberToFinal(TyphonASTTransformer):
|
|
|
32
33
|
self.generic_visit(node)
|
|
33
34
|
if annotation is not None:
|
|
34
35
|
new_annotation = ast.Subscript(
|
|
35
|
-
value=
|
|
36
|
+
value=get_final(ctx=ast.Load(), **pos),
|
|
36
37
|
slice=annotation,
|
|
37
38
|
ctx=ast.Load(),
|
|
38
39
|
**pos,
|
|
39
40
|
)
|
|
40
41
|
else:
|
|
41
|
-
new_annotation =
|
|
42
|
+
new_annotation = get_final(ctx=ast.Load(), **pos)
|
|
42
43
|
return ast.AnnAssign(
|
|
43
|
-
target=ast.Name(
|
|
44
|
+
target=ast.Name(
|
|
45
|
+
id=target.id, ctx=ast.Store(), **get_pos_attributes(target)
|
|
46
|
+
),
|
|
44
47
|
annotation=new_annotation,
|
|
45
48
|
value=node.value,
|
|
46
49
|
simple=1,
|
|
@@ -73,4 +76,6 @@ def const_member_to_final(module: ast.Module):
|
|
|
73
76
|
transformer = ConstMemberToFinal(module)
|
|
74
77
|
transformer.run()
|
|
75
78
|
if transformer.changed:
|
|
79
|
+
# If we use add_import_get_final, final import confuses the visitor
|
|
80
|
+
# by pushing the current visiting node.
|
|
76
81
|
add_import_for_final(module)
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Protocol, Iterable, Final
|
|
4
|
+
from ..Grammar.typhon_ast import (
|
|
5
|
+
RecordLiteral,
|
|
6
|
+
get_record_literal_fields,
|
|
7
|
+
get_record_type_fields,
|
|
8
|
+
get_pos_attributes,
|
|
9
|
+
pos_attribute_to_range,
|
|
10
|
+
get_empty_pos_attributes,
|
|
11
|
+
PosAttributes,
|
|
12
|
+
set_is_var,
|
|
13
|
+
is_record_literal,
|
|
14
|
+
is_record_type,
|
|
15
|
+
is_attributes_pattern,
|
|
16
|
+
is_pattern_tuple,
|
|
17
|
+
)
|
|
18
|
+
from .visitor import TyphonASTVisitor, TyphonASTTransformer, flat_append
|
|
19
|
+
from .utils.imports import (
|
|
20
|
+
get_insert_point_for_class,
|
|
21
|
+
)
|
|
22
|
+
from .utils.make_class import (
|
|
23
|
+
make_protocol_definition,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class AttributePatternFieldInfo:
|
|
29
|
+
name: ast.Name
|
|
30
|
+
annotation: ast.expr
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class AttributePatternInfo:
|
|
35
|
+
class_name: str
|
|
36
|
+
pattern: ast.MatchClass
|
|
37
|
+
record: ast.Name
|
|
38
|
+
type_variables: list[str]
|
|
39
|
+
fields: list[AttributePatternFieldInfo]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class _GatherRecords(TyphonASTVisitor):
|
|
43
|
+
record_patterns: list[AttributePatternInfo]
|
|
44
|
+
|
|
45
|
+
def __init__(self, module: ast.Module):
|
|
46
|
+
super().__init__(module)
|
|
47
|
+
self.record_patterns = []
|
|
48
|
+
|
|
49
|
+
def visit_MatchClass(self, node: ast.MatchClass):
|
|
50
|
+
if isinstance(node.cls, ast.Name) and is_attributes_pattern(node.cls):
|
|
51
|
+
record_cls = node.cls
|
|
52
|
+
type_vars: list[str] = []
|
|
53
|
+
fields: list[AttributePatternFieldInfo] = []
|
|
54
|
+
# Gather the member names from keyword patterns.
|
|
55
|
+
for kwd_name in node.kwd_attrs:
|
|
56
|
+
type_var = self.new_typevar_name(kwd_name)
|
|
57
|
+
type_vars.append(type_var)
|
|
58
|
+
fields.append(
|
|
59
|
+
AttributePatternFieldInfo(
|
|
60
|
+
name=ast.Name(
|
|
61
|
+
id=kwd_name,
|
|
62
|
+
ctx=ast.Load(),
|
|
63
|
+
**get_pos_attributes(node.cls),
|
|
64
|
+
),
|
|
65
|
+
annotation=ast.Name(
|
|
66
|
+
id=type_var, ctx=ast.Load(), **get_pos_attributes(node.cls)
|
|
67
|
+
),
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
self.record_patterns.append(
|
|
71
|
+
AttributePatternInfo(
|
|
72
|
+
self.new_class_name(""),
|
|
73
|
+
node,
|
|
74
|
+
record_cls,
|
|
75
|
+
type_vars,
|
|
76
|
+
fields,
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
return self.generic_visit(node)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class _Transform(TyphonASTTransformer):
|
|
83
|
+
def __init__(
|
|
84
|
+
self,
|
|
85
|
+
module: ast.Module,
|
|
86
|
+
class_for_record_pattern: dict[ast.Name, ast.ClassDef],
|
|
87
|
+
info_for_record_pattern: dict[ast.Name, AttributePatternInfo],
|
|
88
|
+
):
|
|
89
|
+
super().__init__(module)
|
|
90
|
+
self.class_for_record_pattern = class_for_record_pattern
|
|
91
|
+
self.info_for_record_pattern = info_for_record_pattern
|
|
92
|
+
|
|
93
|
+
def visit_Name(self, node: ast.Name):
|
|
94
|
+
if node in self.class_for_record_pattern:
|
|
95
|
+
class_def = self.class_for_record_pattern[node]
|
|
96
|
+
info = self.info_for_record_pattern[node]
|
|
97
|
+
return ast.Name(
|
|
98
|
+
id=class_def.name, ctx=ast.Load(), **get_pos_attributes(node)
|
|
99
|
+
)
|
|
100
|
+
return self.generic_visit(node)
|
|
101
|
+
|
|
102
|
+
# Convert tuple pattern
|
|
103
|
+
def visit_MatchSequence(self, node: ast.MatchSequence):
|
|
104
|
+
if is_pattern_tuple(node):
|
|
105
|
+
pos = get_pos_attributes(node)
|
|
106
|
+
return ast.MatchClass(
|
|
107
|
+
cls=ast.Name(id="tuple", ctx=ast.Load(), **pos),
|
|
108
|
+
patterns=[
|
|
109
|
+
ast.MatchSequence(node.patterns, **pos_attribute_to_range(pos))
|
|
110
|
+
],
|
|
111
|
+
kwd_attrs=[],
|
|
112
|
+
kwd_patterns=[],
|
|
113
|
+
**pos_attribute_to_range(pos),
|
|
114
|
+
)
|
|
115
|
+
return self.generic_visit(node)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def extended_protocol(module: ast.Module):
|
|
119
|
+
gatherer = _GatherRecords(module)
|
|
120
|
+
gatherer.run()
|
|
121
|
+
# Create class and information for each record pattern.
|
|
122
|
+
class_for_record_pattern: dict[ast.Name, ast.ClassDef] = {}
|
|
123
|
+
info_for_record_pattern: dict[ast.Name, AttributePatternInfo] = {}
|
|
124
|
+
for info in gatherer.record_patterns:
|
|
125
|
+
class_def_type = make_protocol_definition(
|
|
126
|
+
module,
|
|
127
|
+
info.class_name,
|
|
128
|
+
info.type_variables,
|
|
129
|
+
info.fields,
|
|
130
|
+
get_pos_attributes(info.record),
|
|
131
|
+
)
|
|
132
|
+
class_for_record_pattern[info.record] = class_def_type
|
|
133
|
+
info_for_record_pattern[info.record] = info
|
|
134
|
+
module.body.insert(get_insert_point_for_class(module), class_def_type)
|
|
135
|
+
_Transform(
|
|
136
|
+
module,
|
|
137
|
+
class_for_record_pattern,
|
|
138
|
+
info_for_record_pattern,
|
|
139
|
+
).run()
|
|
@@ -8,6 +8,8 @@ from ..Grammar.typhon_ast import (
|
|
|
8
8
|
is_decl_assign,
|
|
9
9
|
is_multi_decl,
|
|
10
10
|
PosAttributes,
|
|
11
|
+
is_let_else,
|
|
12
|
+
is_inline_with,
|
|
11
13
|
)
|
|
12
14
|
from ..Grammar.syntax_errors import (
|
|
13
15
|
raise_forbidden_statement_error,
|
|
@@ -48,6 +50,28 @@ class ForbiddenStatementChecker(TyphonASTVisitor):
|
|
|
48
50
|
)
|
|
49
51
|
return self.generic_visit(node)
|
|
50
52
|
|
|
53
|
+
def visit_With_AsyncWith(self, node: ast.With | ast.AsyncWith):
|
|
54
|
+
if is_inline_with(node) and self.now_is_top_level():
|
|
55
|
+
self._raise_forbidden_statement(
|
|
56
|
+
"Inline `with` statement in module top level is forbidden",
|
|
57
|
+
get_pos_attributes(node),
|
|
58
|
+
)
|
|
59
|
+
return self.generic_visit(node)
|
|
60
|
+
|
|
61
|
+
def visit_With(self, node: ast.With):
|
|
62
|
+
self.visit_With_AsyncWith(node)
|
|
63
|
+
|
|
64
|
+
def visit_AsyncWith(self, node: ast.AsyncWith):
|
|
65
|
+
self.visit_With_AsyncWith(node)
|
|
66
|
+
|
|
67
|
+
def visit_If(self, node: ast.If):
|
|
68
|
+
if is_let_else(node) and self.now_is_top_level() and node.orelse:
|
|
69
|
+
self._raise_forbidden_statement(
|
|
70
|
+
"`let-else` statement in module top level is forbidden",
|
|
71
|
+
get_pos_attributes(node),
|
|
72
|
+
)
|
|
73
|
+
return self.generic_visit(node)
|
|
74
|
+
|
|
51
75
|
# TODO: Check control statements inside class definition.
|
|
52
76
|
|
|
53
77
|
def _is_valid_stmt_in_class(self, node: ast.stmt) -> bool:
|
|
@@ -61,6 +85,7 @@ class ForbiddenStatementChecker(TyphonASTVisitor):
|
|
|
61
85
|
ast.Import,
|
|
62
86
|
ast.AnnAssign,
|
|
63
87
|
ast.Assign,
|
|
88
|
+
ast.Pass,
|
|
64
89
|
),
|
|
65
90
|
)
|
|
66
91
|
):
|
Typhon/Transform/if_while_let.py
CHANGED
|
@@ -5,11 +5,13 @@ from ..Grammar.typhon_ast import (
|
|
|
5
5
|
is_let_else,
|
|
6
6
|
set_is_var,
|
|
7
7
|
set_is_let_else,
|
|
8
|
+
LetPatternInfo,
|
|
8
9
|
)
|
|
9
10
|
from .visitor import TyphonASTTransformer, flat_append
|
|
10
11
|
from contextlib import contextmanager
|
|
11
12
|
from dataclasses import dataclass
|
|
12
|
-
from .utils import is_body_jump_away
|
|
13
|
+
from .utils.jump_away import is_body_jump_away
|
|
14
|
+
from ..Grammar.syntax_errors import raise_let_missing_else_error
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
class IfMultipleLetTransformer(TyphonASTTransformer):
|
|
@@ -32,6 +34,13 @@ class IfMultipleLetTransformer(TyphonASTTransformer):
|
|
|
32
34
|
match <subjectN>:
|
|
33
35
|
case <patternN> if <cond>:
|
|
34
36
|
<body>
|
|
37
|
+
case _:
|
|
38
|
+
pass
|
|
39
|
+
...
|
|
40
|
+
case _:
|
|
41
|
+
pass
|
|
42
|
+
case _:
|
|
43
|
+
pass
|
|
35
44
|
else:
|
|
36
45
|
<orelse>
|
|
37
46
|
|
|
@@ -43,6 +52,11 @@ class IfMultipleLetTransformer(TyphonASTTransformer):
|
|
|
43
52
|
match <subjectN>:
|
|
44
53
|
case <patternN> if <cond>:
|
|
45
54
|
<body>
|
|
55
|
+
case _:
|
|
56
|
+
pass
|
|
57
|
+
...
|
|
58
|
+
case _:
|
|
59
|
+
pass
|
|
46
60
|
<orelse> # Unconditional execution of orelse
|
|
47
61
|
|
|
48
62
|
when <body> does not jump away:
|
|
@@ -54,20 +68,22 @@ class IfMultipleLetTransformer(TyphonASTTransformer):
|
|
|
54
68
|
case <patternN> if <cond>:
|
|
55
69
|
<else_flag> = False
|
|
56
70
|
<body>
|
|
71
|
+
case _:
|
|
72
|
+
pass
|
|
73
|
+
...
|
|
74
|
+
case _:
|
|
75
|
+
pass
|
|
57
76
|
if <else_flag>: # Only when body did not execute
|
|
58
77
|
<orelse>
|
|
59
78
|
"""
|
|
60
79
|
|
|
61
|
-
def
|
|
62
|
-
|
|
63
|
-
body = get_let_pattern_body(node)
|
|
64
|
-
if body is None:
|
|
65
|
-
return node
|
|
80
|
+
def visit_IfLet(self, node: ast.If, info: LetPatternInfo) -> list[ast.stmt]:
|
|
81
|
+
body = info.body
|
|
66
82
|
pos = get_pos_attributes(node)
|
|
67
83
|
# When jump away, does not need flag control.
|
|
68
84
|
jump_away = is_body_jump_away(body)
|
|
69
85
|
if jump_away:
|
|
70
|
-
result: list[ast.stmt] = node.body
|
|
86
|
+
result: list[ast.stmt] = node.body # Inside if True
|
|
71
87
|
if node.orelse:
|
|
72
88
|
result.extend(node.orelse)
|
|
73
89
|
return result
|
|
@@ -101,6 +117,101 @@ class IfMultipleLetTransformer(TyphonASTTransformer):
|
|
|
101
117
|
)
|
|
102
118
|
return result
|
|
103
119
|
|
|
120
|
+
"""
|
|
121
|
+
let <pattern1> = <subject1>, <pattern2> = <subject2>, ...,
|
|
122
|
+
<patternN> = <subjectN>
|
|
123
|
+
else { # else clause is required unless all patterns are irrefutable
|
|
124
|
+
<orelse> # If exists, must jump away.
|
|
125
|
+
}
|
|
126
|
+
<body>
|
|
127
|
+
|
|
128
|
+
--> (temporary represented now)
|
|
129
|
+
|
|
130
|
+
if True: # multiple_let_pattern_body set to <body>
|
|
131
|
+
match <subject1>:
|
|
132
|
+
case <pattern1>:
|
|
133
|
+
match <subject2>:
|
|
134
|
+
case <pattern2>:
|
|
135
|
+
...
|
|
136
|
+
match <subjectN>:
|
|
137
|
+
case <patternN>:
|
|
138
|
+
<body>
|
|
139
|
+
case _:
|
|
140
|
+
pass
|
|
141
|
+
(raise TypeError) # If else does not exist
|
|
142
|
+
...
|
|
143
|
+
case _:
|
|
144
|
+
pass
|
|
145
|
+
(raise TypeError) # If else does not exist
|
|
146
|
+
case _:
|
|
147
|
+
pass
|
|
148
|
+
(raise TypeError) # If else does not exist
|
|
149
|
+
else: # If exists
|
|
150
|
+
<orelse>
|
|
151
|
+
|
|
152
|
+
--> (transformation here)
|
|
153
|
+
|
|
154
|
+
The case when <body> does jumps away:
|
|
155
|
+
|
|
156
|
+
match <subject1>:
|
|
157
|
+
case <pattern1>:
|
|
158
|
+
...
|
|
159
|
+
match <subjectN>:
|
|
160
|
+
case <patternN>:
|
|
161
|
+
<body>
|
|
162
|
+
case _:
|
|
163
|
+
pass
|
|
164
|
+
(raise TypeError) # If else does not exist
|
|
165
|
+
...
|
|
166
|
+
case _:
|
|
167
|
+
pass
|
|
168
|
+
<orelse> # Unconditional execution of orelse
|
|
169
|
+
|
|
170
|
+
The case when <body> does not jump away:
|
|
171
|
+
|
|
172
|
+
<else_flag> = True
|
|
173
|
+
match <subject1>:
|
|
174
|
+
case <pattern1>:
|
|
175
|
+
...
|
|
176
|
+
match <subjectN>:
|
|
177
|
+
case <patternN>:
|
|
178
|
+
<else_flag> = False
|
|
179
|
+
<body>
|
|
180
|
+
case _:
|
|
181
|
+
pass
|
|
182
|
+
(raise TypeError) # If else does not exist
|
|
183
|
+
...
|
|
184
|
+
case _:
|
|
185
|
+
pass
|
|
186
|
+
(raise TypeError) # If else does not exist
|
|
187
|
+
if <else_flag>: # Only when body did not execute
|
|
188
|
+
<orelse> # If exists
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
def visit_LetElse(self, node: ast.If, info: LetPatternInfo) -> list[ast.stmt]:
|
|
192
|
+
pos = get_pos_attributes(node)
|
|
193
|
+
orelse = node.orelse
|
|
194
|
+
if not orelse:
|
|
195
|
+
if info.is_all_pattern_irrefutable:
|
|
196
|
+
return node.body # Simply return inside if True
|
|
197
|
+
else:
|
|
198
|
+
raise_let_missing_else_error(
|
|
199
|
+
"let pattern is irrefutable, possibly fails to match. else clause of let-else statement is required.",
|
|
200
|
+
**pos,
|
|
201
|
+
)
|
|
202
|
+
# With orelse, same as IfLet.
|
|
203
|
+
return self.visit_IfLet(node, info)
|
|
204
|
+
|
|
205
|
+
def visit_If(self, node: ast.If):
|
|
206
|
+
self.generic_visit(node) # Visit children first
|
|
207
|
+
info = get_let_pattern_body(node)
|
|
208
|
+
if info is None: # Not a if-let nor let-else
|
|
209
|
+
return node
|
|
210
|
+
if is_let_else(node):
|
|
211
|
+
return self.visit_LetElse(node, info)
|
|
212
|
+
else:
|
|
213
|
+
return self.visit_IfLet(node, info)
|
|
214
|
+
|
|
104
215
|
|
|
105
216
|
class WhileLetTransformer(TyphonASTTransformer):
|
|
106
217
|
"""
|
|
@@ -149,8 +260,8 @@ class WhileLetTransformer(TyphonASTTransformer):
|
|
|
149
260
|
|
|
150
261
|
def visit_While(self, node: ast.While):
|
|
151
262
|
self.generic_visit(node)
|
|
152
|
-
|
|
153
|
-
if
|
|
263
|
+
info = get_let_pattern_body(node)
|
|
264
|
+
if info is None:
|
|
154
265
|
return node
|
|
155
266
|
pos = get_pos_attributes(node)
|
|
156
267
|
# <continue_flag> = True
|
|
@@ -161,14 +272,14 @@ class WhileLetTransformer(TyphonASTTransformer):
|
|
|
161
272
|
**pos,
|
|
162
273
|
)
|
|
163
274
|
set_is_var(continue_flag_assign_true)
|
|
164
|
-
if not is_body_jump_away(body):
|
|
275
|
+
if not is_body_jump_away(info.body):
|
|
165
276
|
# <continue_flag> = True at end of body
|
|
166
277
|
continue_flag_set_true = ast.Assign(
|
|
167
278
|
targets=[ast.Name(id=continue_flag_name, ctx=ast.Store(), **pos)],
|
|
168
279
|
value=ast.Constant(value=True),
|
|
169
280
|
**pos,
|
|
170
281
|
)
|
|
171
|
-
body.append(continue_flag_set_true)
|
|
282
|
+
info.body.append(continue_flag_set_true)
|
|
172
283
|
# <continue_flag> = False at start of while body
|
|
173
284
|
continue_flag_set_false = ast.Assign(
|
|
174
285
|
targets=[ast.Name(id=continue_flag_name, ctx=ast.Store(), **pos)],
|
|
@@ -4,12 +4,13 @@ from ..Grammar.typhon_ast import (
|
|
|
4
4
|
clear_inline_with,
|
|
5
5
|
is_let_else,
|
|
6
6
|
get_let_pattern_body,
|
|
7
|
+
get_pos_attributes,
|
|
7
8
|
)
|
|
8
9
|
from .visitor import TyphonASTVisitor, flat_append
|
|
9
10
|
from contextlib import contextmanager
|
|
10
11
|
from dataclasses import dataclass
|
|
11
12
|
from ..Driver.debugging import debug_print, debug_verbose_print
|
|
12
|
-
from .utils import is_body_jump_away
|
|
13
|
+
from .utils.jump_away import is_body_jump_away
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
@dataclass
|
|
@@ -73,14 +74,14 @@ class _InlineStatementBlockCaptureGather(TyphonASTVisitor):
|
|
|
73
74
|
|
|
74
75
|
--> (is represented here actually)
|
|
75
76
|
|
|
76
|
-
if True:
|
|
77
|
+
if True:
|
|
77
78
|
match <subject1>:
|
|
78
79
|
case <pattern1>:
|
|
79
80
|
match <subject2>:
|
|
80
81
|
...
|
|
81
82
|
match <subjectN>:
|
|
82
83
|
case <patternN>:
|
|
83
|
-
# empty
|
|
84
|
+
# empty # info.body
|
|
84
85
|
else:
|
|
85
86
|
<orelse> # Must jump away.
|
|
86
87
|
<after>
|
|
@@ -96,7 +97,7 @@ class _InlineStatementBlockCaptureGather(TyphonASTVisitor):
|
|
|
96
97
|
|
|
97
98
|
--> (transformation here actually)
|
|
98
99
|
|
|
99
|
-
if True: #
|
|
100
|
+
if True: # let_pattern_body set to empty
|
|
100
101
|
match <subject1>:
|
|
101
102
|
case <pattern1>:
|
|
102
103
|
match <subject2>:
|
|
@@ -107,8 +108,8 @@ class _InlineStatementBlockCaptureGather(TyphonASTVisitor):
|
|
|
107
108
|
else:
|
|
108
109
|
<orelse> # Must jump away.
|
|
109
110
|
"""
|
|
110
|
-
|
|
111
|
-
if
|
|
111
|
+
info = get_let_pattern_body(node)
|
|
112
|
+
if info is None or not is_let_else(node):
|
|
112
113
|
self._visit_list_scoped(node, node.body)
|
|
113
114
|
self._visit_list_scoped(node, node.orelse)
|
|
114
115
|
return
|
|
@@ -118,18 +119,23 @@ class _InlineStatementBlockCaptureGather(TyphonASTVisitor):
|
|
|
118
119
|
node=node,
|
|
119
120
|
parent_block=parent_block_node,
|
|
120
121
|
parent_block_body=parent_block_body,
|
|
121
|
-
body_capture_into=body,
|
|
122
|
+
body_capture_into=info.body,
|
|
122
123
|
)
|
|
123
124
|
debug_print(
|
|
124
|
-
f"Found let else: \n node:{ast.dump(node)} \n parent:{ast.dump(parent_block_node)}\n body:{list(map(ast.dump, body))}"
|
|
125
|
+
f"Found let else: \n node:{ast.dump(node)} \n parent:{ast.dump(parent_block_node)}\n body:{list(map(ast.dump, info.body))}"
|
|
125
126
|
)
|
|
126
|
-
if
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
if node.orelse:
|
|
128
|
+
if not is_body_jump_away(node.orelse):
|
|
129
|
+
raise RuntimeError(
|
|
130
|
+
"let-else's else body must jump away"
|
|
131
|
+
) # TODO: Arrow NoReturn function call
|
|
132
|
+
else:
|
|
133
|
+
# Empty else body. The pattern must be irrefutable.
|
|
134
|
+
# The error is checked in if_while_let_transform.
|
|
135
|
+
pass
|
|
130
136
|
self._visit_list_scoped(node, node.orelse)
|
|
131
137
|
# Hack to make parent of the followings to case's body
|
|
132
|
-
self.parent_block_scopes[-1] = (node, body)
|
|
138
|
+
self.parent_block_scopes[-1] = (node, info.body)
|
|
133
139
|
|
|
134
140
|
def visit_For(self, node: ast.For):
|
|
135
141
|
# No need to visit target, iter
|
|
@@ -215,9 +221,10 @@ def inline_statement_block_capture(module: ast.Module):
|
|
|
215
221
|
if node not in parent_block_body:
|
|
216
222
|
raise RuntimeError("Parent block not found") # TODO: proper error handling
|
|
217
223
|
index = parent_block_body.index(node)
|
|
218
|
-
# Move the remained stmts into
|
|
219
|
-
body_capture_into.clear()
|
|
224
|
+
# Move the remained stmts into capturer's body
|
|
220
225
|
body_capture_into.extend(parent_block_body[index + 1 :])
|
|
226
|
+
if not body_capture_into:
|
|
227
|
+
body_capture_into.append(ast.Pass(**get_pos_attributes(node)))
|
|
221
228
|
# Remove the remained stmts from parent block
|
|
222
229
|
del parent_block_body[index + 1 :]
|
|
223
230
|
debug_verbose_print(
|