Skip to content

Commit

Permalink
Get #include working, provisionally
Browse files Browse the repository at this point in the history
  • Loading branch information
iafisher committed Dec 17, 2018
1 parent ed93ece commit fa96b77
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 11 deletions.
5 changes: 4 additions & 1 deletion hera/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,13 @@ def execute_program(
vm = VirtualMachine()

try:
program = parse(program)
program = parse(program, expand_includes=True)
except HERAError as e:
emit_error(str(e), line=e.line, column=e.column, exit=True)

# Filter out #include statements for now.
program = [op for op in program if op.name != "#include"]

symtab = get_symtab(program)

typecheck(program, symtab)
Expand Down
49 changes: 41 additions & 8 deletions hera/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,31 @@
class TreeToOplist(Transformer):
"""Transform Lark's parse tree into a list of HERA ops."""

def start(self, matches):
# TODO: Figure out why all this is necessary.
if len(matches) == 2:
if isinstance(matches[0], tuple):
return [matches[0]] + matches[1]
else:
return matches[0] + matches[1]
elif len(matches) > 2:
return matches[:-1] + matches[-1]
else:
return matches[0]

def cpp_program(self, matches):
emit_warning("void HERA_main() { ... } is not necessary")
return matches

def hera_program(self, matches):
return matches

def op(self, matches):
return Op(matches[0], matches[1:])

def include(self, matches):
return Op("#include", [matches[0]])

def value(self, matches):
line = matches[0].line
column = matches[0].column
Expand Down Expand Up @@ -70,17 +88,18 @@ def value(self, matches):

_parser = Lark(
r"""
?start: cpp_program | _hera_program
start: include* (cpp_program | hera_program)
cpp_program: _INCLUDE* _cpp_open op* _CPP_CLOSE
_hera_program: op*
cpp_program: _cpp_open (op | include)* _CPP_CLOSE
hera_program: op (op | include)* |
// Underscores before everything so that no tokens end up in the tree.
_INCLUDE: /#include.*/
_cpp_open: "void" _SYMBOL "(" ")" "{"
_CPP_CLOSE: "}"
_SYMBOL: SYMBOL
include: "#include" ( STRING | /<[^>]+>/ )
op: SYMBOL "(" _arglist? ")" ";"?
_arglist: ( value "," )* value
Expand All @@ -106,7 +125,7 @@ def value(self, matches):
)


def parse(text):
def parse(text, *, expand_includes=False):
"""Parse a HERA program into a list of Op objects."""
try:
tree = _parser.parse(text)
Expand All @@ -121,11 +140,25 @@ def parse(text):
raise HERAError("invalid syntax", e.line, e.column) from None

if isinstance(tree, Tree):
return tree.children
ops = tree.children
elif isinstance(tree, Op):
return [tree]
ops = [tree]
else:
ops = tree

if expand_includes:
expanded_ops = []
for op in ops:
if op.name == "#include" and len(op.args) == 1 and not op.args[0].startswith("<"):
# Strip off the leading and trailing quote.
fpath = op.args[0][1:-1]
with open(fpath) as f:
expanded_ops.extend(parse(f.read()))
else:
expanded_ops.append(op)
return expanded_ops
else:
return tree
return ops


def replace_escapes(s):
Expand Down
7 changes: 7 additions & 0 deletions test/assets/lib/add.hera
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
BR(end_of_add)

LABEL(add)
ADD(R3, R1, R2)
RETURN(R12, R13)

LABEL(end_of_add)
6 changes: 6 additions & 0 deletions test/assets/simple_include.hera
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// TODO: This should be just "lib/add.hera"
#include "test/assets/lib/add.hera"

SET(R1, 20)
SET(R2, 22)
CALL(R12, add)
18 changes: 18 additions & 0 deletions test/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,24 @@ def test_manual_strings_dot_hera():
assert not vm.flag_sign


def test_simple_include_dot_hera():
vm = VirtualMachine()
main(["test/assets/simple_include.hera"], vm)

assert vm.registers[1] == 20
assert vm.registers[2] == 22
assert vm.registers[3] == 42

for r in vm.registers[4:11]:
assert r == 0

assert not vm.flag_sign
assert not vm.flag_zero
assert not vm.flag_overflow
assert not vm.flag_carry
assert not vm.flag_carry_block


def test_error_message_for_missing_comma(capsys):
line = "SETLO(R1 40)"
with pytest.raises(SystemExit):
Expand Down
21 changes: 19 additions & 2 deletions test/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,13 @@ def test_parse_single_line_comment():

def test_parse_hera_boilerplate():
assert parse("#include <HERA.h>\nvoid HERA_main() {SETLO(R1, 42)}") == [
Op("SETLO", ["R1", 42])
Op("#include", ["<HERA.h>"]),
Op("SETLO", ["R1", 42]),
]


def test_parse_hera_boilerplate_weird_whitespace_and_spelling():
assert parse("#include <HERA.h>\nvoid HeRA_mAin( \t)\n {\n\n}") == []
assert parse("#include <HERA.h>\nvoid HeRA_mAin( \t)\n {\n\n}") == [Op("#include", ["<HERA.h>"])]


def test_parse_hera_boilerplate_no_includes():
Expand Down Expand Up @@ -132,6 +133,11 @@ def test_parse_multiline_comment():
assert parse(program) == [Op("SETLO", ["R1", 1])]


def test_parse_include_amidst_instructions():
program = 'SETLO(R1, 42)\n#include "whatever"\n'
assert parse(program) == [Op("SETLO", ["R1", 42]), Op("#include", ['"whatever"'])]


def test_parse_missing_comma():
with pytest.raises(HERAError):
parse("ADD(R1, R2 R3)")
Expand All @@ -155,3 +161,14 @@ def test_parse_exception_has_line_number():
assert e.line == 3
else:
assert False, "expected excepton"


def test_parse_expands_include():
program = '#include "test/assets/lib/add.hera"'
assert parse(program, expand_includes=True) == [
Op("BR", ["end_of_add"]),
Op("LABEL", ["add"]),
Op("ADD", ["R3", "R1", "R2"]),
Op("RETURN", ["R12", "R13"]),
Op("LABEL", ["end_of_add"]),
]

0 comments on commit fa96b77

Please sign in to comment.