Skip to content

Commit

Permalink
Warn when zero-prefixed numbers are used
Browse files Browse the repository at this point in the history
Resolves #25
  • Loading branch information
iafisher committed Dec 13, 2018
1 parent 362dd43 commit 292f655
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 81 deletions.
16 changes: 16 additions & 0 deletions hera/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
LINES = None


# ANSI color codes (https://stackoverflow.com/questions/4842424/)
# When the --no-color flag is specified, these constants are set to the empty
# string, so they can be used unconditionally in your code but will still obey
# the flag value.


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


ANSI_RED_BOLD = _make_ansi(31, 1)
ANSI_MAGENTA_BOLD = _make_ansi(35, 1)
ANSI_RESET = _make_ansi(0)
57 changes: 8 additions & 49 deletions hera/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@

from docopt import docopt

from . import utils
from . import config
from .parser import parse
from .preprocessor import preprocess
from .typechecker import typecheck
from .utils import print_register_debug, HERAError
from .utils import emit_error, print_register_debug, HERAError
from .vm import VirtualMachine


lines = None
LINES = None


def main(argv=None, vm=None):
Expand All @@ -41,7 +41,7 @@ def main(argv=None, vm=None):
path = arguments["<path>"]

if arguments["--no-color"]:
utils.ANSI_MAGENTA_BOLD = utils.ANSI_RED_BOLD = utils.ANSI_RESET = ""
config.ANSI_MAGENTA_BOLD = config.ANSI_RED_BOLD = config.ANSI_RESET = ""

