Note that this is not the optimized solution, since the translated .asm code size could be reduced further. An obvious first step might be to subroutine-ize the CALLs. The most optimized translator for Chapter 8 would generate about 70 % less instructions. As always, there's a tradeoff between lines of the translator and that of the translation. And small code doesn't necessarilly equal fast code. Nevertheless, some further optimizations are necessary. Otherwise the results of compiling, translating and assembling one's own user application (Chapter 9) and OS (Chapter 12) won't fit in the available 32 K of ROM.

import argparse
import os
from string import Template

sInit = Template('@256          \n'
                 'D=A           \n'
                 '@SP           \n'
                 'M=D           \n'
                 '$callTemplate \n'  # @Sys.init; 0;JMP
                 '(HALT)        \n'
                 '@HALT         \n'
                 '0;JMP         \n')

sEqGtLt = Template('@SP                            \n'
                   'AM=M-1                         \n'
                   'D=M                            \n'
                   'A=A-1                          \n'
                   'D=D-M                          \n'
                   'M=-1                           \n'
                   '@END$$${command}$$${uniquify}  \n'
                   'D;J${command}                  \n'
                   '@SP                            \n'
                   'A=M-1                          \n'
                   'M=0                            \n'
                   '(END$$${command}$$${uniquify}) \n')

sPushConstant = Template('@$number \n'
                         'D=A      \n'
                         '@SP      \n'
                         'AM=M+1   \n'
                         'A=A-1    \n'
                         'M=D      \n')

sPush = Template('@$base            \n'
                 'D=${fixed_A_or_M} \n'
                 '@$index           \n'
                 'A=A+D             \n'
                 'D=M               \n'
                 '@SP               \n'
                 'AM=M+1            \n'
                 'A=A-1             \n'
                 'M=D               \n')

sPop = Template('@$base            \n'
                'D=${fixed_A_or_M} \n'
                '@$index           \n'
                'D=D+A             \n'
                '@R13              \n'
                'M=D               \n'
                '@SP               \n'
                'AM=M-1            \n'
                'D=M               \n'
                '@R13              \n'
                'A=M               \n'
                'M=D               \n')

sLabel = Template('($label) \n')

sGoto = Template('@$label \n'
                 '0;JMP   \n')

sIfGoto = Template('@SP     \n'
                   'AM=M-1  \n'
                   'D=M     \n'
                   '@$label \n'
                   'D;JNE   \n')

sCall = Template('@RET$$$label  \n' # push RET address
                 'D=A           \n'
                 '@SP           \n'
                 'AM=M+1        \n'
                 'A=A-1         \n'
                 'M=D           \n'
                 '@LCL          \n' # push LCL
                 'D=M           \n'
                 '@SP           \n'
                 'AM=M+1        \n'
                 'A=A-1         \n'
                 'M=D           \n'
                 '@ARG          \n' # push ARG
                 'D=M           \n'
                 '@SP           \n'
                 'AM=M+1        \n'
                 'A=A-1         \n'
                 'M=D           \n'
                 '@THIS         \n' # push THIS
                 'D=M           \n'
                 '@SP           \n'
                 'AM=M+1        \n'
                 'A=A-1         \n'
                 'M=D           \n'
                 '@THAT         \n' # push THAT
                 'D=M           \n'
                 '@SP           \n'
                 'AM=M+1        \n'
                 'A=A-1         \n'
                 'M=D           \n'
                 '@SP           \n' # repoint ARG for called func
                 'D=M           \n'
                 '@5            \n'
                 'D=D-A         \n'
                 '@$n           \n' # number of args
                 'D=D-A         \n'
                 '@ARG          \n'
                 'M=D           \n'
                 '@SP           \n' # repoint LCL for called func
                 'D=M           \n'
                 '@LCL          \n'
                 'M=D           \n'
                 '@$function    \n'
                 '0;JMP         \n'
                 '(RET$$$label) \n')

