from .statements import Statement from .tokens import Token from .utils import * SCOPES = ["static", "field", "var"] VAR_TYPES = ["int", "char", "boolean"] RETURN_TYPES = ["int", "char", "boolean", "void"] SUBROUTINE_CATS = ["constructor", "method", "function"] 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' '{' * * '}' """ tokens_total = len(tokens) if tokens_total < 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] ) t = 3 variables = [] while t < tokens_total: variable, dt = Variable.from_tokens(tokens[t:], context="class") if variable is None: break variables.append(variable) t += dt subroutines = [] while t < tokens_total: subroutine, dt = Subroutine.from_tokens(tokens[t:]) if subroutine is None: break subroutines.append(subroutine) t += dt end = tokens[t] if end != RIGHT_BRACE: raise JackSyntaxError( f"Neither variable declaration nor subroutine: {end}", end ) return Class(name, variables, subroutines) def print_verbose(self): print(f"Define class {self.name}") for variable in self.variables: variable.print_verbose() for subroutine in self.subroutines: subroutine.print_verbose() print(f"End of class {self.name}") 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 VAR_TYPES and type.type != "identifier": raise JackSyntaxError(f"Expected datatype, got `{tokens[1]}` instead", type) t = 2 names = [] # names of variables expecting_identifier = True found_semicolon = False for token in tokens[2:]: t += 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 ) found_semicolon = True break else: expected = "variable name" if expecting_identifier else "`,` or `;`" raise JackSyntaxError( f"Expected {expected}, got `{token}` instead", token ) if not found_semicolon: # TODO: print caret at end of token raise JackSyntaxError(f"Missing semicolon", token) return (Variable(scope, type, names), t) def print_verbose(self): print(f"Declare {len(self.names)} variable(s):") for name in self.names: print(self.scope, self.type, name) class ParamList: def __init__(self, params: list[tuple]): self.params = params @classmethod def from_tokens(cls, tokens: list) -> tuple: """Construct parameter list of subroutine from tokens. Format: ( (, )*)? = int | char | boolean | """ if not tokens: return (None, 0) if tokens[0] == RIGHT_PAREN: # empty param list, i.e. '(' ')' return (ParamList([]), 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": raise JackSyntaxError(f"Expected datatype, got `{type}` instead", type) if not name: # TODO: print caret at end of type raise JackSyntaxError("Expected variable name", type) if name.type != "identifier": raise JackSyntaxError( f"Expected variable name, got `{name}` instead", name ) if not delim: raise JackSyntaxError(f"Expected `,` or `{RIGHT_PAREN}`", name) if delim == ",": t += 3 params.append((type, name)) continue elif delim == RIGHT_PAREN: t += 2 params.append((type, name)) break else: raise JackSyntaxError( f"Expected `,` or `{RIGHT_PAREN}`, got `{delim}` instead", delim ) return (ParamList(params), t) def print_verbose(self): print(f"Subroutine takes {len(self.params)} parameter(s):") for param in self.params: print(param[0], param[1]) class Subroutine: def __init__( 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.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 | """ if len(tokens) < 7 or tokens[0] not in SUBROUTINE_CATS: # not a subroutine return (None, 0) category = tokens[0] return_type = tokens[1] if return_type not in RETURN_TYPES and return_type.type != "identifier": raise JackSyntaxError( f"Expected datatype, got `{return_type}` instead", return_type ) name = tokens[2] if name.type != "identifier": raise JackSyntaxError( f"Expected {category} name, got `{name}` instead", name ) if tokens[3] != LEFT_PAREN: raise JackSyntaxError( f"Expected `{LEFT_PAREN}`, got `{tokens[3]}` instead", tokens[3] ) t = 4 params, dt = ParamList.from_tokens(tokens[t:]) if params is None: raise JackSyntaxError("Expected parameter list", tokens[t]) # it can be safely assumed that the next token is ')' 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, variables, statements), t) def print_verbose(self): print(f"Define {self.category} {self.type} {self.name}") self.params.print_verbose() for variable in self.variables: variable.print_verbose() for statement in self.statements: statement.print_verbose() print(f"End of {self.category} {self.name}")