#!/usr/bin/python3
# python3 backend for comun
# to make this faster use pypy3, i.e. replace the BOTH shebangs in this script
# with #!/usr/bin/pypy3

# TODO: make it just one pass? (see how C backend does it)

import os

INDENT = 2

headerBytesLeft = 8
indent = 0
readWhile = False
readIf = False
inFunc = False
bytecode = []
sourceLine = 0

def readByte():
  return os.read(0,1)

def ptrIToPtrTableI(n):
  return 0 if n < 16 else (n - 15)

def ptrIToAddrSubStr(n):
  return "" if n > 15 or n == 0 else (" - " + str(n))

def funAddrToNum(a):
  a -= 1

  while bytecode[a * 2] != 0b00000010:
    a -= 1

  return a

for i in range(8): # skip header
  readByte()

while True:
  b1 = readByte()
  b2 = readByte()

  if b1 == b"" or len(b2) == b"":
    break

  bytecode.append(ord(b1))
  bytecode.append(ord(b2))

  if b1 == 0b00000000:
    break

print("#!/usr/bin/python3")
print("import os, sys")
print("def readByte():\n  return os.read(0,1)\n")
print("arr = ([],[],[],[], [],[],[],[]) # mems, ptrs\n") # arrays: mem0, mem8, mem16, mem32, ptrs0, ptrs8, ptrs16, ptrs32

teVals = ("0x100000000","0x100","0x10000","0x100000000","","","","")

print("def nop():\n  pass\n")

print("# array set/get funcs")

for i in range(8):
  vals = teVals[i]
  si = str(i)

  print("def as" + si + "(a,v):\n  while len(arr[" + si + "]) <= a:\n    arr[" + si + "].append(0)\n")

  if i < 4:
    print("  if v < 0:\n    v = " + vals + " - ((-1 * v) % " + vals + ")\n")

  print("  arr[" + si + "][a] = v" + ((" % " + vals) if i < 4 else "") + "\n")

  print("def ag" + si + "(a):\n  return arr[" + si + "][a] if a < len(arr[" + si + "]) else 0\n")

print("# cond. check and sig./unsig. conv. funcs:")

for i in range(4):
  print("def ch" + str(i) + "(p):\n  r = ag" + str(i) + "(ag" + str(i + 4) + "(0)) != 0\n\n  if p:")
  print("    as" + str(i + 4) + "(0,ag" + str(i + 4) + "(0) - 1)\n\n  return r\n")

  print("def s" + str(i) + "(x):\n  return (x - " + teVals[i] + ") if x >= " + teVals[i] + " / 2 else x\n");
  print("def u" + str(i) + "(x):\n  return (" + teVals[i] + " + x) if x < 0 else x\n");

print("inpDone = False\n")
print("def out(v):\n  sys.stdout.buffer.write((v % 256).to_bytes(1,'big'))\n")
print("def inp():\n  global inpDone\n  r = readByte()\n\n  if inpDone or r == b\"\":\n    inpDone = True\n    return 0\n\n  return ord(r)\n")
print("def inu():\n  return 0 if inpDone else 1\n")

print("def ini():\n  for a in sys.argv:\n    as4(0,ag4(0) + 1)\n    as0(ag4(0),0)\n\n    for i in range(len(a)):\n\n      as4(0,ag4(0) + 1)")
print("      as0(ag4(0),ord(a[len(a) - i - 1]))\n  as4(0,ag4(0) + 1)\n  as0(ag4(0),len(sys.argv))\n");

