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.
Files changed (57) hide show
  1. Typhon/Driver/configs.py +14 -0
  2. Typhon/Driver/debugging.py +148 -5
  3. Typhon/Driver/diagnostic.py +4 -3
  4. Typhon/Driver/language_server.py +25 -0
  5. Typhon/Driver/run.py +1 -1
  6. Typhon/Driver/translate.py +16 -11
  7. Typhon/Driver/utils.py +39 -1
  8. Typhon/Grammar/_typhon_parser.py +2920 -2718
  9. Typhon/Grammar/parser.py +80 -53
  10. Typhon/Grammar/parser_helper.py +68 -87
  11. Typhon/Grammar/syntax_errors.py +41 -20
  12. Typhon/Grammar/token_factory_custom.py +541 -485
  13. Typhon/Grammar/tokenizer_custom.py +52 -0
  14. Typhon/Grammar/typhon_ast.py +754 -76
  15. Typhon/Grammar/typhon_ast_error.py +438 -0
  16. Typhon/Grammar/unparse_custom.py +25 -0
  17. Typhon/LanguageServer/__init__.py +3 -0
  18. Typhon/LanguageServer/client/__init__.py +42 -0
  19. Typhon/LanguageServer/client/pyrefly.py +115 -0
  20. Typhon/LanguageServer/client/pyright.py +173 -0
  21. Typhon/LanguageServer/semantic_tokens.py +446 -0
  22. Typhon/LanguageServer/server.py +376 -0
  23. Typhon/LanguageServer/utils.py +65 -0
  24. Typhon/SourceMap/ast_match_based_map.py +199 -152
  25. Typhon/SourceMap/ast_matching.py +102 -87
  26. Typhon/SourceMap/datatype.py +275 -264
  27. Typhon/SourceMap/defined_name_retrieve.py +145 -0
  28. Typhon/Transform/comprehension_to_function.py +2 -5
  29. Typhon/Transform/const_member_to_final.py +12 -7
  30. Typhon/Transform/extended_patterns.py +139 -0
  31. Typhon/Transform/forbidden_statements.py +25 -0
  32. Typhon/Transform/if_while_let.py +122 -11
  33. Typhon/Transform/inline_statement_block_capture.py +22 -15
  34. Typhon/Transform/optional_operators_to_checked.py +14 -6
  35. Typhon/Transform/placeholder_to_function.py +0 -1
  36. Typhon/Transform/record_to_dataclass.py +22 -238
  37. Typhon/Transform/scope_check_rename.py +109 -29
  38. Typhon/Transform/transform.py +16 -12
  39. Typhon/Transform/type_abbrev_desugar.py +11 -15
  40. Typhon/Transform/type_annotation_check_expand.py +2 -2
  41. Typhon/Transform/utils/__init__.py +0 -0
  42. Typhon/Transform/utils/imports.py +83 -0
  43. Typhon/Transform/{utils.py → utils/jump_away.py} +2 -38
  44. Typhon/Transform/utils/make_class.py +135 -0
  45. Typhon/Transform/visitor.py +25 -0
  46. Typhon/Typing/pyrefly.py +145 -0
  47. Typhon/Typing/pyright.py +141 -144
  48. Typhon/Typing/result_diagnostic.py +1 -1
  49. Typhon/__main__.py +15 -1
  50. {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/METADATA +13 -6
  51. typhon_language-0.1.4.dist-info/RECORD +65 -0
  52. {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/WHEEL +1 -1
  53. typhon_language-0.1.4.dist-info/licenses/LICENSE +201 -0
  54. typhon_language-0.1.2.dist-info/RECORD +0 -48
  55. typhon_language-0.1.2.dist-info/licenses/LICENSE +0 -21
  56. {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/entry_points.txt +0 -0
  57. {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/top_level.txt +0 -0
@@ -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
@@ -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 debug_print(*arg, **kwargs) -> None:
105
+ def debug_file_write(*arg, **kwargs) -> None: # type: ignore
40
106
  if _debug:
41
- print(*arg, **kwargs)
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 debug_verbose_print(*arg, **kwargs) -> None:
115
+ def debug_file_write_verbose(*arg, **kwargs) -> None: # type: ignore
46
116
  if _debug_verbose:
47
- print(*arg, **kwargs)
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")
@@ -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 >= 1 and line_num <= len(source_lines):
26
- result += f" {line_num:<{len_in_digit}}| {source_lines[line_num - 1]}\n"
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":{orig_pythonpath}"
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
@@ -5,21 +5,23 @@ from dataclasses import dataclass
5
5
  from ..Grammar.parser import parse_file
6
6
  from ..Grammar.syntax_errors import (
7
7
  diag_errors,
8
- TyphonSyntaxError,
8
+ TyphonTransformSyntaxError,
9
9
  TyphonSyntaxErrorList,
10
10
  )
11
+ from ..Grammar.unparse_custom import unparse_custom
11
12
  from .utils import (
12
13
  shorthand,
13
14
  TYPHON_EXT,
14
15
  copy_type,
15
16
  default_output_dir,
16
17
  canonicalize_path,
18
+ mkdir_and_setup_init_py,
17
19
  )
18
20
  from ..Transform.transform import transform
19
21
  from .debugging import is_debug_mode, debug_print, is_debug_verbose
20
22
  from ..Driver.type_check import run_type_check, TypeCheckResult
21
23
  from ..SourceMap import SourceMap
22
- from ..SourceMap.ast_match_based_map import map_from_transformed_ast
24
+ from ..SourceMap.ast_match_based_map import map_from_translated_ast
23
25
 
24
26
 
25
27
  @dataclass
@@ -27,7 +29,9 @@ class TranslateResult:
27
29
  source_path_canonical: str
28
30
  output_path_canonical: str
29
31
  source_map: SourceMap | None
30
- syntax_error: SyntaxError | TyphonSyntaxError | TyphonSyntaxErrorList | None
32
+ syntax_error: (
33
+ SyntaxError | TyphonTransformSyntaxError | TyphonSyntaxErrorList | None
34
+ )
31
35
 
32
36
 
33
37
  def translate_file(source: Path, output: Path) -> TranslateResult:
@@ -35,7 +39,7 @@ def translate_file(source: Path, output: Path) -> TranslateResult:
35
39
  try:
36
40
  ast_tree = parse_file(source.as_posix(), verbose=is_debug_verbose())
37
41
  transform(ast_tree)
38
- except (SyntaxError, TyphonSyntaxError, TyphonSyntaxErrorList) as e:
42
+ except (SyntaxError, TyphonTransformSyntaxError, TyphonSyntaxErrorList) as e:
39
43
  debug_print(f"Error parsing file {source}: {e}")
40
44
  return TranslateResult(
41
45
  source_path_canonical=canonicalize_path(source),
@@ -43,10 +47,14 @@ def translate_file(source: Path, output: Path) -> TranslateResult:
43
47
  source_map=None,
44
48
  syntax_error=e,
45
49
  )
46
- translated_code = ast.unparse(ast_tree)
50
+ translated_code = unparse_custom(ast_tree)
47
51
  source_code = source.read_text(encoding="utf-8")
48
- mapping = map_from_transformed_ast(
49
- ast_tree, ast.parse(translated_code), source_code, source.as_posix()
52
+ mapping = map_from_translated_ast(
53
+ ast_tree,
54
+ ast.parse(translated_code),
55
+ source_code,
56
+ source.as_posix(),
57
+ translated_code,
50
58
  )
51
59
  output.write_text(translated_code)
52
60
  return TranslateResult(
@@ -95,10 +103,7 @@ def translate_directory(
95
103
  output_path = module_output_dir / (source.stem + ".py")
96
104
  file_result = translate_file(source, output_path)
97
105
  result[source] = file_result
98
- if not (module_output_dir / "__init__.py").exists():
99
- (module_output_dir / "__init__.py").write_text(
100
- "# Init file for Typhon module\n", "utf-8"
101
- )
106
+ mkdir_and_setup_init_py(module_output_dir)
102
107
  for subdir in source_dir.iterdir():
103
108
  if subdir.is_dir():
104
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 / ".typhon"
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")