Typhon-Language 0.1.3__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 +14 -10
- Typhon/Driver/utils.py +39 -1
- Typhon/Grammar/_typhon_parser.py +2738 -2525
- Typhon/Grammar/parser.py +80 -53
- Typhon/Grammar/parser_helper.py +68 -87
- Typhon/Grammar/syntax_errors.py +31 -21
- Typhon/Grammar/token_factory_custom.py +541 -485
- Typhon/Grammar/tokenizer_custom.py +52 -0
- Typhon/Grammar/typhon_ast.py +372 -44
- Typhon/Grammar/typhon_ast_error.py +438 -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 +27 -16
- 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/forbidden_statements.py +1 -0
- Typhon/Transform/optional_operators_to_checked.py +14 -6
- Typhon/Transform/scope_check_rename.py +44 -18
- Typhon/Transform/type_abbrev_desugar.py +11 -15
- Typhon/Transform/type_annotation_check_expand.py +2 -2
- Typhon/Transform/utils/imports.py +39 -4
- Typhon/Transform/utils/make_class.py +18 -23
- Typhon/Transform/visitor.py +25 -0
- Typhon/Typing/pyrefly.py +145 -0
- Typhon/Typing/pyright.py +2 -4
- Typhon/__main__.py +15 -1
- {typhon_language-0.1.3.dist-info → typhon_language-0.1.4.dist-info}/METADATA +7 -5
- typhon_language-0.1.4.dist-info/RECORD +65 -0
- {typhon_language-0.1.3.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.3.dist-info/RECORD +0 -53
- typhon_language-0.1.3.dist-info/licenses/LICENSE +0 -21
- {typhon_language-0.1.3.dist-info → typhon_language-0.1.4.dist-info}/entry_points.txt +0 -0
- {typhon_language-0.1.3.dist-info → typhon_language-0.1.4.dist-info}/top_level.txt +0 -0
Typhon/Driver/configs.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
_language_backend: Literal["pyrefly", "pyright"] = "pyrefly"
|
|
4
|
+
# TODO: Basedpyright seems to hang on semantic tokens request
|
|
5
|
+
_language_backend = "pyright"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def set_language_backend(backend: Literal["pyrefly", "pyright"]):
|
|
9
|
+
global _language_backend
|
|
10
|
+
_language_backend = backend
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_language_backend() -> Literal["pyrefly", "pyright"]:
|
|
14
|
+
return _language_backend
|
Typhon/Driver/debugging.py
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
from .utils import copy_type
|
|
1
|
+
from .utils import copy_type, get_project_root
|
|
2
|
+
from typing import BinaryIO, override
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import logging
|
|
5
|
+
|
|
2
6
|
|
|
3
7
|
_debug = False
|
|
4
8
|
_debug_verbose = False
|
|
5
9
|
_debug_first_error = False
|
|
10
|
+
_debug_log_file: Path | None = None
|
|
6
11
|
|
|
7
12
|
|
|
8
13
|
def set_debug_mode(debug: bool):
|
|
@@ -22,6 +27,20 @@ def set_debug_first_error(first_error: bool):
|
|
|
22
27
|
_debug_first_error = first_error
|
|
23
28
|
|
|
24
29
|
|
|
30
|
+
def set_debug_log_file(
|
|
31
|
+
log_file: str | None, verbose: bool = False, append: bool = False
|
|
32
|
+
):
|
|
33
|
+
global _debug_log_file
|
|
34
|
+
_debug_log_file = Path(log_file) if log_file else None
|
|
35
|
+
if _debug_log_file is not None:
|
|
36
|
+
if verbose:
|
|
37
|
+
set_debug_verbose(True)
|
|
38
|
+
set_debug_mode(True)
|
|
39
|
+
mode = "a" if append else "w"
|
|
40
|
+
with _debug_log_file.open(mode) as f:
|
|
41
|
+
f.write("=== Typhon Debug Log ===\n")
|
|
42
|
+
|
|
43
|
+
|
|
25
44
|
def is_debug_mode() -> bool:
|
|
26
45
|
return _debug
|
|
27
46
|
|
|
@@ -35,13 +54,137 @@ def is_debug_first_error() -> bool:
|
|
|
35
54
|
return _debug_first_error
|
|
36
55
|
|
|
37
56
|
|
|
57
|
+
def get_debug_log_file() -> Path | None:
|
|
58
|
+
return _debug_log_file
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
_debug_printed: int = 0
|
|
62
|
+
_debug_printed_size: int = 0
|
|
63
|
+
_limit_reached_printed: bool = False
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _debug_limit_check(*arg) -> bool:
|
|
67
|
+
global _debug_printed
|
|
68
|
+
global _debug_printed_size
|
|
69
|
+
global _limit_reached_printed
|
|
70
|
+
if _debug_printed > 100000 or _debug_printed_size > 10000000:
|
|
71
|
+
if not _limit_reached_printed:
|
|
72
|
+
print("Debug print limit reached, stopping further debug prints.")
|
|
73
|
+
_limit_reached_printed = True
|
|
74
|
+
return True
|
|
75
|
+
_debug_printed += 1
|
|
76
|
+
_debug_printed_size += sum(len(str(a)) for a in arg)
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@copy_type(print)
|
|
81
|
+
def debug_print(*arg, **kwargs) -> None: # type: ignore
|
|
82
|
+
if _debug:
|
|
83
|
+
if _debug_limit_check(*arg):
|
|
84
|
+
return
|
|
85
|
+
if _debug_log_file is not None:
|
|
86
|
+
with _debug_log_file.open("a") as log_file:
|
|
87
|
+
print(*arg, file=log_file, **kwargs) # type: ignore
|
|
88
|
+
else:
|
|
89
|
+
print(*arg, **kwargs) # type: ignore
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@copy_type(print)
|
|
93
|
+
def debug_verbose_print(*arg, **kwargs) -> None: # type: ignore
|
|
94
|
+
if _debug_verbose:
|
|
95
|
+
if _debug_limit_check(*arg):
|
|
96
|
+
return
|
|
97
|
+
if _debug_log_file is not None:
|
|
98
|
+
with _debug_log_file.open("a") as log_file:
|
|
99
|
+
print(*arg, file=log_file, **kwargs) # type: ignore
|
|
100
|
+
else:
|
|
101
|
+
print(*arg, **kwargs) # type: ignore
|
|
102
|
+
|
|
103
|
+
|
|
38
104
|
@copy_type(print)
|
|
39
|
-
def
|
|
105
|
+
def debug_file_write(*arg, **kwargs) -> None: # type: ignore
|
|
40
106
|
if _debug:
|
|
41
|
-
|
|
107
|
+
if _debug_log_file is not None:
|
|
108
|
+
if _debug_limit_check(*arg):
|
|
109
|
+
return
|
|
110
|
+
with _debug_log_file.open("a") as log_file:
|
|
111
|
+
print(*arg, file=log_file, **kwargs) # type: ignore
|
|
42
112
|
|
|
43
113
|
|
|
44
114
|
@copy_type(print)
|
|
45
|
-
def
|
|
115
|
+
def debug_file_write_verbose(*arg, **kwargs) -> None: # type: ignore
|
|
46
116
|
if _debug_verbose:
|
|
47
|
-
|
|
117
|
+
if _debug_log_file is not None:
|
|
118
|
+
if _debug_limit_check(*arg):
|
|
119
|
+
return
|
|
120
|
+
with _debug_log_file.open("a") as log_file:
|
|
121
|
+
print(*arg, file=log_file, **kwargs) # type: ignore
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
type ReadableBuffer = bytes | bytearray | memoryview
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# Bypass binaryIO class stdio to log
|
|
128
|
+
class BinaryIOLogger(BinaryIO):
|
|
129
|
+
def __init__(self, pipe: BinaryIO):
|
|
130
|
+
self._pipe = pipe
|
|
131
|
+
|
|
132
|
+
def fileno(self) -> int:
|
|
133
|
+
return self._pipe.fileno()
|
|
134
|
+
|
|
135
|
+
def isatty(self) -> bool:
|
|
136
|
+
return self._pipe.isatty()
|
|
137
|
+
|
|
138
|
+
def write(self, data: ReadableBuffer) -> int:
|
|
139
|
+
# Log the data being written
|
|
140
|
+
if _debug_log_file is not None:
|
|
141
|
+
with _debug_log_file.open("a") as log_file:
|
|
142
|
+
log_file.write(f"Writing {data}\n")
|
|
143
|
+
log_file.flush()
|
|
144
|
+
return self._pipe.write(data)
|
|
145
|
+
|
|
146
|
+
def flush(self) -> None:
|
|
147
|
+
self._pipe.flush()
|
|
148
|
+
|
|
149
|
+
def read(self, n: int = -1) -> bytes:
|
|
150
|
+
data = self._pipe.read(n)
|
|
151
|
+
if _debug_log_file is not None:
|
|
152
|
+
with _debug_log_file.open("a") as log_file:
|
|
153
|
+
log_file.write(f"Read {data}\n")
|
|
154
|
+
log_file.flush()
|
|
155
|
+
return data
|
|
156
|
+
|
|
157
|
+
def close(self) -> None:
|
|
158
|
+
if _debug_log_file is not None:
|
|
159
|
+
with _debug_log_file.open("a") as log_file:
|
|
160
|
+
log_file.write("Closing pipe.\n")
|
|
161
|
+
log_file.flush()
|
|
162
|
+
self._pipe.close()
|
|
163
|
+
|
|
164
|
+
@override
|
|
165
|
+
def readline(self, limit: int = -1) -> bytes:
|
|
166
|
+
line = self._pipe.readline(limit)
|
|
167
|
+
if _debug_log_file is not None:
|
|
168
|
+
with _debug_log_file.open("a") as log_file:
|
|
169
|
+
log_file.write(f"Read line {line}\n")
|
|
170
|
+
log_file.flush()
|
|
171
|
+
return line
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def debug_setup_logging(verbose: bool = True, append: bool = False) -> None:
|
|
175
|
+
set_debug_log_file(
|
|
176
|
+
str(get_project_root() / "private" / "server1.log"),
|
|
177
|
+
verbose=verbose,
|
|
178
|
+
append=append,
|
|
179
|
+
)
|
|
180
|
+
set_debug_mode(True)
|
|
181
|
+
set_debug_verbose(verbose)
|
|
182
|
+
if (log_file := get_debug_log_file()) is not None:
|
|
183
|
+
logging.basicConfig(
|
|
184
|
+
filename=log_file,
|
|
185
|
+
level=logging.INFO if verbose else logging.DEBUG,
|
|
186
|
+
filemode="a",
|
|
187
|
+
format="%(asctime)s %(levelname)s %(name)s %(message)s",
|
|
188
|
+
force=True,
|
|
189
|
+
)
|
|
190
|
+
debug_file_write("=== Typhon Debug Logging set ===\n")
|
Typhon/Driver/diagnostic.py
CHANGED
|
@@ -9,8 +9,9 @@ def diag_error_file_position(
|
|
|
9
9
|
rule: str | None,
|
|
10
10
|
message: str,
|
|
11
11
|
):
|
|
12
|
+
# Range is 0-based, but line numbers displayed are 1-based
|
|
12
13
|
return (
|
|
13
|
-
f"{error_type}: {file_path}:{position.start.line}:{position.start.column}{rule if rule else ''}\n"
|
|
14
|
+
f"{error_type}: {file_path}:{position.start.line + 1}:{position.start.column + 1}{rule if rule else ''}\n"
|
|
14
15
|
f" {message}"
|
|
15
16
|
)
|
|
16
17
|
|
|
@@ -22,8 +23,8 @@ def positioned_source_code(
|
|
|
22
23
|
result = ""
|
|
23
24
|
len_in_digit = len(str(range_in_source.end.line)) + 1
|
|
24
25
|
for line_num in range(range_in_source.start.line, range_in_source.end.line + 1):
|
|
25
|
-
if line_num >=
|
|
26
|
-
result += f" {line_num:<{len_in_digit}}| {source_lines[line_num
|
|
26
|
+
if line_num >= 0 and line_num < len(source_lines):
|
|
27
|
+
result += f" {line_num + 1:<{len_in_digit}}| {source_lines[line_num]}\n"
|
|
27
28
|
if (
|
|
28
29
|
range_in_source.start.line == range_in_source.end.line
|
|
29
30
|
and line_num == range_in_source.start.line
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from ..LanguageServer import server
|
|
2
|
+
from ..Driver.debugging import (
|
|
3
|
+
is_debug_mode,
|
|
4
|
+
BinaryIOLogger,
|
|
5
|
+
set_debug_log_file,
|
|
6
|
+
debug_print,
|
|
7
|
+
get_debug_log_file,
|
|
8
|
+
set_debug_verbose,
|
|
9
|
+
debug_setup_logging,
|
|
10
|
+
)
|
|
11
|
+
import sys
|
|
12
|
+
import logging
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def language_server():
|
|
16
|
+
"""
|
|
17
|
+
Start the Typhon Language Server.
|
|
18
|
+
"""
|
|
19
|
+
server.start_io(
|
|
20
|
+
stdin=BinaryIOLogger(sys.stdin.buffer),
|
|
21
|
+
stdout=BinaryIOLogger(sys.stdout.buffer),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# TODO: Support TCP Mode?
|
|
25
|
+
# server.start_tcp("127.0.0.1", 8080)
|
Typhon/Driver/run.py
CHANGED
|
@@ -92,7 +92,7 @@ def run_directory(source_dir: Path, capture_output: bool, *args: str) -> RunResu
|
|
|
92
92
|
# Set up environment with modified PYTHONPATH
|
|
93
93
|
orig_pythonpath = os.environ.get("PYTHONPATH", "")
|
|
94
94
|
if orig_pythonpath:
|
|
95
|
-
orig_pythonpath = f"
|
|
95
|
+
orig_pythonpath = f"{os.pathsep}{orig_pythonpath}"
|
|
96
96
|
subprocess_env = {
|
|
97
97
|
**dict(os.environ),
|
|
98
98
|
# Override PYTHONPATH to include temp_output_dir
|
Typhon/Driver/translate.py
CHANGED
|
@@ -5,7 +5,7 @@ from dataclasses import dataclass
|
|
|
5
5
|
from ..Grammar.parser import parse_file
|
|
6
6
|
from ..Grammar.syntax_errors import (
|
|
7
7
|
diag_errors,
|
|
8
|
-
|
|
8
|
+
TyphonTransformSyntaxError,
|
|
9
9
|
TyphonSyntaxErrorList,
|
|
10
10
|
)
|
|
11
11
|
from ..Grammar.unparse_custom import unparse_custom
|
|
@@ -15,12 +15,13 @@ from .utils import (
|
|
|
15
15
|
copy_type,
|
|
16
16
|
default_output_dir,
|
|
17
17
|
canonicalize_path,
|
|
18
|
+
mkdir_and_setup_init_py,
|
|
18
19
|
)
|
|
19
20
|
from ..Transform.transform import transform
|
|
20
21
|
from .debugging import is_debug_mode, debug_print, is_debug_verbose
|
|
21
22
|
from ..Driver.type_check import run_type_check, TypeCheckResult
|
|
22
23
|
from ..SourceMap import SourceMap
|
|
23
|
-
from ..SourceMap.ast_match_based_map import
|
|
24
|
+
from ..SourceMap.ast_match_based_map import map_from_translated_ast
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
@dataclass
|
|
@@ -28,7 +29,9 @@ class TranslateResult:
|
|
|
28
29
|
source_path_canonical: str
|
|
29
30
|
output_path_canonical: str
|
|
30
31
|
source_map: SourceMap | None
|
|
31
|
-
syntax_error:
|
|
32
|
+
syntax_error: (
|
|
33
|
+
SyntaxError | TyphonTransformSyntaxError | TyphonSyntaxErrorList | None
|
|
34
|
+
)
|
|
32
35
|
|
|
33
36
|
|
|
34
37
|
def translate_file(source: Path, output: Path) -> TranslateResult:
|
|
@@ -36,7 +39,7 @@ def translate_file(source: Path, output: Path) -> TranslateResult:
|
|
|
36
39
|
try:
|
|
37
40
|
ast_tree = parse_file(source.as_posix(), verbose=is_debug_verbose())
|
|
38
41
|
transform(ast_tree)
|
|
39
|
-
except (SyntaxError,
|
|
42
|
+
except (SyntaxError, TyphonTransformSyntaxError, TyphonSyntaxErrorList) as e:
|
|
40
43
|
debug_print(f"Error parsing file {source}: {e}")
|
|
41
44
|
return TranslateResult(
|
|
42
45
|
source_path_canonical=canonicalize_path(source),
|
|
@@ -46,8 +49,12 @@ def translate_file(source: Path, output: Path) -> TranslateResult:
|
|
|
46
49
|
)
|
|
47
50
|
translated_code = unparse_custom(ast_tree)
|
|
48
51
|
source_code = source.read_text(encoding="utf-8")
|
|
49
|
-
mapping =
|
|
50
|
-
ast_tree,
|
|
52
|
+
mapping = map_from_translated_ast(
|
|
53
|
+
ast_tree,
|
|
54
|
+
ast.parse(translated_code),
|
|
55
|
+
source_code,
|
|
56
|
+
source.as_posix(),
|
|
57
|
+
translated_code,
|
|
51
58
|
)
|
|
52
59
|
output.write_text(translated_code)
|
|
53
60
|
return TranslateResult(
|
|
@@ -96,10 +103,7 @@ def translate_directory(
|
|
|
96
103
|
output_path = module_output_dir / (source.stem + ".py")
|
|
97
104
|
file_result = translate_file(source, output_path)
|
|
98
105
|
result[source] = file_result
|
|
99
|
-
|
|
100
|
-
(module_output_dir / "__init__.py").write_text(
|
|
101
|
-
"# Init file for Typhon module\n", "utf-8"
|
|
102
|
-
)
|
|
106
|
+
mkdir_and_setup_init_py(module_output_dir)
|
|
103
107
|
for subdir in source_dir.iterdir():
|
|
104
108
|
if subdir.is_dir():
|
|
105
109
|
sub_output_dir = module_output_dir / subdir.name
|
Typhon/Driver/utils.py
CHANGED
|
@@ -4,6 +4,8 @@ import os
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
TYPHON_EXT = ".typh"
|
|
7
|
+
TYPHON_TEMP_DIR = ".typhon"
|
|
8
|
+
TYPHON_SERVER_TEMP_DIR = ".typhon-server"
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
def shorthand[T](
|
|
@@ -29,7 +31,19 @@ def shorthand[T](
|
|
|
29
31
|
|
|
30
32
|
|
|
31
33
|
def default_output_dir(source: str) -> Path:
|
|
32
|
-
return Path(source).parent /
|
|
34
|
+
return Path(source).parent / TYPHON_TEMP_DIR
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def output_dir_for_workspace(workspace: Path) -> Path:
|
|
38
|
+
return workspace / TYPHON_SERVER_TEMP_DIR
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def default_server_output_dir(source: str) -> Path:
|
|
42
|
+
return Path(source).parent / TYPHON_SERVER_TEMP_DIR
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def output_dir_for_server_workspace(workspace: Path) -> Path:
|
|
46
|
+
return workspace / TYPHON_SERVER_TEMP_DIR
|
|
33
47
|
|
|
34
48
|
|
|
35
49
|
def prepare_default_output_file(source: Path) -> Path:
|
|
@@ -39,6 +53,21 @@ def prepare_default_output_file(source: Path) -> Path:
|
|
|
39
53
|
return output_file
|
|
40
54
|
|
|
41
55
|
|
|
56
|
+
def prepare_default_server_output_file(source: Path) -> Path:
|
|
57
|
+
temp_output_dir = default_server_output_dir(source.as_posix())
|
|
58
|
+
temp_output_dir.mkdir(exist_ok=True)
|
|
59
|
+
output_file = temp_output_dir / (source.stem + ".py")
|
|
60
|
+
return output_file
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def mkdir_and_setup_init_py(dir: Path) -> None:
|
|
64
|
+
if not dir.exists():
|
|
65
|
+
dir.mkdir(parents=True, exist_ok=True)
|
|
66
|
+
init_file = dir / "__init__.py"
|
|
67
|
+
if not init_file.exists():
|
|
68
|
+
init_file.write_text("# Init file_for Typhon module.", encoding="utf-8")
|
|
69
|
+
|
|
70
|
+
|
|
42
71
|
def copy_type[**P, T](
|
|
43
72
|
_: Callable[P, T],
|
|
44
73
|
) -> Callable[[Callable[..., T]], Callable[P, T]]:
|
|
@@ -52,3 +81,12 @@ def copy_type[**P, T](
|
|
|
52
81
|
# For windows, convert all to lower case and resolve.
|
|
53
82
|
def canonicalize_path(path: Path) -> str:
|
|
54
83
|
return Path(os.path.normcase(str(path))).resolve().as_posix()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_project_root() -> Path:
|
|
87
|
+
current = Path(__file__).resolve()
|
|
88
|
+
for _ in range(10):
|
|
89
|
+
if (current / "pyproject.toml").exists():
|
|
90
|
+
return current
|
|
91
|
+
current = current.parent
|
|
92
|
+
raise FileNotFoundError("Could not find project root with pyproject.toml")
|