Skip to content

Commit

Permalink
Detect type errors even after parse errors
Browse files Browse the repository at this point in the history
  • Loading branch information
iafisher committed Feb 8, 2019
1 parent 78826f2 commit f8e1678
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Execution times of long-running programs has been cut down by roughly 50%.
- Use of `SWI` and `RTI` operations now result in parse-time errors instead of run-time warnings.
- HERA files may no longer contain non-ASCII bytes.
- Type errors are detected even in files with parse errors.

## [0.5.2] - 2019-02-02
### Added
Expand Down
4 changes: 2 additions & 2 deletions hera/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@

def check(
oplist: List[AbstractOperation], settings: Settings
) -> Tuple[Optional[Program], Messages]:
) -> Tuple[Program, Messages]:
symbol_table, messages = typecheck(oplist, settings=settings)
if messages.errors:
return (None, messages)
return (Program([], [], {}), messages)

oplist, preprocess_messages = convert_ops(oplist, symbol_table)
messages.extend(preprocess_messages)
Expand Down
12 changes: 8 additions & 4 deletions hera/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ def load_program(text: str, settings=Settings()) -> Program:
The return value of this function is valid input to the VirtualMachine.run method.
"""
oplist = handle_messages(settings, parse(text, settings=settings))
return handle_messages(settings, check(oplist, settings))
oplist, parse_messages = parse(text, settings=settings)
program, check_messages = check(oplist, settings=settings)
handle_messages(settings, parse_messages.extend(check_messages))
return program


def load_program_from_file(path: str, settings=Settings()) -> Program:
Expand Down Expand Up @@ -48,5 +50,7 @@ def load_program_from_file(path: str, settings=Settings()) -> Program:
except HERAError as e:
handle_messages(settings, Messages(str(e) + "."))

oplist = handle_messages(settings, parse(text, path=path, settings=settings))
return handle_messages(settings, check(oplist, settings))
oplist, parse_messages = parse(text, path=path, settings=settings)
program, check_messages = check(oplist, settings=settings)
handle_messages(settings, parse_messages.extend(check_messages))
return program
16 changes: 14 additions & 2 deletions hera/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ def match_op(self, name_tkn: Token) -> Optional[AbstractOperation]:
self.lexer.next_token()
args = self.match_optional_arglist()
self.lexer.next_token()
if args is None:
return None

try:
cls = name_to_class[name_tkn.value]
except KeyError:
Expand All @@ -110,13 +113,20 @@ def match_op(self, name_tkn: Token) -> Optional[AbstractOperation]:

VALUE_TOKENS = {Token.INT, Token.REGISTER, Token.SYMBOL, Token.STRING, Token.CHAR}

def match_optional_arglist(self) -> List[Token]:
def match_optional_arglist(self) -> Optional[List[Token]]:
"""Match zero or more comma-separated values. Exits with the right parenthesis
as the current token. Make sure to distinguish between a None return value (the
arglist could not be parsed) and a [] return value (an empty arglist was parsed
successfully).
"""
if self.lexer.tkn.type == Token.RPAREN:
return []

args = []
hit_error = False
while True:
if not self.expect(self.VALUE_TOKENS, "expected value"):
hit_error = True
self.skip_until({Token.COMMA, Token.RPAREN})
if self.lexer.tkn.type == Token.COMMA:
self.lexer.next_token()
Expand All @@ -131,6 +141,7 @@ def match_optional_arglist(self) -> List[Token]:
if self.lexer.tkn.type == Token.RPAREN:
break
elif self.lexer.tkn.type != Token.COMMA:
hit_error = True
self.err("expected comma or right parenthesis")
self.skip_until({Token.COMMA, Token.RPAREN})
if (
Expand All @@ -140,7 +151,8 @@ def match_optional_arglist(self) -> List[Token]:
break
else:
self.lexer.next_token()
return args

return args if not hit_error else None

def match_value(self) -> Token:
if self.lexer.tkn.type == Token.INT:
Expand Down
20 changes: 20 additions & 0 deletions test/assets/error/mega_error.hera
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// A parse error (missing comma)
SET(R1 40)

// A type error
FOFF(R1)

// Multiple type errors on one line
ADD('c', "abc")

// Warning for zero-prefixed octal literal
SET(R2, 01)

// Error for over-long character literal
SETLO(R7, 'ab')

// Warning for invalid backslash escape, and error for data after code
LP_STRING("\y")

// Finish off with an unclosed string literal
SET(R1, "
62 changes: 62 additions & 0 deletions test/test_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,3 +415,65 @@ def test_error_for_interrupt_instructions(capsys):
"""
)


def test_mega_error(capsys):
with pytest.raises(SystemExit):
main(["test/assets/error/mega_error.hera"])

captured = capsys.readouterr().err
assert (
captured
== """\
Warning: consider using "0o" prefix for octal numbers, line 11 col 9 of test/assets/error/mega_error.hera
SET(R2, 01)
^
Warning: unrecognized backslash escape, line 17 col 13 of test/assets/error/mega_error.hera
LP_STRING("\\y")
^
Error: expected comma or right parenthesis, line 2 col 8 of test/assets/error/mega_error.hera
SET(R1 40)
^
Error: over-long character literal, line 14 col 11 of test/assets/error/mega_error.hera
SETLO(R7, 'ab')
^
Error: unclosed string literal, line 20 col 9 of test/assets/error/mega_error.hera
SET(R1, "
^
Error: expected integer, line 5 col 6 of test/assets/error/mega_error.hera
FOFF(R1)
^
Error: too few args to ADD (expected 3), line 8 col 1 of test/assets/error/mega_error.hera
ADD('c', "abc")
^
Error: expected register, line 8 col 6 of test/assets/error/mega_error.hera
ADD('c', "abc")
^
Error: expected register, line 8 col 10 of test/assets/error/mega_error.hera
ADD('c', "abc")
^
Error: data statement after code, line 17 col 1 of test/assets/error/mega_error.hera
LP_STRING("\\y")
^
"""
)

0 comments on commit f8e1678

Please sign in to comment.