from .tokens import Token from .utils import * SCOPES = ["static", "field", "var"] PRIMITIVE_TYPES = ["int", "char", "boolean"] class Class: def __init__(self, name: Token, variables: list, subroutines: list): self.name = name self.variables = variables self.subroutines = subroutines @classmethod def from_tokens(cls, tokens: list): """Construct a class from a list of tokens. In standard Jack, one file is exactly one class. Format: class { } """ if len(tokens) < 4: return None if tokens[0] != "class": raise JackSyntaxError( f"Expected `class`, got `{tokens[0]}` instead", tokens[0] ) name = tokens[1] if name.type != "identifier": raise JackSyntaxError(f"You cannot name a class `{name}`", name) if tokens[2] != LEFT_BRACE: raise JackSyntaxError( f"Expected `{LEFT_BRACE}`, got `{tokens[2]}` instead", tokens[2] ) tokens_consumed = 3 while True: variables, token_cnt = Variable.from_tokens( tokens[tokens_consumed:], context="class" ) if variables is None: break variables.print_verbose() tokens_consumed += token_cnt return Class(name, variables, []) class Variable: def __init__(self, scope: Token, type: Token, names: list[Token]): self.scope = scope self.type = type self.names = names @classmethod def from_tokens(cls, tokens: list, context: str) -> tuple: """Construct variable declaration statement from a list of tokens. Return a tuple of an instance of Variable and number of tokens consumed. When `tokens` does not begin with a variable declaration, return (None, 0). context -- "class" ( = static | field) or "subroutine" ( = var) Format: ; = static | field | var = int | char | boolean | """ if len(tokens) < 4 or tokens[0] not in SCOPES: # not variable declaration return (None, 0) scope = tokens[0] if scope in ["static", "field"] and context != "class": raise JackSyntaxError( f"You cannot declare a {scope} variable in a subroutine", scope ) if scope == "var" and context != "subroutine": raise JackSyntaxError( f"You cannot declare a local variable outside of a subroutine", scope, ) type = tokens[1] if type not in PRIMITIVE_TYPES and type.type != "identifier": raise JackSyntaxError(f"Expected datatype, got `{tokens[1]}` instead", type) 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) expecting_identifier = False else: raise JackSyntaxError(f"Expected `,`, got `{token}` instead", token) elif token == ",": if not expecting_identifier: expecting_identifier = True else: raise JackSyntaxError( f"Expected variable name, got `,` instead", token ) elif token == ";": if expecting_identifier: raise JackSyntaxError( 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), tokens_consumed) def print_verbose(self): print(f"Declare {len(self.names)} variable(s):") for name in self.names: print(self.scope, self.type, name) class Subroutine: def __init__(self): pass class Statement: def __init__(self): super().__init__() class IfStatement(Statement): def __init__(self): super().__init__()