Skip to content

Commit

Permalink
Handle C++ boilerplate in parser
Browse files Browse the repository at this point in the history
  • Loading branch information
iafisher committed Jan 30, 2019
1 parent a307518 commit cc20f68
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 15 deletions.
3 changes: 3 additions & 0 deletions hera/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,10 @@ class TOKEN(Enum):

LPAREN = "TOKEN_LPAREN"
RPAREN = "TOKEN_RPAREN"
LBRACE = "TOKEN_LBRACE"
RBRACE = "TOKEN_RBRACE"
COMMA = "TOKEN_COMMA"
SEMICOLON = "TOKEN_SEMICOLON"

FMT = "TOKEN_FMT"
INCLUDE = "TOKEN_INCLUDE"
Expand Down
2 changes: 1 addition & 1 deletion hera/debugger/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
)
from hera.data import DataLabel, HERAError, Label, Program, Settings
from hera.loader import load_program
from hera.parser import parse
from hera.parser_bespoke import parse
from hera.utils import BRANCHES, DATA_STATEMENTS, format_int, pad, register_to_index


Expand Down
6 changes: 6 additions & 0 deletions hera/lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,14 @@ def next_token(self):
self.set_token(TOKEN.LPAREN)
elif ch == ")":
self.set_token(TOKEN.RPAREN)
elif ch == "{":
self.set_token(TOKEN.LBRACE)
elif ch == "}":
self.set_token(TOKEN.RBRACE)
elif ch == ",":
self.set_token(TOKEN.COMMA)
elif ch == ";":
self.set_token(TOKEN.SEMICOLON)
else:
self.set_token(TOKEN.UNKNOWN)

Expand Down
2 changes: 1 addition & 1 deletion hera/op.py
Original file line number Diff line number Diff line change
Expand Up @@ -1012,7 +1012,7 @@ def resolve_ops(program: List[Op]) -> Tuple[List[Operation], Messages]:
ret = []
for op in program:
try:
cls = name_to_class[op.name]
cls = name_to_class[str(op.name)]
except KeyError:
messages.err("unknown instruction `{}`".format(op.name), loc=op.name)
else:
Expand Down
42 changes: 31 additions & 11 deletions hera/parser_bespoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,31 +41,51 @@ def parse(self, lexer):
if lexer.path:
self.visited.add(get_canonical_path(lexer.path))

expecting_brace = False
ops = []
while lexer.tkn.type != TOKEN.EOF:
if lexer.tkn.type == TOKEN.INCLUDE:
ops.extend(self.match_include(lexer))
elif lexer.tkn.type == TOKEN.SYMBOL:
ops.append(self.match_op(lexer))
name = lexer.tkn
lexer.next_token()
# Many legacy HERA program are enclosed with void HERA_main() { ... },
# which we have to handle unfortunately.
if lexer.tkn.type == TOKEN.SYMBOL and name == "void":
if lexer.next_token().type != TOKEN.LPAREN:
self.err(lexer.tkn, "expected left parenthesis")

if lexer.next_token().type != TOKEN.RPAREN:
self.err(lexer.tkn, "expected right parenthesis")

if lexer.next_token().type != TOKEN.LBRACE:
self.err(lexer.tkn, "expected left curly brace")

lexer.next_token()
expecting_brace = True
continue
elif lexer.tkn.type == TOKEN.LPAREN:
ops.append(self.match_op(lexer, name))
# Ops may optionally be separated by semicolons.
if lexer.tkn.type == TOKEN.SEMICOLON:
lexer.next_token()
else:
self.err(lexer.tkn, "expected left parenthesis")
elif expecting_brace and lexer.tkn.type == TOKEN.RBRACE:
lexer.next_token()
expecting_brace = False
else:
self.err(lexer.tkn, "expected HERA operation or #include")
break
return ops

def match_op(self, lexer):
name = lexer.tkn
def match_op(self, lexer, name):
"""Match an operation, assuming that lexer.tkn is on the right parenthesis."""
lexer.next_token()

if lexer.tkn.type != TOKEN.LPAREN:
self.err(lexer.tkn, "expected left parenthesis")
return Op(name, [])
else:
lexer.next_token()

args = self.match_optional_arglist(lexer)

if lexer.tkn.type != TOKEN.RPAREN:
self.err(lexer.tkn, "expected left parenthesis")
self.err(lexer.tkn, "expected right parenthesis")
return Op(name, args)
else:
lexer.next_token()
Expand Down
2 changes: 1 addition & 1 deletion test/test_unit/test_conversion.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from hera.op import ADD, BR, BRR, CALL, FOFF, FON, resolve_ops, SETHI, SETLO, SUB
from hera.parser import parse
from hera.parser_bespoke import parse


def helper(argstr):
Expand Down
13 changes: 12 additions & 1 deletion test/test_unit/test_lexer.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# TODO: The equality checks here don't check type because of the way that Token.__eq__
# is defined.
from hera.data import Token
from hera.lexer import Lexer, TOKEN

Expand Down Expand Up @@ -80,9 +82,17 @@ def test_lexer_with_include():
assert lexer.next_token() == Token(TOKEN.EOF, "")


def test_lexer_with_braces():
lexer = lex_helper("{}")

assert lexer.tkn == Token(TOKEN.LBRACE, "{")
assert lexer.next_token() == Token(TOKEN.RBRACE, "}")
assert lexer.next_token() == Token(TOKEN.EOF, "")


def test_lexer_with_big_example():
# This isn't a syntactically valid expression, but it doesn't matter to the lexer.
lexer = lex_helper("@FP_alt R15 0xabc some_symbol :xdc -10 ,, ()+*/?")
lexer = lex_helper("@FP_alt R15 0xabc some_symbol :xdc -10; ,, ()+*/?")

assert lexer.tkn == Token(TOKEN.AT, "@")
assert lexer.next_token() == Token(TOKEN.REGISTER, "FP_alt")
Expand All @@ -91,6 +101,7 @@ def test_lexer_with_big_example():
assert lexer.next_token() == Token(TOKEN.SYMBOL, "some_symbol")
assert lexer.next_token() == Token(TOKEN.FMT, "xdc")
assert lexer.next_token() == Token(TOKEN.INT, "-10")
assert lexer.next_token() == Token(TOKEN.SEMICOLON, ";")
assert lexer.next_token() == Token(TOKEN.COMMA, ",")
assert lexer.next_token() == Token(TOKEN.COMMA, ",")
assert lexer.next_token() == Token(TOKEN.LPAREN, "(")
Expand Down
38 changes: 38 additions & 0 deletions test/test_unit/test_parser_bespoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,41 @@ def test_parse_single_line_comment_with_ops():
assert len(program) == 2
assert program[0].name == "SETLO"
assert program[1].name == "SETHI"


def test_parse_cpp_boilerplate():
program = valid(
"""\
#include <HERA.h>
void HERA_main() {
SET(R1, 42)
}
""",
warnings=True,
)

assert len(program) == 1
assert program[0].name == "SET"


def test_parse_empty_cpp_boilerplate():
program = valid(
"""\
#include <HERA.h>
void HERA_main() {
}
""",
warnings=True,
)

assert len(program) == 0


def test_parse_ops_with_semicolons():
program = valid("SET(R1, 1); SET(R2, 2)")

assert len(program) == 2
assert program[0].name == "SET"
assert program[1].name == "SET"

0 comments on commit cc20f68

Please sign in to comment.