sFunction = Template('($function)       \n'
                     '@$k               \n' # init k local vars to 0
                     'D=A               \n'
                     '@R5               \n'
                     'M=D               \n'
                     '(LOOP$$$function) \n'
                     '@R5               \n'
                     'MD=M-1            \n'
                     '@END$$$function   \n'
                     'D;JLT             \n'
                     '@SP               \n'
                     'M=M+1             \n'
                     '@LCL              \n'
                     'A=M               \n'
                     'A=A+D             \n'
                     'M=0               \n'
                     '@LOOP$$$function  \n'
                     '0;JMP             \n'
                     '(END$$$function)  \n')

sReturn = """@LCL // temp store LCL in R13
D=M
@R13
M=D
// save RET address (which is at LCL-5) in R14
@5
A=D-A
D=M
@R14
M=D
// *ARG = pop() (return value to calling function's ARG[0])
@SP
A=M-1
D=M
@ARG
A=M
M=D
// SP = ARG[1] (ARG[0] holds the returned value)
D=A+1
@SP
M=D
// THAT = *(R5 - 1)
@R13
D=M
@1
A=D-A
D=M
@THAT
M=D
// THIS = *(R5 - 2)
@R13
D=M
@2
A=D-A
D=M
@THIS
M=D
// ARG = *(R5 - 3)
@R13
D=M
@3
A=D-A
D=M
@ARG
M=D
// LCL = *(R5 - 4)
@R13
D=M
@4
A=D-A
D=M
@LCL
M=D
// goto RET
@R14
A=M
0;JMP
"""

vm2asm = {'add'      : '@SP \n AM=M-1 \n D=M \n A=A-1 \n M=D+M \n',
          'sub'      : '@SP \n AM=M-1 \n D=M \n A=A-1 \n M=M-D \n',
          'and'      : '@SP \n AM=M-1 \n D=M \n A=A-1 \n M=D&M \n',
          'or'       : '@SP \n AM=M-1 \n D=M \n A=A-1 \n M=D|M \n',
          'neg'      : '@SP \n A=M-1 \n M=-M \n',
          'not'      : '@SP \n A=M-1 \n M=!M \n',
          'eq'       : sEqGtLt,
          'gt'       : sEqGtLt,
          'lt'       : sEqGtLt,
          'push'     : sPush,
          'pop'      : sPop}

memsegment2reg = {'constant' : 'SP',
                  'local'    : 'LCL',
                  'argument' : 'ARG',
                  'this'     : 'THIS',
                  'that'     : 'THAT',
                  'temp'     : 'R5',
                  'pointer'  : 'THIS',
                  'static'   : '16'}

class Parser:

    def __init__(self, fin):
        self.lines = open(fin).read().splitlines()
        self.lines = [line for line in self.lines
                      if len(line.strip()) and
                      not line.startswith(('//', '/*'))]
        self.cur_line = ''
        self.cur_command = ''

    def hasMoreCommands(self):
        return len(self.lines) > 0

    def advance(self):
        self.cur_line = self.lines.pop(0)

    def command(self):
        self.cur_command = self.cur_line.split()[0]
        return self.cur_command

    def arg1(self):
        if self.cur_command in ('push', 'pop', 'function', 'call', 'label', 'goto', 'if-goto'):
            return self.cur_line.split()[1]
        else:
            return ''

    def arg2(self):
        if self.cur_command in ('push', 'pop', 'function', 'call'):
            return self.cur_line.split()[2]
        else:
            return ''