if path == "-":
try:
Expand Down Expand Up @@ -86,42 +86,28 @@ def execute_program(program, *, lines_to_exec=None, no_dump_state=False, vm=None
A virtual machine instance may be passed in for testing purposes. If it is not, a
new one is instantiated. The virtual machine is returned.
"""
lines = program.splitlines()
config.LINES = program.splitlines()

if vm is None:
vm = VirtualMachine()

try:
program = parse(program)
except HERAError as e:
report_hera_error(e, lines)
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:
# TODO: Remove duplication with report_hera_error and error_and_exit.
if error.line:
if error.column:
caret = align_caret(lines[error.line - 1], error.column) + "^"
msg = "{0.msg}, line {0.line} col {0.column}\n\n {1}\n {2}\n".format(
error, lines[error.line - 1], caret
)
else:
msg = "{0.msg}, line {0.line}\n\n {1}\n".format(
error, lines[error.line - 1]
)
else:
msg = error.msg
sys.stderr.write(utils.ANSI_RED_BOLD + "Error" + utils.ANSI_RESET + ": ")
sys.stderr.write(msg + "\n")
emit_error(error.msg, line=error.line, column=error.column)
sys.exit(3)

try:
program = preprocess(program)
vm.exec_many(program, lines=lines_to_exec)
except HERAError as e:
report_hera_error(e, lines)
emit_error(str(e), line=e.line, column=e.column, exit=True)
else:
if not no_dump_state:
dump_state(vm)
Expand Down Expand Up @@ -161,30 +147,3 @@ def dump_state(vm):
nprint("\tOverflow flag is " + ("ON" if vm.flag_overflow else "OFF"))
nprint("\tCarry flag is " + ("ON" if vm.flag_carry else "OFF"))
nprint("\tCarry block flag is " + ("ON" if vm.flag_carry_block else "OFF"))


def report_hera_error(exc, lines):
if exc.line:
if exc.column:
caret = align_caret(lines[exc.line - 1], exc.column) + "^"
msg = "{0}, line {0.line} col {0.column}\n\n {1}\n {2}\n".format(
exc, lines[exc.line - 1], caret
)
else:
msg = "{0}, line {0.line}\n\n {1}\n".format(exc, lines[exc.line - 1])
else:
msg = str(exc)
error_and_exit(msg)


def error_and_exit(msg, *, exitcode=3):
sys.stderr.write(utils.ANSI_RED_BOLD + "Error" + utils.ANSI_RESET + ": ")
sys.stderr.write(msg + "\n")
sys.exit(exitcode)


def align_caret(line, col):
"""Return the whitespace necessary to align a caret to underline the desired
column in the line of text. Mainly this means handling tabs.
"""
return "".join("\t" if c == "\t" else " " for c in line[: col - 1])
6 changes: 5 additions & 1 deletion hera/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ def value(self, matches):
return IntToken(matches[0], base=16, line=line, column=column)
elif matches[0].type == "OCTAL":
if not matches[0].startswith("0o"):
emit_warning("zero-prefixed numbers are interpreted as octal")
emit_warning(
"zero-prefixed numbers are interpreted as octal",
line=matches[0].line,
column=matches[0].column,
)
return IntToken(matches[0], base=8, line=line, column=column)
elif matches[0].type == "BINARY":
return IntToken(matches[0], base=2, line=line, column=column)
Expand Down
38 changes: 27 additions & 11 deletions hera/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

from lark import Token

from . import config


class HERAError(Exception):
def __init__(self, msg, line=None, column=None):
Expand Down Expand Up @@ -105,20 +107,34 @@ def is_symbol(s):
return isinstance(s, Token) and s.type == "SYMBOL"


def emit_warning(msg):
sys.stderr.write(ANSI_MAGENTA_BOLD + "Warning" + ANSI_RESET + ": " + msg + "\n")
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
_emit_msg(msg, line=line, column=column, exit=exit)


# ANSI color codes (https://stackoverflow.com/questions/4842424/)
# When the --no-color flag is specified, these constants are set to the empty
# string, so they can be used unconditionally in your code but will still obey
# the flag value.
def emit_warning(msg, *, line=None, column=None):
"""Print a error warning to stderr."""
msg = config.ANSI_MAGENTA_BOLD + "Warning" + config.ANSI_RESET + ": " + msg
_emit_msg(msg, line=line, column=column, exit=False)


def make_ansi(*params):
return "\033[" + ";".join(map(str, params)) + "m"
def _emit_msg(msg, *, line=None, column=None, exit=False):
if line is not None and config.LINES is not None:
if column is not None:
caret = _align_caret(config.LINES[line - 1], column) + "^"
msg += ", line {} col {}\n\n {}\n {}\n".format(
line, column, config.LINES[line - 1], caret
)
else:
msg += ", line {}\n\n {}\n".format(line, config.LINES[line - 1])
sys.stderr.write(msg + "\n")
if exit:
sys.exit(exit)


ANSI_RED_BOLD = make_ansi(31, 1)
ANSI_MAGENTA_BOLD = make_ansi(35, 1)
ANSI_RESET = make_ansi(0)
def _align_caret(line, col):
"""Return the whitespace necessary to align a caret to underline the desired
column in the line of text. Mainly this means handling tabs.
"""
return "".join("\t" if c == "\t" else " " for c in line[: col - 1])
9 changes: 9 additions & 0 deletions test/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from hera.config import _make_ansi


def test_make_ansi_red():
assert _make_ansi(31, 1) == "\033[31;1m"


def test_make_ansi_reset():
assert _make_ansi(0) == "\033[0m"
12 changes: 1 addition & 11 deletions test/test_main.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
from hera.main import (
align_caret,
dump_state,
op_to_string,
program_to_string,
)
from hera.main import dump_state, op_to_string, program_to_string
from hera.parser import Op
from hera.vm import VirtualMachine



def test_op_to_string():
assert op_to_string(Op("SET", ["R1", "top"])) == "SET(R1, top)"

Expand All @@ -26,10 +20,6 @@ def test_program_to_string():
assert program_to_string(program) == "SET(R1, 20)\nSET(R2, 22)\nADD(R3, R1, R2)"


def test_align_caret():
assert align_caret("\t\t a", 5) == "\t\t "


def test_dump_state(capsys):
dump_state(VirtualMachine())

Expand Down
14 changes: 5 additions & 9 deletions test/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import pytest

from hera.utils import from_u16, make_ansi, register_to_index, to_u16, to_u32


def test_make_ansi_red():
assert make_ansi(31, 1) == "\033[31;1m"


def test_make_ansi_reset():
assert make_ansi(0) == "\033[0m"
from hera.utils import from_u16, register_to_index, to_u16, to_u32, _align_caret


def test_to_u16_with_max_negative():
Expand Down Expand Up @@ -154,3 +146,7 @@ def test_register_to_index_with_invalid_register():
with pytest.raises(ValueError) as e:
register_to_index("R16")
assert "R16" in str(e)


def test_align_caret():
assert _align_caret("\t\t a", 5) == "\t\t "

0 comments on commit 292f655

Please sign in to comment.