# CROSS ASSEMBLER FOR THE LITTLE MAN COMPUTER MK. V
#
# INPUT FILE POSSIBLE LINE FORMATS:
# LABEL: <spaces> OPCODE <spaces> TARGET <spaces> IGNORED
#        <spaces> OPCODE <spaces> TARGET <spaces> IGNORED
# LABEL: <spaces> "EQU" <spaces> HEXBYTE <spaces> IGNORED
#        <spaces> "*" IGNORED

import sys

#####
##### DECLARATIONS
#####
i = 0
_Start = 0x0100         # base address for the assembled machine code
_Line = ''
_Address = _Start
_Inc = 0x00             # Offset of next OpCode

_ThreeBytes = ['ADD', 0x10, 'SUB', 0x20, 'STA', 0x30, 'AND', 0x40, 'LDA', 0x50, 'JMP', 0x60, 'BRZ', 0x70, 'BRB', 0x7b, 'BRF', 0x7f]
_SingleByte = ['HLT', 0x00, 'INP', 0x93, 'OUT', 0x94, 'RDT', 0x95, 'LSR', 0xf2, 'LSL', 0xf3, 'INC', 0xf4, 'DEC', 0xf5, 'NOP', 0xff]

_Labels = [ ]

#####
##### ACCESS COMMANDD LINE ARGUMENT
#####
if len(sys.argv) != 2:
  raise SystemExit('Error: Incorrect Number of Arguments.')
_AsmFile = (str(sys.argv[1]))           # _AsmFile now holds name of .asm source file

#####
##### OPEN FILES
#####
_Target = open('.scratch.pad', 'w')     # scratch.pad is the intermediate 'object file'
_Source = open(_AsmFile, 'r')           # The source code file from CL Argument

#####
##### FIRST PASS: TOKENIZE, NUMBER LINES, LIST LABEL ADDRESSES
#####
for _Line in _Source:
  _Line = _Line.replace('\t', ' ')             # Clean out tabs
  _Line = _Line.replace('\n', '')              # Remove new lines
  _Tokens = _Line.split()                      # Tokenize

  for _Token in _Tokens:                       # Examine each token
     if _Token.find(':') >= 1:                 # If this _Token is a 'Label:'...
        _Labels.append(_Token[:-1])            # then add it to the list of labels
        _Labels.append(_Address)               # followed by its address
        continue

     if _Token in _ThreeBytes:                 # Token denotes Three Byte Opcode
        i = _ThreeBytes.index(_Token)
        _Target.write(hex(_ThreeBytes[i+1]))
        _Target.write('    ')
        _Address = _Address + 3
        i = _Tokens.index(_Token)
        _Target.write(_Tokens[i+1])
        continue

     if _Token in _SingleByte:                 # Token denotes Single Byte Opcode
        i = _SingleByte.index(_Token)
        _Target.write(hex(_SingleByte[i+1]))
        _Target.write('    ')
        _Address = _Address + 1
        continue

     if _Token == 'EQU':                       # EQU is a Compiler Symbol
        i = _Tokens.index(_Token)
        _Target.write(_Tokens[i+1])
        _Address = _Address + 1
        continue

     if _Token == '*':                         # Denotes this line is a comment
        continue

  _Target.write('\n')

_Target.close()
_Source.close()

#####
##### SECOND PASS - RAPLACE TARGETS WITH HEX ADDRESSES
#####

_Object = open('.object.obj', 'w')
_Scratch = open('.scratch.pad', 'r')

_Byte = 0x00
_MaskFF00 = 0xFF00
_Mask00FF = 0x00FF

for _Line in _Scratch:                          # READ INTEREM CODE FROM SCRATCH
  _Object.write(_Line[0:4])                    # PASS OPCODE BYTE TO OUTPUT FILE
  _Found = False                               # NO LABEL FOUND YET

  for _Label in _Labels:                       # SEARCH THROUGH ALL LABELS
     if str(_Label) in _Line:                  # IF THE CURRENT LABEL IS IN CURRENT LINE...
        i = _Labels.index(_Label)              #   NOTE ITS INDEX
        j = i + 1                              #   AND THE INDEX OF THE ASSOCIATED ADDRESS
        if len(_Labels) > j:                   # IF WE'RE NOT AT THE END OF LABELS
           _Labels[i+1] = int(_Labels[i+1])    #   HEXIFY IT

           _Byte = str(int(((_Labels[i+1]) & (_MaskFF00)) / 256))      # ISOLATE HI ORDER BYTE
           _Object.write(' 0x')
           _Object.write(_Byte.rjust(2, '0'))

           _Byte = str(int(((_Labels[i+1]) & (_Mask00FF)) / 1))        # ISOLATE LO ORDER BYTE
           _Object.write(' 0x')
           _Object.write(_Byte.rjust(2, '0'))

           _Found = True                       #   NOTE THAT LABEL FOUND

  _Object.write('\n')

_Object.close()
_Scratch.close()

#####
##### THIRD PASS - TURN OBJECT FILE INTO BINARY EXECUTABLE
#####

_Object = open('.object.obj', 'r')
_Tape = open('tape', 'w')

for _Line in _Object:
  for _Byte in _Line.split():
     _Num = int(_Byte, 16)
     _Tape.write(chr(_Num))