from .statements import StatementList 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 UnexpectedToken("class", 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 UnexpectedToken(LEFT_BRACE, 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 UnexpectedToken("datatype", 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 UnexpectedToken(",", token) elif token == ",": if not expecting_identifier: expecting_identifier = True else: raise UnexpectedToken("variable name", token) elif token == ";": if expecting_identifier: raise UnexpectedToken("variable name", token) found_semicolon = True break else: expected = "variable name" if expecting_identifier else "`,` or `;`" raise UnexpectedToken(expected, 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 UnexpectedToken("datatype", type) if not name: # TODO: print caret at end of type raise JackSyntaxError("Expected variable name", type) if name.type != "identifier": raise UnexpectedToken("variable name", name) if not delim: raise UnexpectedToken(f"`,` 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 UnexpectedToken(f"`,` or `{RIGHT_PAREN}`", 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: StatementList, ): 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 UnexpectedToken("datatype", return_type) name = tokens[2] if name.type != "identifier": raise UnexpectedToken(f"{category} name", name) if tokens[3] != LEFT_PAREN: raise UnexpectedToken(LEFT_PAREN, 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 UnexpectedToken(LEFT_BRACE, 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, dt = StatementList.from_tokens(tokens[t:]) t += dt body_close = tokens[t] if body_close != RIGHT_BRACE: raise UnexpectedToken(RIGHT_BRACE, 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() self.statements.print_verbose() print(f"End of {self.category} {self.name}")