class CompilationEngine:
    """Recursive top-down parser"""

    def __init__(self, inFile, outFile):
        """Creates a new compilation engine with the given input and output.
        The next routine called must be compileClass()"""
        self.tokenizer = JackTokenizer(inFile)
        self.targetFile = open(outFile, 'w')
        self.getNext()
        self.classTable = None
        self.className = ''
        self.writer = VMWriter(outFile)
        self.labelWhile = 1
        self.labelIf = 1

    def getNext(self):
        if self.tokenizer.hasMoreTokens():
            self.tokenizer.advance()

    def compileClass(self):
        """Compiles a complete class"""
        self.classTable = SymbolTable()
        # 'class' className '{' classVarDec* subroutineDec* '}'
        # class
        self.getNext()
        # className
        self.className = self.tokenizer.getToken()
        self.getNext()
        # {
        self.getNext()

        token = self.tokenizer.getToken()
        while token in ["static", "field"]:
            self.compileDec()
            token = self.tokenizer.getToken()

        token = self.tokenizer.getToken()
        while token in ["constructor", "function", "method"]:
            self.compileSubroutine()
            token = self.tokenizer.getToken()
        # }
        self.getNext()


    def compileSubroutine(self):
        """Compiles a complete method, function, or constructor."""
        # subroutine dec
        self.classTable.startSubroutine()
        # ('constructor' | 'function' | 'method') ('void' | type) subroutineName '(' parameterList ')' subroutineBody
        # ('constructor' | 'function' | 'method')
        subroutineType = self.tokenizer.getToken()
        self.getNext()
        # ('void' | type)
        self.getNext()

        # subroutineName
        name = self.tokenizer.getToken()
        self.getNext()
        # (
        self.getNext()
        # parameterList
        self.compileParameterList(subroutineType == 'method')
        # )
        self.getNext()

        # subroutine body
        # '{' varDec* statements '}'
        # {
        self.getNext()

        # varDec*
        while self.tokenizer.getToken() == 'var':
            self.compileDec()
        numOfVars = self.classTable.varCount(Toolbox.VAR)

        if subroutineType == 'function':
            self.writer.writeFunction(self.className + "." + name, numOfVars)
        elif subroutineType == 'constructor':
            self.writer.writeFunction(self.className + "." + name, numOfVars)
            # push constant (num of fields)
            # call Memory.alloc 1
            # pop pointer 0
            fields = self.classTable.varCount(Toolbox.FIELD)
            self.writer.writePush(Toolbox.CONST, fields)
            self.writer.writeCall('Memory.alloc', 1)
            self.writer.writePop(Toolbox.POINTER, 0)
        else:  # method
            self.writer.writeFunction(self.className + "." + name, numOfVars)
            # push argument 0
            # pop pointer 0
            self.writer.writePush(Toolbox.SEG_ARG, 0)
            self.writer.writePop(Toolbox.POINTER, 0)

        # statements
        self.compileStatements()
        # }
        self.getNext()

    def compileParameterList(self, method=False):
        """Compiles a (possibly empty) parameter list,
        not including the enclosing "()"."""
        tokenType, name = '', ''

        if method:  # Add this to method's var list.
            self.classTable.define(None, None, Toolbox.ARG)

        if self.tokenizer.tokenType() != self.tokenizer.SYMBOL:  # param list not empty
            while True:
                tokenType = self.tokenizer.getToken()
                self.getNext()

                name = self.tokenizer.getToken()
                self.classTable.define(name, tokenType, Toolbox.ARG)
                self.getNext()

                if self.tokenizer.getToken() == ')':
                    break

                self.getNext()  # ','

    def compileStatements(self):  # (letStatement | ifStatement | whileStatement | doStatement | returnStatement)*
        """Compiles a sequence of statements,
        not including the enclosing "{}"."""
        token = self.tokenizer.getToken()
        while token in ["let", "if", "while", "do", "return"]:
            if token == 'let':
                self.compileLet()
            elif token == 'if':
                self.compileIf()
            elif token == 'while':
                self.compileWhile()
            elif token == 'do':
                self.compileDo()
            elif token == 'return':
                self.compileReturn()
            token = self.tokenizer.getToken()

    def compileSubroutineCall(self, name, printIdentifier=True):
     # subroutineName '(' expressionList ') ' | ( className | varName) '.' subroutineName '(' expressionList ') '

        var = None
        nArgs = 0
        if printIdentifier:
            # subroutineName | ( className | varName)
            self.getNext()

        var = self.classTable.searchScope(name)

        if self.tokenizer.getToken() == '.':
            if var:
                # push <this>
                self.writer.writePush(var[0], var[1])
                nArgs += 1
                className = var[2]  # Use the type instead of the variable name
            else:
                className = name
            self.getNext()
            subroutineName = self.tokenizer.getToken()
            self.getNext()
        else:
            # push <this>
            self.writer.writePush(Toolbox.POINTER, 0)
            nArgs += 1
            className = self.className
            subroutineName = name

        name = className + '.' + subroutineName
        # '('
        self.getNext()
        nArgs += self.compileExpressionList()

        self.writer.writeCall(name, nArgs)
        # ')'
        self.getNext()

    def compileDo(self):  # 'do' subroutineCall ';'
        """Compiles a do statement"""
        # do
        self.getNext()
        # subroutineCall
        self.compileSubroutineCall(self.tokenizer.getToken())
        self.writer.writePop(Toolbox.TEMP, 0)
        # ;
        if self.tokenizer.getToken() == ';':
            self.getNext()

    def compileLet(self):  # 'let' varName ('[' expression ']')? '=' expression ';'
        """Compiles a let statement"""
        # let
        # self.targetFile.write(T_LET)
        self.getNext()
        # var name
        name = self.tokenizer.getToken()
        # search scope
        segment, index, type = self.classTable.searchScope(name)

        self.getNext()
        # [
        array = False
        if self.tokenizer.getToken() == '[':
            array = True
            self.writer.writePush(segment, index)
            self.getNext()
            # expression
            self.compileExpression()
            # ]
            self.getNext()
            self.writer.writeArithmetic('add')
        # =
        self.getNext()
        # expression
        self.compileExpression()

        if array:
            self.writer.writePop(Toolbox.TEMP, 0)
            self.writer.writePop(Toolbox.TEMP, 1)
            self.writer.writePush(Toolbox.TEMP, 0)
            self.writer.writePush(Toolbox.TEMP, 1)

            self.writer.writePop(Toolbox.POINTER, 1)
            self.writer.writePop(Toolbox.THAT, 0)
        else:
            self.writer.writePop(segment, index)

        # ;
        token = self.tokenizer.getToken()
        if token == ';':
            self.getNext()

    def compileWhile(self):  # while' '(' expression ')' '{' statements '}'
        """Compiles a while statement"""
        # while
        label = str(self.labelWhile)
        self.labelWhile += 1
        self.writer.writeLabel('while' + label)
        self.getNext()
        # (
        self.getNext()
        # expression
        self.compileExpression()
        # )
        self.getNext()
        self.writer.writeArithmetic('not')
        self.writer.writeIf('endwhile' + label)
        # {
        self.getNext()
        # statements
        self.compileStatements()
        # }
        self.getNext()
        self.writer.writeGoto('while' + label)
        self.writer.writeLabel('endwhile' + label)

    def compileReturn(self):  # 'return' expression? ';'
        """Compiles a return statement"""
        # return
        self.getNext()
        # expression
        if not (self.tokenizer.getToken() == ";"):
            self.compileExpression()
        else:
            self.writer.writePush(Toolbox.CONST, 0)
        self.writer.writeReturn()
        # ;
        self.getNext()

    def compileIf(self):  # 'if' '(' expression ')' '{' statements '}' ( 'else' '{' statements '}' )?
        """Compiles an if statement, possibly with a trailing else clause"""
        # if
        label = 'if' + str(self.labelIf)
        self.labelIf += 1

        self.getNext()
        # (
        self.getNext()
        # expression
        self.compileExpression()
        # )
        self.getNext()

        self.writer.writeArithmetic('not')
        self.writer.writeIf('else' + label)

        # {
        self.getNext()
        # statements
        self.compileStatements()
        # }
        self.getNext()

        self.writer.writeGoto('end' + label)
        self.writer.writeLabel('else' + label)

        # else
        if self.tokenizer.getToken() == 'else':
            self.getNext()
            # {
            self.getNext()
            # expression
            self.compileStatements()
            # }
            self.getNext()
        self.writer.writeLabel('end' + label)

    def compileExpression(self):
        """Compiles an expression"""
        # term (op term)*

        self.compileTerm()
        token = self.tokenizer.getToken()
        while token in ['+', '/', '-', '*', '&', '|', '>', '<', '=']:
            self.getNext()
            self.compileTerm()
            self.writer.writeArithmetic(token)

            token = self.tokenizer.getToken()


    def compileTerm(self):  #integerConstant | stringConstant | keywordConstant | varName | varName '[' expression']' |
                            # subroutineCall | '(' expression ')' | unaryOp term
        """Compiles a term"""

        token = self.tokenizer.getToken()
        tokenType = self.tokenizer.tokenType()

        if tokenType == self.tokenizer.INT_CONST:
            self.writer.writePush(Toolbox.CONST, token)
            self.getNext()
        elif tokenType == self.tokenizer.STRING_CONST:
            self.writer.writePush(Toolbox.CONST, len(token))
            self.writer.writeCall('String.new', 1)

            for c in token:
                self.writer.writePush(Toolbox.CONST, ord(c))
                self.writer.writeCall('String.appendChar', 2)

            self.getNext()
        elif tokenType == self.tokenizer.KEYWORD:  # true | false | null | this
            self.compileKeywordConstant(token)
        elif tokenType == self.tokenizer.IDENTIFIER:
            name = token
            self.getNext()
            token = self.tokenizer.getToken()
            if token == '[':
                self.compileVarName(name)
                self.getNext()
                self.compileExpression()
                self.getNext()
                self.writer.writeArithmetic('add')
                self.writer.writePop(Toolbox.POINTER, 1)
                self.writer.writePush(Toolbox.THAT, 0)
            elif token in ['(', '.']:
                self.compileSubroutineCall(name, False)
            else:
                self.compileVarName(name)

        elif token == '(':
            self.getNext()
            self.compileExpression()
            self.getNext()
        elif token in ['-', '~']:
            self.compileUnary(token)


    def compileExpressionList(self):
        """Compiles a (possibly empty) comma separated list of expressions"""
        nArgs = 0

        if self.tokenizer.getToken() != ')':
            self.compileExpression()
            nArgs += 1

            while self.tokenizer.getToken() == ',':
                self.getNext()
                self.compileExpression()
                nArgs += 1

        return nArgs

    def compileDec(self):  # 'var' type varName (',' varName)* ';'
        """Compiles a var declaration"""
        # keyword 'var'
        token = self.tokenizer.getToken()
        kind = None
        if token == 'var':
            kind = Toolbox.VAR
        elif token == 'field':
            kind = Toolbox.FIELD
        elif token == 'static':
            kind = Toolbox.STATIC
        self.getNext()
        tokenType = self.tokenizer.getToken()

        # type can be an identifier or a keyword
        self.getNext()

        # var name
        name = self.tokenizer.getToken()
        self.classTable.define(name, tokenType, kind)
        self.getNext()
        while self.tokenizer.tokenType() == self.tokenizer.SYMBOL and self.tokenizer.getToken() == ',':
            # ,
            self.getNext()
            name = self.tokenizer.getToken()
            self.classTable.define(name, tokenType, kind)
            # var name
            self.getNext()
        # ;
        self.getNext()

    def compileVarName(self, name):
        segment, index, type = self.classTable.searchScope(name)
        self.writer.writePush(segment, index)

    def compileKeywordConstant(self, keyword):
        if keyword == 'false' or keyword == 'null':
            self.writer.writePush(Toolbox.CONST, 0)
        if keyword == 'true':
            self.writer.writePush(Toolbox.CONST, 0)
            self.writer.writeArithmetic('not')
        if keyword == 'this':
            self.writer.writePush(Toolbox.POINTER, 0)
        self.getNext()

    def compileUnary(self, token):
        """
        Compiles an unary operator with its operand (term)
        :param token: unary token
        """
        self.getNext()  # '~' or '-'
        self.compileTerm()  # operand

        if token == '-':
            self.writer.writeArithmetic('neg')
        else:  # token is '~'
            self.writer.writeArithmetic('not')