Commit 0e1b6134 authored by Tom Niget's avatar Tom Niget

Start adding proper error handling infrastructure

parent ced073f3
...@@ -26,7 +26,7 @@ def run_tests(): ...@@ -26,7 +26,7 @@ def run_tests():
with open(path, "r", encoding="utf-8") as f: with open(path, "r", encoding="utf-8") as f:
code = f.read() code = f.read()
execute = "# norun" not in code execute = "# norun" not in code
res = format_code(transpile(code)) res = format_code(transpile(code, path.name, path))
#print(res) #print(res)
name_cpp = path.with_suffix('.cpp') name_cpp = path.with_suffix('.cpp')
with open(name_cpp, "w", encoding="utf-8") as fcpp: with open(name_cpp, "w", encoding="utf-8") as fcpp:
......
# coding: utf-8 # coding: utf-8
import ast import ast
import builtins
import inspect
from transpiler.consts import MAPPINGS from transpiler.consts import MAPPINGS
from transpiler.phases.desugar_with import DesugarWith from transpiler.phases.desugar_with import DesugarWith
...@@ -13,18 +15,30 @@ import sys ...@@ -13,18 +15,30 @@ import sys
from colorama import Fore from colorama import Fore
import colorama import colorama
from transpiler.utils import highlight
colorama.init() colorama.init()
def exception_hook(exc_type, exc_value, tb): def exception_hook(exc_type, exc_value, tb):
print = lambda *args, **kwargs: builtins.print(*args, **kwargs, file=sys.stderr)
last_node = None
last_file = None
while tb: while tb:
local_vars = tb.tb_frame.f_locals local_vars = tb.tb_frame.f_locals
name = tb.tb_frame.f_code.co_name
if name == "transpile":
last_file = local_vars["path"]
if name == "visit" and (node := local_vars["node"]) and isinstance(node, ast.AST):
last_node = node
if local_vars.get("TB_SKIP", None) and tb.tb_next: if local_vars.get("TB_SKIP", None) and tb.tb_next:
tb = tb.tb_next tb = tb.tb_next
continue continue
filename = tb.tb_frame.f_code.co_filename filename = tb.tb_frame.f_code.co_filename
name = tb.tb_frame.f_code.co_name
line_no = tb.tb_lineno line_no = tb.tb_lineno
print(f"{Fore.RED}File \"{filename}\", line {line_no}, in {name}", end="") print(f"{Fore.RED}File \"{filename}\", line {line_no}, in {name}", end="")
...@@ -34,13 +48,18 @@ def exception_hook(exc_type, exc_value, tb): ...@@ -34,13 +48,18 @@ def exception_hook(exc_type, exc_value, tb):
print() print()
tb = tb.tb_next tb = tb.tb_next
# Exception type and value if last_node is not None:
print(f"{exc_type.__name__}, Message: {exc_value}") print(f"In file \"{Fore.RESET}{last_file}{Fore.RED}\", line {last_node.lineno}")
print("\t" + highlight(ast.unparse(last_node)))
print(f"{Fore.RED}Error:{Fore.RESET} {exc_value}")
print(inspect.cleandoc(exc_value.detail(last_node)))
sys.excepthook = exception_hook sys.excepthook = exception_hook
def transpile(source): def transpile(source, name="<module>", path=None):
TB = f"transpiling module {Fore.RESET}{name}"
res = ast.parse(source, type_comments=True) res = ast.parse(source, type_comments=True)
#res = initial_pytype.run(source, res) #res = initial_pytype.run(source, res)
res = DesugarWith().visit(res) res = DesugarWith().visit(res)
......
import ast
from abc import ABC
class CompileError(Exception, ABC):
def detail(self, last_node: ast.AST = None) -> str:
return ""
...@@ -7,6 +7,7 @@ from typing import Iterable ...@@ -7,6 +7,7 @@ from typing import Iterable
from transpiler.phases.emit_cpp.consts import MAPPINGS from transpiler.phases.emit_cpp.consts import MAPPINGS
from transpiler.phases.typing import TypeVariable from transpiler.phases.typing import TypeVariable
from transpiler.phases.typing.exceptions import UnresolvedTypeVariableError
from transpiler.phases.typing.types import BaseType, TY_INT, TY_BOOL, TY_NONE, Promise, PromiseKind, TY_STR, UserType, \ from transpiler.phases.typing.types import BaseType, TY_INT, TY_BOOL, TY_NONE, Promise, PromiseKind, TY_STR, UserType, \
TypeType, TypeOperator, TY_FLOAT TypeType, TypeOperator, TY_FLOAT
from transpiler.utils import UnsupportedNodeError, highlight from transpiler.utils import UnsupportedNodeError, highlight
...@@ -88,7 +89,7 @@ class NodeVisitor(UniversalVisitor): ...@@ -88,7 +89,7 @@ class NodeVisitor(UniversalVisitor):
yield ">" yield ">"
elif isinstance(node, TypeVariable): elif isinstance(node, TypeVariable):
#yield f"TYPEVAR_{node.name}";return #yield f"TYPEVAR_{node.name}";return
raise NotImplementedError(f"Not unified type variable {node}") raise UnresolvedTypeVariableError(node)
elif isinstance(node, TypeOperator): elif isinstance(node, TypeOperator):
yield "Py" + node.name.title() yield "Py" + node.name.title()
if node.args: if node.args:
......
import ast
from dataclasses import dataclass
from transpiler.utils import highlight
from transpiler.exceptions import CompileError
from transpiler.phases.typing import TypeVariable
@dataclass
class UnresolvedTypeVariableError(CompileError):
variable: TypeVariable
def __str__(self) -> str:
return f"Unresolved type variable: {self.variable}"
def detail(self, last_node: ast.AST = None) -> str:
if isinstance(last_node, (ast.Import, ast.ImportFrom)):
return f"""
This indicates the compiler was unable to infer the type of a function in a module.
Currently, Typon cannot determine the type of Python functions imported from other modules, except
for the standard library.
As such, you need to give enough information to the compiler to infer the type of the function.
For example:
vvv this tells the compiler that {highlight('math.factorial')} returns an {highlight('int')}
{highlight('res: int = math.factorial(5)')}"""
return """
This generally indicates the compiler was unable to infer the type of a variable or expression.
A common fix is to add a type annotation to the variable or function.
"""
...@@ -7,7 +7,7 @@ from typing import Union ...@@ -7,7 +7,7 @@ from typing import Union
from colorama import Fore from colorama import Fore
def highlight(code): def highlight(code, full=False):
""" """
Syntax highlights code as Python using colorama Syntax highlights code as Python using colorama
""" """
...@@ -21,6 +21,8 @@ def highlight(code): ...@@ -21,6 +21,8 @@ def highlight(code):
from pygments.formatters import TerminalFormatter from pygments.formatters import TerminalFormatter
items = pyg_highlight(code, PythonLexer(), TerminalFormatter()).splitlines() items = pyg_highlight(code, PythonLexer(), TerminalFormatter()).splitlines()
if full:
return Fore.RESET + "\n".join(items)
res = items[0] res = items[0]
if len(items) > 1: if len(items) > 1:
res += Fore.WHITE + " [...]" res += Fore.WHITE + " [...]"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment