From 43df98ea8f491b31b580fd909e92d5fea486599d Mon Sep 17 00:00:00 2001 From: Frederick Yin Date: Tue, 30 Aug 2022 15:21:25 +0800 Subject: hackc: print syntax error message --- projects/hackc/parser.py | 22 ++++++++++++++-------- projects/hackc/syntax.py | 31 +++++++++++++++++++++---------- 2 files changed, 35 insertions(+), 18 deletions(-) (limited to 'projects/hackc') diff --git a/projects/hackc/parser.py b/projects/hackc/parser.py index 9a927c6..2c34d1b 100644 --- a/projects/hackc/parser.py +++ b/projects/hackc/parser.py @@ -35,6 +35,12 @@ class Parser: self._extensions = extensions self.tokens = [] + # load source code + input_file = open(fp) + self.source = input_file.read() + self.lines = self.source.splitlines() + input_file.close() + def print_tokens(self): print("LINE\tCOL\tTYPE\tTOKEN") for token in self.tokens: @@ -42,16 +48,10 @@ class Parser: print(f"===== {len(self.tokens)} tokens =====") def tokenize(self): - # read file - input_file = open(self._fp) - source_code = input_file.read() - source_lines = source_code.splitlines() - input_file.close() - # tokenize code self.tokens = [] in_multicomment = False # True when inside /* */ - for line_no, line in enumerate(source_lines): + for line_no, line in enumerate(self.lines): pos = 0 # current position in line line_width = len(line) if in_multicomment: @@ -94,4 +94,10 @@ class Parser: exit(EXIT_CODE_INVALID_TOKEN) def parse(self): - syntax_tree = Class.from_tokens(self.tokens) + try: + syntax_tree = Class.from_tokens(self.tokens) + except JackSyntaxError as err: + print_err(f"{self._fp}:{err.token.line_no + 1}") + print_err(self.lines[err.token.line_no]) + print_err(" " * err.token.column + "^ " + err.message) + exit(EXIT_CODE_SYNTAX_ERROR) diff --git a/projects/hackc/syntax.py b/projects/hackc/syntax.py index c9be157..a07e69e 100644 --- a/projects/hackc/syntax.py +++ b/projects/hackc/syntax.py @@ -41,8 +41,15 @@ class Class: f"Expected `{LEFT_BRACE}`, got `{tokens[2]}` instead", tokens[2] ) - variables = Variable.from_tokens(tokens[3:]) - variables.print_verbose() + tokens_consumed = 3 + + while True: + variables, token_cnt = Variable.from_tokens(tokens[tokens_consumed:]) + if variables is None: + break + variables.print_verbose() + tokens_consumed += token_cnt + return Class(name, variables, []) @@ -53,10 +60,9 @@ class Variable: self.names = names @classmethod - def from_tokens(cls, tokens: list): + def from_tokens(cls, tokens: list) -> tuple: """Construct variable declaration statement. - - You can declare multiple variables of one scope and type on the same line. + Return a tuple of a Variable instance and number of tokens consumed. Format: ; @@ -66,7 +72,7 @@ class Variable: """ if len(tokens) < 4 or tokens[0] not in SCOPES: # not variable declaration - return None + return (None, 0) scope = tokens[0] @@ -77,10 +83,12 @@ class Variable: type = tokens[1] + tokens_consumed = 2 names = [] # names of variables expecting_identifier = True for token in tokens[2:]: + tokens_consumed += 1 if token.type == "identifier": if expecting_identifier: names.append(token) @@ -94,19 +102,22 @@ class Variable: expecting_identifier = True else: raise JackSyntaxError( - f"Expected identifier, got `,` instead", token + f"Expected variable name, got `,` instead", token ) elif token == ";": if expecting_identifier: raise JackSyntaxError( - f"Expected identifier, got `;` instead", token + f"Expected variable name, got `;` instead", token ) break + else: + expected = "variable name" if expecting_identifier else "`,` or `;`" + raise JackSyntaxError(f"Expected {expected}, got `{token}` instead", token) - return Variable(scope, type, names) + return (Variable(scope, type, names), tokens_consumed) def print_verbose(self): - print(f"Declare {len(self.names)} variables:") + print(f"Declare {len(self.names)} variable(s):") for name in self.names: print(self.scope, self.type, name) -- cgit v1.2.3