summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrederick Yin <fkfd@fkfd.me>2022-08-30 15:21:25 +0800
committerFrederick Yin <fkfd@fkfd.me>2022-08-30 15:21:25 +0800
commit43df98ea8f491b31b580fd909e92d5fea486599d (patch)
tree1358545301a13413428859bbbe67cf0c279e7e75
parentb439d663a3f3d4d275f07339c1c0e794808f67d9 (diff)
hackc: print syntax error message
-rw-r--r--projects/hackc/parser.py22
-rw-r--r--projects/hackc/syntax.py31
2 files changed, 35 insertions, 18 deletions
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:
<scope> <type> <one or more names, joined with a comma>;
@@ -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)