Skip to content

Commit

Permalink
Simplify error reporting system
Browse files Browse the repository at this point in the history
Resolves #23
  • Loading branch information
iafisher committed Dec 13, 2018
1 parent bc04304 commit 21acf72
Show file tree
Hide file tree
Showing 7 changed files with 407 additions and 250 deletions.
4 changes: 4 additions & 0 deletions hera/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
LINES = None


# Whether or not an error has been registered.
SEEN_ERROR = False


def _make_ansi(*params):
return "\033[" + ";".join(map(str, params)) + "m"

Expand Down
12 changes: 6 additions & 6 deletions hera/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,15 @@ def execute_program(program, *, lines_to_exec=None, no_dump_state=False, vm=None
except HERAError as e:
emit_error(str(e), line=e.line, column=e.column, exit=True)

# TODO: Seems like typechecking should happen after preprocessing.
errors = typecheck(program)
if errors:
for error in errors:
emit_error(error.msg, line=error.line, column=error.column)
typecheck(program)
if config.SEEN_ERROR:
sys.exit(3)

program = preprocess(program)
if config.SEEN_ERROR:
sys.exit(3)

try:
program = preprocess(program)
vm.exec_many(program, lines=lines_to_exec)
except HERAError as e:
emit_error(str(e), line=e.line, column=e.column, exit=True)
Expand Down
6 changes: 3 additions & 3 deletions hera/preprocessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from lark import Token

from .parser import Op
from .utils import copy_token, emit_error, HERAError, is_symbol, to_u16
from .utils import copy_token, emit_error, is_symbol, to_u16


# Arbitrary value copied over from HERA-C.
Expand Down Expand Up @@ -39,9 +39,9 @@ def substitute_label(op, labels):
try:
label = labels[v]
except KeyError:
raise HERAError(
emit_error(
"undefined symbol `{}`".format(v), line=op.name.line, column=v.column
) from None
)
else:
return Op(name, [d, label & 0xFF])
elif op.name == "SETHI" and is_symbol(op.args[1]):
Expand Down
54 changes: 18 additions & 36 deletions hera/typechecker.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,24 @@
Author: Ian Fisher (iafisher@protonmail.com)
Version: December 2018
"""
from collections import namedtuple

from lark import Token

from .utils import is_symbol, register_to_index


ErrorInfo = namedtuple("ErrorInfo", ["msg", "line", "column"])
from .utils import emit_error, is_symbol, register_to_index


def typecheck(program):
"""Type-check the program and return a list of errors encountered."""
errors = []
"""Type-check the program and emit errors as appropriate."""
for op in program:
errors.extend(typecheck_one(op))
return errors
typecheck_one(op)


def typecheck_one(op):
"""Type-check a single HERA op and return a list of errors encountered."""
"""Type-check a single HERA op and emit errors as appropriate."""
params = _types_map.get(op.name)
if params is not None:
return check_types(op.name, params, op.args)
check_types(op.name, params, op.args)
else:
return [
ErrorInfo("unknown instruction `{}`".format(op.name), op.name.line, None)
]
emit_error("unknown instruction `{}`".format(op.name), line=op.name.line)


# Constants to pass to check_types
Expand Down Expand Up @@ -131,39 +122,30 @@ def typecheck_one(op):


def check_types(name, expected, got):
"""Verify that the given args match the expected ones and return a list of errors.
`name` is the name of the HERA op, as a Token object. `expected` is a tuple or list
of constants (REGISTER, U16, etc., defined above) representing the expected argument
types to the operation. `args` is a tuple or list of the actual arguments given.
"""Verify that the given args match the expected ones and emit errors as
appropriate. `name` is the name of the HERA op, as a Token object. `expected` is a
tuple or list of constants (REGISTER, U16, etc., defined above) representing the
expected argument types to the operation. `args` is a tuple or list of the actual
arguments given.
"""
errors = []

if len(got) < len(expected):
errors.append(
ErrorInfo(
"too few args to {} (expected {})".format(name, len(expected)),
name.line,
None,
)
emit_error(
"too few args to {} (expected {})".format(name, len(expected)),
line=name.line,
)

if len(expected) < len(got):
errors.append(
ErrorInfo(
"too many args to {} (expected {})".format(name, len(expected)),
name.line,
None,
)
emit_error(
"too many args to {} (expected {})".format(name, len(expected)),
line=name.line,
)

ordinals = ["first", "second", "third"]
for ordinal, pattern, arg in zip(ordinals, expected, got):
prefix = "{} arg to {} ".format(ordinal, name)
error = check_one_type(pattern, arg)
if error:
errors.append(ErrorInfo(prefix + error, arg.line, arg.column))

return errors
emit_error(prefix + error, line=arg.line, column=arg.column)


def check_one_type(pattern, arg):
Expand Down
1 change: 1 addition & 0 deletions hera/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def is_symbol(s):
def emit_error(msg, *, line=None, column=None, exit=False):
"""Print an error message to stderr."""
msg = config.ANSI_RED_BOLD + "Error" + config.ANSI_RESET + ": " + msg
config.SEEN_ERROR = True
_emit_msg(msg, line=line, column=column, exit=exit)


Expand Down
4 changes: 3 additions & 1 deletion test/test_preprocessor.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pytest
from unittest.mock import patch

from lark import Token

Expand Down Expand Up @@ -45,8 +46,9 @@ def test_substitute_label_with_other_op():


def test_substitute_label_with_undefined_label():
with pytest.raises(HERAError):
with patch("hera.utils._emit_msg") as mock_emit_error:
substitute_label(Op(SYM("SETLO"), [R("R1"), SYM("N")]), {})
assert mock_emit_error.call_count == 1


def test_convert_set_with_small_positive():
Expand Down
Loading

0 comments on commit 21acf72

Please sign in to comment.