From 61f3490b9e6132cbad70c90637ed349a42fe7704 Mon Sep 17 00:00:00 2001 From: Frederick Yin Date: Wed, 31 Aug 2022 16:42:22 +0800 Subject: hackc: i dont even remember --- projects/hackc/classes.py | 95 +++++++++++++++++++++++++++++++------------ projects/hackc/expressions.py | 76 ++++++++++++++++++++++++++++++++++ projects/hackc/statements.py | 49 ++++++++++++++++++++++ 3 files changed, 195 insertions(+), 25 deletions(-) create mode 100644 projects/hackc/expressions.py create mode 100644 projects/hackc/statements.py 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: - '(' ')' '{' '}' + '(' ')' '{' * * '}' = constructor | method | function = int | char | boolean | void | @@ -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: + ( )? + """ + 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' '=' ';' + """ + 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) + -- cgit v1.2.3