for j in range(2): # two passes: 1st functions, then top-level code
  sourceLine = 1
  addr = 0

  while (addr * 2 + 1) < len(bytecode):
    b1 = bytecode[addr * 2] 
    b2 = bytecode[addr * 2 + 1]

    b3 = b2

    const = b3 & 0b00001111
    constParts = 1

    while (b3 & 0b00010000) != 0: # read constant
      addr += 1
      b3 = bytecode[addr * 2 + 1]


      const = const | ((b3 & 0b00001111) << (4 * constParts))
      constParts += 1

    const1 = const >> int((constParts * 4) / 2)
    const2 = (const1 << int((constParts * 4) / 2)) ^ const

    typeEnv = b2 >> 6

    noPop = (b2 & 0b00100000) != 0

    indentStr = " " * (INDENT * indent)
    cmdStr = indentStr
    teStr = str(typeEnv)
    memSetStr = "as" + str(typeEnv) + "("
    memGetStr = "ag" + str(typeEnv) + "("
    ptrSetStr = "as" + str(4 + typeEnv) + "("
    ptrGetStr = "ag" + str(4 + typeEnv) + "("
    stGetStr = ptrGetStr + "0)"

    if readWhile:
      indent += 1
      indentStr += INDENT * " "

      if b1 == 0b00001010 or b1 == 0b00001011:
        cmdStr += "while ch" + str(typeEnv) + "(" + str(not noPop) + "):\n" + indentStr + "nop()"
        b1 = 0b00000000 # NOP
      else:
        cmdStr += "while True:\n" + indentStr + "nop()\n" + indentStr

    if readIf:
      cmdStr += "if ch" + str(typeEnv) + "(" + str(not noPop) + "):\n"
      cmdStr += indentStr + (INDENT * " ") + "nop()"
      indent += 1

    readIf = False        
    readWhile = False

    if (b1 >> 4) >= 2: # typical stack operation
      mode = b1 & 0b00000011

      dstOffset = -1

      xStr = memGetStr + ptrGetStr + "0))"
      yStr = memGetStr + ptrGetStr + "0) - 1)"

      if mode == 1:
        dstOffset = 0

        yStr = xStr
        xStr = str(const)
      elif mode == 2:
        dstOffset = 0
      elif mode == 3:
        dstOffset = 1

      if noPop:
        dstOffset = 1

      opStr = ""

      opcodeGroup = b1 & 0b11111100

      if opcodeGroup == 0b00100000:   # AD
        opStr = yStr + " + " + xStr
      elif opcodeGroup == 0b00100100: # SU
        opStr = yStr + " - " + xStr
      elif opcodeGroup == 0b00101000: # MU
        opStr = yStr + " * " + xStr
      elif opcodeGroup == 0b00101100: # DI
        opStr = "int(" + yStr + " / " + xStr + ")"
      elif opcodeGroup == 0b00110000: # DS
        opStr = "u" + teStr + "(int(float(s" + teStr + "(" + yStr + ")) / s" + teStr + "(" + xStr + ")))"
      elif opcodeGroup == 0b00110100: # MO
        opStr = yStr + " % " + xStr
      elif opcodeGroup == 0b00111000: # MS
        xSig = "s" + teStr + "(" + xStr + ")"
        ySig = "s" + teStr + "(" + yStr + ")"
        opStr = "u" + teStr + "(" + ySig + " - int(float(" + ySig + ") / " + xSig + ") * " + xSig + ")"
      elif opcodeGroup == 0b01000000: # GR
        opStr = "1 if " + yStr + " > " + xStr + " else 0"
      elif opcodeGroup == 0b01000100: # GE
        opStr = "1 if " + yStr + " >= " + xStr + " else 0"
      elif opcodeGroup == 0b01001000: # SM
        opStr = "1 if " + yStr + " < " + xStr + " else 0"
      elif opcodeGroup == 0b01001100: # SE
        opStr = "1 if " + yStr + " <= " + xStr + " else 0"
      elif opcodeGroup == 0b01010000: # GS
        opStr = "1 if s" + teStr + "(" + yStr + ") > s" + teStr + "(" + xStr + ") else 0"
      elif opcodeGroup == 0b01010100: # BS
        opStr = "1 if s" + teStr + "(" + yStr + ") >= s" + teStr + "(" + xStr + ") else 0"
      elif opcodeGroup == 0b01011000: # SS
        opStr = "1 if s" + teStr + "(" + yStr + ") < s" + teStr + "(" + xStr + ") else 0"
      elif opcodeGroup == 0b01011100: # LS
        opStr = "1 if s" + teStr + "(" + yStr + ") <= s" + teStr + "(" + xStr + ") else 0"
      elif opcodeGroup == 0b01100000: # EQ
        opStr = "1 if " + yStr + " == " + xStr + " else 0"
      elif opcodeGroup == 0b01100100: # NE
        opStr = "1 if " + yStr + " != " + xStr + " else 0"
      elif opcodeGroup == 0b01101000: # BA
        opStr = yStr + " & " + xStr
      elif opcodeGroup == 0b01101100: # BO
        opStr = yStr + " | " + xStr
      elif opcodeGroup == 0b01110000: # BX
        opStr = yStr + " ^ " + xStr
      elif opcodeGroup == 0b01110100: # LA
        opStr = "1 if (" + yStr + " != 0) and (" + xStr + ") != 0 else 0"
      elif opcodeGroup == 0b01111000: # LO
        opStr = "1 if (" + yStr + " != 0) or (" + xStr + ") != 0 else 0"
      elif opcodeGroup == 0b01111100: # LX
        opStr = "1 if (" + yStr + " == 0) != (" + xStr + " == 0) else 0"
      elif opcodeGroup == 0b10000000: # BN
        opStr = "~" + xStr
      elif opcodeGroup == 0b10000100: # SR
        opStr = yStr + " >> " + xStr
      elif opcodeGroup == 0b10001000: # SL
        opStr = yStr + " << " + xStr
      elif b1 == 0b11110011:          # ADR
        opStr = stGetStr
      elif b1 == 0b11111011:          # INU
        opStr = "inu()"
      elif b1 == 0b11111111:          # INP
        opStr = "inp()"

      cmdStr += memSetStr + stGetStr + " + " + str(dstOffset) + "," + opStr + ")"

      if dstOffset != 0:
        cmdStr += "\n" + indentStr + ptrSetStr + "0," + ptrGetStr + "0) + " + str(dstOffset) + ")"
    else:
      if b1 == 0b00011010:   # CON
        cmdStr += memSetStr + stGetStr + " + 1," + str(const) + ")"

        if noPop:
          cmdStr += "\n" + indentStr + ptrSetStr + "0," + ptrGetStr + "0) + 1)"

      elif b1 == 0b00010000: # PSC
        cmdStr += ptrSetStr + str(ptrIToPtrTableI(const2)) + "," + str(const1) + ")"

      elif b1 == 0b00010001: # PAC
        cmdStr += ptrSetStr + str(ptrIToPtrTableI(const2)) + "," + ptrGetStr + str(ptrIToPtrTableI(const2)) + ") + "
        cmdStr += str(const1 if const1 < 8 else (const1 - 16)) + ")"

      elif b1 == 0b00010010: # PAX
        cmdStr += ptrSetStr + str(ptrIToPtrTableI(const)) + "," + ptrGetStr + str(ptrIToPtrTableI(const))
        cmdStr += ") + s" + str(typeEnv) + "(" + memGetStr + stGetStr + ")))"

        if not noPop and const != 0:
          cmdStr += "\n" + indentStr + ptrSetStr + "0," + stGetStr + " - 1)"

      elif b1 == 0b00010110: # PCM
        cmdStr += "_v1, _v2 = " + ptrGetStr + str(ptrIToPtrTableI(const1)) + ")" + ptrIToAddrSubStr(const1) + ", "
        cmdStr += ptrGetStr + str(ptrIToPtrTableI(const2)) + ")" + ptrIToAddrSubStr(const2)
        cmdStr += "\n" + indentStr + memSetStr + stGetStr + " + 1,0 if _v1 == _v2 else (1 if _v1 > _v2 else 2))"
        cmdStr += "\n" + indentStr + ptrSetStr + "0," + stGetStr + " + 1)"

      elif b1 == 0b00001111: # INI
        cmdStr += "ini()"

      elif b1 == 0b00010011: # PCO
        cmdStr += ptrSetStr + str(ptrIToPtrTableI(const2)) + "," + ptrGetStr + str(ptrIToPtrTableI(const1)) + ")" + ptrIToAddrSubStr(const1) + ")"

      elif b1 == 0b00010100: # MEX
        cmdStr += memSetStr + ptrGetStr + str(ptrIToPtrTableI(const)) + ")" + ptrIToAddrSubStr(const) + "," + memGetStr + stGetStr + "))"

        if not noPop:
          cmdStr += "\n" + indentStr + ptrSetStr + "0," + stGetStr + " - 1)"
      elif b1 == 0b00010101: # MGE
        cmdStr += memSetStr + stGetStr + " + 1," + memGetStr + ptrGetStr
        cmdStr += str(ptrIToPtrTableI(const)) + ")" + ptrIToAddrSubStr(const) + "))\n"
        cmdStr += indentStr + ptrSetStr + "0," + stGetStr + " + 1)"
      elif b1 == 0b00010111: # PUX
        cmdStr += memSetStr + stGetStr + (" + 1" if noPop else "") + ","
        cmdStr += memGetStr + stGetStr + " - " + memGetStr + stGetStr + ")))"

        if noPop:
          cmdStr += "\n" + indentStr + ptrSetStr + "0," + stGetStr + " + 1)"
      elif b1 == 0b00011011: # CND
        offset = 1 if noPop else -2

        cmdStr += memSetStr + stGetStr + " + " + str(offset) + ","
        cmdStr += memGetStr + stGetStr + " - 1) if " + memGetStr + stGetStr + " - 2) != 0 else " + memGetStr + stGetStr + "))\n"
        cmdStr += indentStr + ptrSetStr + "0," + stGetStr + " + " + str(offset) + ")"
      elif b1 == 0b00011100: # SWP
        cmdStr += "_v1, _v2 = " + memGetStr + stGetStr + "), " + memGetStr + stGetStr + " - 1)"

        if noPop:
          cmdStr += "\n" + indentStr + ptrSetStr + "0," + stGetStr + " + 2)"

        cmdStr += "\n" + indentStr + memSetStr + stGetStr + ",_v2)"
        cmdStr += "\n" + indentStr + memSetStr + stGetStr + " - 1,_v1)"
      elif b1 == 0b00011110: # POP
        cmdStr += ptrSetStr + "0," + ptrGetStr + "0) - " + str(const + 1) + ")"
      elif b1 == 0b00000111: # CAL
        cmdStr += "f" + str(funAddrToNum(const)) + "()"
      elif b1 == 0b00001000: # CAE
        cmdStr += "fe" + str(const) + "()"
      elif b1 == 0b00011101: # TRA
        cmdStr += "as" + str(const) + "(ag" + str(4 + const) + "(0)," + memGetStr + stGetStr + "))"

        if not noPop:
          cmdStr += "\n" + indentStr + ptrSetStr + "0," + stGetStr + " - 1)"
      elif b1 == 0b00011111: # OUT
        cmdStr += "out(" + memGetStr + stGetStr + "))"

        if not noPop:
          cmdStr += "\n" + indentStr + ptrSetStr + "0," + stGetStr + " - 1)"

      elif b1 == 0b00001001: # RET
        inFunc = False
        indent = 0
      elif b1 == 0b00000010: # DES
        if const == 1:   # if
          readIf = True
        elif const == 2: # else
          cmdStr = cmdStr[:-1 * INDENT] + "else:\n"
          cmdStr += indentStr + "nop()"
        elif const == 3: # endif
          indent -= 1
        elif const == 4: # while
          readWhile = True
        elif const == 5: # break
          cmdStr += "break"
        elif const == 6: # loopend
          indent -= 1
        elif const == 7: # func
          cmdStr += "def f" + str(addr) + "():\n"
          cmdStr += (INDENT * " ") + "nop()"
          indent = 1
          inFunc = True
        elif const == 8: # exit
          cmdStr += "return" if inFunc else "quit()"
        elif const == 9: # goto
          raise Exception("goto unsupported")
        elif const == 10: # label
          cmdStr += "# label"
        elif const == 11: # new line
          sourceLine += 1
          cmdStr += "# line " + str(sourceLine)

    if len(cmdStr) > len(indentStr):
      if inFunc == (j == 0):
        print(cmdStr)

    addr += 1
