summaryrefslogtreecommitdiff
path: root/projects
diff options
context:
space:
mode:
Diffstat (limited to 'projects')
-rw-r--r--projects/hackc/classes.py95
-rw-r--r--projects/hackc/expressions.py76
-rw-r--r--projects/hackc/statements.py49
3 files changed, 195 insertions, 25 deletions
diff --git a/projects/hackc/classes.py b/projects/hackc/classes.py
index 14b3105..8dc1dd5 100644
--- a/projects/hackc/classes.py
+++ b/projects/hackc/classes.py
@@ -1,3 +1,4 @@
+from .statements import Statement
from .tokens import Token
from .utils import *
@@ -42,25 +43,31 @@ class Class:
f"Expected `{LEFT_BRACE}`, got `{tokens[2]}` instead", tokens[2]
)
- tokens_consumed = 3
+ t = 3
variables = []
- while tokens_consumed < tokens_total:
- variable, token_cnt = Variable.from_tokens(
- tokens[tokens_consumed:], context="class"
- )
+ while t < tokens_total:
+ variable, dt = Variable.from_tokens(tokens[t:], context="class")
if variable is None:
break
variables.append(variable)
- tokens_consumed += token_cnt
+ t += dt
subroutines = []
- while tokens_consumed < tokens_total:
- subroutine, token_cnt = Subroutine.from_tokens(tokens[tokens_consumed:])
+ while t < tokens_total:
+ subroutine, dt = Subroutine.from_tokens(tokens[t:])
if subroutine is None:
break
subroutines.append(subroutine)
- tokens_consumed += token_cnt
+ t += dt
+
+ if t == tokens_total:
+ raise JackSyntaxError(f"Class {self.name} ends unexpectedly", tokens[-1])
+ end = tokens[t]
+ if end != RIGHT_BRACE:
+ raise JackSyntaxError(
+ f"Neither variable declaration nor subroutine: {end}", end
+ )
return Class(name, variables, subroutines)
@@ -112,13 +119,13 @@ class Variable:
if type not in VAR_TYPES and type.type != "identifier":
raise JackSyntaxError(f"Expected datatype, got `{tokens[1]}` instead", type)
- tokens_consumed = 2
+ t = 2
names = [] # names of variables
expecting_identifier = True
found_semicolon = False
for token in tokens[2:]:
- tokens_consumed += 1
+ t += 1
if token.type == "identifier":
if expecting_identifier:
names.append(token)
@@ -149,7 +156,7 @@ class Variable:
# TODO: print caret at end of token
raise JackSyntaxError(f"Missing semicolon", token)
- return (Variable(scope, type, names), tokens_consumed)
+ return (Variable(scope, type, names), t)
def print_verbose(self):
print(f"Declare {len(self.names)} variable(s):")
@@ -177,7 +184,7 @@ class ParamList:
# empty param list, i.e. '(' ')'
return (ParamList([]), 2)
- tokens_consumed = 0
+ t = 0
params = []
for type, name, delim in zip(tokens[0::3], tokens[1::3], tokens[2::3]):
if type not in VAR_TYPES and type.type != "identifier":
@@ -192,11 +199,11 @@ class ParamList:
if not delim:
raise JackSyntaxError(f"Expected `,` or `{RIGHT_PAREN}`", name)
if delim == ",":
- tokens_consumed += 3
+ t += 3
params.append((type, name))
continue
elif delim == RIGHT_PAREN:
- tokens_consumed += 2
+ t += 2
params.append((type, name))
break
else:
@@ -204,7 +211,7 @@ class ParamList:
f"Expected `,` or `{RIGHT_PAREN}`, got `{delim}` instead", delim
)
- return (ParamList(params), tokens_consumed)
+ return (ParamList(params), t)
def print_verbose(self):
print(f"Subroutine takes {len(self.params)} parameter(s):")
@@ -214,20 +221,27 @@ class ParamList:
class Subroutine:
def __init__(
- self, category: Token, type: Token, name: Token, params: ParamList, body
+ self,
+ category: Token,
+ type: Token,
+ name: Token,
+ params: ParamList,
+ variables: list,
+ statements: list,
):
self.category = category
self.type = type
self.name = name
self.params = params
- self.body = body
+ self.variables = variables
+ self.statements = statements
@classmethod
def from_tokens(cls, tokens: list) -> tuple:
"""Construct subroutine from tokens.
Format:
- <category> <return type> <name> '(' <paramlist> ')' '{' <body> '}'
+ <category> <return type> <name> '(' <paramlist> ')' '{' <variable>* <statement>* '}'
<category> = constructor | method | function
<return type> = int | char | boolean | void | <class>
@@ -251,17 +265,48 @@ class Subroutine:
if tokens[3] != LEFT_PAREN:
raise JackSyntaxError(
- f"Expected `{LEFT_PAREN}`, got `{tokens[3]}`", tokens[3]
+ f"Expected `{LEFT_PAREN}`, got `{tokens[3]}` instead", tokens[3]
)
- tokens_consumed = 4
- params, token_cnt = ParamList.from_tokens(tokens[tokens_consumed:])
+ t = 4
+ params, dt = ParamList.from_tokens(tokens[t:])
if params is None:
- raise JackSyntaxError("Expected parameter list", tokens[tokens_consumed])
+ raise JackSyntaxError("Expected parameter list", tokens[t])
# it can be safely assumed that the next token is ')'
- tokens_consumed += token_cnt + 1
+ t += dt + 1
+
+ # TODO: catch IndexError
+ body_open = tokens[t]
+ if body_open != LEFT_BRACE:
+ raise JackSyntaxError(
+ f"Expected `{LEFT_BRACE}`, got `{body_open}` instead", body_open
+ )
+ t += 1
+
+ variables = []
+ while t < len(tokens):
+ variable, dt = Variable.from_tokens(tokens[t:], context="subroutine")
+ if variable is None:
+ break
+ variables.append(variable)
+ t += dt
+
+ statements = []
+ while t < len(tokens):
+ statement, dt = Statement.from_tokens(tokens[t:])
+ if statement is None:
+ break
+ statements.append(statement)
+ t += dt
+
+ body_close = tokens[t]
+ if body_close != RIGHT_BRACE:
+ raise JackSyntaxError(
+ f"Expected `{RIGHT_BRACE}`, got `{body_close}` instead", body_close
+ )
+ t += 1
- return (Subroutine(category, return_type, name, params, None), tokens_consumed)
+ return (Subroutine(category, return_type, name, params, variables, statements), t)
def print_verbose(self):
print(f"Define {self.category} {self.type} {self.name}")
diff --git a/projects/hackc/expressions.py b/projects/hackc/expressions.py
new file mode 100644
index 0000000..de58528
--- /dev/null
+++ b/projects/hackc/expressions.py
@@ -0,0 +1,76 @@
+from .tokens import Token
+
+OPS = "+-*/&|<>="
+UNARY_OPS = "-~"
+CONSTANTS = ["true", "false", "null", "this"]
+
+
+class Term:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def from_tokens(cls, tokens: list) -> tuple:
+ if not tokens:
+ return (None, 0)
+ if tokens[0].type in ["integer", "string"] or tokens[0] in CONSTANTS:
+ return (ConstantTerm(tokens[0]), 1)
+ if tokens[0] in UNARY_OPS:
+ term, dt = Term.from_tokens(tokens[1:])
+ return (UnaryTerm(tokens[0], term), dt + 1)
+
+
+class ConstantTerm:
+ def __init__(self, term: Token):
+ self.term = term
+
+
+class VarTerm:
+ def __init__(self, var: Token, subscript=None):
+ self.var = var
+ self.subscript = subscript
+
+
+class UnaryTerm:
+ def __init__(self, op: Token, term: Term):
+ self.op = op
+ self.term = term
+
+
+class SubroutineCall:
+ def __init__(self, name: Token, exprs: list):
+ self.name = name
+ self.exprs = exprs
+
+ @classmethod
+ def from_tokens(cls, tokens: list) -> tuple:
+ pass
+
+
+class Expression:
+ def __init__(self, lhs: Term, op=None, rhs=None):
+ self.lhs = lhs
+ self.op = op
+ self.rhs = rhs
+
+ @classmethod
+ def from_tokens(cls, tokens: list) -> tuple:
+ """Construct expression.
+
+ Format:
+ <term> (<op> <term>)?
+ """
+ lhs, dt = Term.from_tokens(tokens)
+ t = dt
+
+ op = tokens[t]
+ if op.token not in OPS:
+ return (Expression(lhs), t)
+
+ t += 1
+ rhs, dt = Term.from_tokens(tokens[t])
+ if rhs is None:
+ raise JSE(f"Expected other term, got `{rhs}` instead", rhs)
+ t += dt
+
+ return (Expression(lhs, op, rhs), t)
diff --git a/projects/hackc/statements.py b/projects/hackc/statements.py
new file mode 100644
index 0000000..e8bc0b3
--- /dev/null
+++ b/projects/hackc/statements.py
@@ -0,0 +1,49 @@
+from .expressions import Expression
+
+class Statement:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def from_tokens(cls, tokens: list) -> tuple:
+ for StatementClass in [LetStatement]:
+ stmt, dt = StatementClass.from_tokens(tokens)
+ if stmt is not None:
+ return (stmt, dt)
+
+ return (None, 0)
+
+class LetStatement:
+ def __init__(self, name, expr):
+ self.name = name
+ self.expr = expr
+
+ @classmethod
+ def from_tokens(cls, tokens: list) -> tuple:
+ """Construct let statement.
+
+ Format:
+ 'let' <name> '=' <expression> ';'
+ """
+ if len(tokens) < 5 or tokens[0] != "let":
+ return (None, 0)
+
+ name = tokens[1]
+ if name.type != "identifier":
+ raise JackSyntaxError(f"Expected variable name, got `{name}` instead", name)
+
+ if tokens[2] != "=":
+ raise JackSyntaxError(f"Expected `=`, got `{tokens[2]}` instead", tokens[2])
+
+ t = 3
+ expr, dt = Expression.from_tokens(tokens[t:])
+ if expr is None:
+ raise JackSyntaxError(f"Expected expression", tokens[3])
+ t += dt
+
+ if tokens[t] != ";":
+ raise JackSyntaxError(f"Expected `;`, got `{tokens[t]}` instead", tokens[t])
+
+ t += 1
+ return (LetStatement(name, expr), t)
+