class CodeWriter:

    def __init__(self, fou):
        self.fou = open(fou, 'w')
        self.cur_filename = ''
        self.cur_function = ''
        self.unique_counter = 0

    def setFilename(self, filename):
        """Inform the codewriter that a translation of
        a new VM file has started.
        """
        self.cur_filename = filename
        self.cur_function = filename

    def uniquify(self, label):
        self.unique_counter += 1
        return self.cur_filename + '$' + self.cur_function + '$' + label + '$' + str(self.unique_counter)

    def uniquify2(self, label):
        return self.cur_filename + '$' + self.cur_function + '$' + label

    def write(self, command, arg1='', arg2=''):
        if command in ('push', 'pop'):
            self.writePushPop(command, arg1, arg2)
        elif command in ('add', 'sub', 'neg', 'eq', 'gt', 'lt', 'and', 'or', 'not'):
            self.writeArithmetic(command)
        elif command == 'label':
            self.writeLabel(arg1)
        elif command == 'goto':
            self.writeGoto(arg1)
        elif command == 'if-goto':
            self.writeIfGoto(arg1)
        elif command == 'call':
            self.writeCall(arg1, arg2)
        elif command == 'function':
            self.writeFunction(arg1, arg2)
        elif command == 'return':
            self.writeReturn()

    def writeInit(self):
        call = sCall.substitute(function='Sys.init', n=0, label='Sys.init')
        asm = sInit.substitute(callTemplate=call)
        self.fou.write(asm)

    def writeArithmetic(self, command):
        asm = vm2asm[command]
        if command in ('eq', 'gt', 'lt'):
            if command == 'gt':
                command='lt' # 'gt' needs 'JLT' in .asm
            elif command == 'lt':
                command='gt'
            self.unique_counter += 1
            asm = asm.substitute(command=command.upper(),
                                 uniquify=self.unique_counter)
        self.fou.write(asm)

    def writePushPop(self, command, segment, index):
        if segment == 'constant':
            asm = sPushConstant.substitute(number=index)
        else:
            asm = vm2asm[command]
            register = memsegment2reg[segment]
            if segment in ('temp', 'pointer', 'static'):
                fixed_A_or_M = 'A'
            else:
                fixed_A_or_M = 'M'
            if segment == 'static':
                register = self.cur_filename + '$' + str(index)
                index = 0
            asm = asm.substitute(base=register,
                                 index=index,
                                 fixed_A_or_M=fixed_A_or_M)
        self.fou.write(asm)

    def writeLabel(self, label):
        asm = sLabel.substitute(label=self.uniquify2(label))
        self.fou.write(asm)

    def writeGoto(self, label):
        asm = sGoto.substitute(label=self.uniquify2(label))
        self.fou.write(asm)

    def writeIfGoto(self, label):
        asm = sIfGoto.substitute(label=self.uniquify2(label))
        self.fou.write(asm)

    def writeCall(self, subroutine, nargs):
        label = self.uniquify(subroutine)
        asm = sCall.substitute(function=subroutine, n=nargs, label=label)
        self.fou.write(asm)

    def writeFunction(self, functionName, numLocals):
        self.cur_function = functionName
        asm = sFunction.substitute(function=functionName, k=numLocals)
        self.fou.write(asm)

    def writeReturn(self):
        self.fou.write(sReturn)

    def close(self):
        self.fou.close()


def main():
    argparser = argparse.ArgumentParser(description='Translate .vm to .asm')
    argparser.add_argument('filepath', help='(dir of) .vm file(s)')
    argparser.add_argument('-b', '--bootstrap', action='store_true',
                           help='prepend bootstrap .asm')
    args = argparser.parse_args()
    fin = os.path.normpath(args.filepath)

    if os.path.isdir(fin):
        files = [os.path.join(fin, file) for file in os.listdir(fin)
                 if os.path.splitext(file)[1] == '.vm']
        parent_dir = os.path.basename(os.path.normpath(fin))
        fou = os.path.join(fin, parent_dir+'.asm')
    elif os.path.isfile(fin) and os.path.splitext(fin)[1] == '.vm':
        files = [fin]
        fou = os.path.splitext(fin)[0] + '.asm'
    else:
        print('Invalid filepath and/or not a .vm file')
        import sys
        sys.exit()

    writer = CodeWriter(fou)
    if args.bootstrap:
        writer.writeInit()
    for file in files:
        writer.setFilename(os.path.basename(file))
        parser = Parser(file)
        while parser.hasMoreCommands():
            parser.advance()
            writer.write(parser.command(), parser.arg1(), parser.arg2())
    writer.close()


if __name__ == '__main__':
    main()