#!/usr/bin/env python

import argparse
import os.path
import re
import string
import subprocess
import sys
import tempfile

parser = argparse.ArgumentParser(description='Process an assembly source file and output it as an ASMTest constructor invocation.')
parser.add_argument('input', nargs='+', help='Specifies the input file(s).')
parser.add_argument('-as', help='sh-elf-as path.', required=True)
parser.add_argument('-ld', help='sh-elf-ld path.', required=True)
parser.add_argument('-nm', help='sh-elf-nm path.', required=True)
parser.add_argument('-objcopy', help='sh-elf-objcopy path.', required=True)
parser.add_argument('-o', help='Specifies the output file.', required=True)
args = parser.parse_args()

def compile_asm(input_path):
  obj_file = tempfile.mktemp(suffix='.obj')
  srec_file = tempfile.mktemp(suffix='.srec')
  bin_file = tempfile.mktemp(suffix='.bin')
  subprocess.call([vars(args)['as'], '-little', '-o', obj_file, input_path])
  subprocess.call([vars(args)['ld'], '--oformat', 'srec', '-Ttext', '0x8c010000', '-e', '0x8c010000', '-o', srec_file, obj_file])
  map = subprocess.check_output([vars(args)['nm'], obj_file])
  subprocess.call([vars(args)['objcopy'], '-I', 'srec', '-O', 'binary', '-R', '.sec1', srec_file, bin_file])
  with open(bin_file, 'r') as f:
    bin = f.read()
  return (map, bin)

def parse_value(value):
  if value.startswith('0x'):
    return int(value, 16);
  return int(value)

def add_value(test, set, idx, value):
  if idx == None:
    test[set] = value
  elif set.startswith('dr') or set.startswith('xd'):
    # convert 64-bit dr / xd register values into the pairs of fr and xf
    # registers that they alias
    set = set.replace('dr', 'fr')
    set = set.replace('xd', 'xf')
    test[set][idx + 1] = (value & 0xffffffff);
    test[set][idx] = ((value >> 32) & 0xffffffff);
  else:
    test[set][idx] = value

def format_values(values):
  if (type(values) is list):
    output = ''
    for v in values:
      output += hex(v & 0xffffffff) + ','
    output = output.strip(',')
  else:
    output = hex(values & 0xffffffff)
  return output

def format_test(test):
  output = 'TEST_SH4('
  output += test['name'] + ','
  output += '(uint8_t *)"' + ''.join('\\x' + x.encode('hex') for x in test['bin']) + '",'
  output += str(len(test['bin'])) + ','
  output += hex(test['offset']) + ','
  output += format_values(test['fpscr_in']) + ','
  output += format_values(test['r_in']) + ','
  output += format_values(test['fr_in']) + ','
  output += format_values(test['xf_in']) + ','
  output += format_values(test['fpscr_out']) + ','
  output += format_values(test['r_out']) + ','
  output += format_values(test['fr_out']) + ','
  output += format_values(test['xf_out'])
  output += ')'
  return output

def new_test(name, bin, offset):
  return {
    'name': name,
    'bin': bin,
    'offset': offset,
    'fpscr_in': 0xbaadf00d,
    'r_in': [0xbaadf00d] * 16,
    'fr_in': [0xbaadf00d] * 16,
    'xf_in': [0xbaadf00d] * 16,
    'fpscr_out': 0xbaadf00d,
    'r_out': [0xbaadf00d] * 16,
    'fr_out': [0xbaadf00d] * 16,
    'xf_out': [0xbaadf00d] * 16
  }

def parse_symbol_map(map, bin):
  tests = {}
  symbols = re.findall('([\d\w]+)\s+t\s+(test_.+)', map)
  for i, symbol in enumerate(symbols):
    offset = int(symbol[0], 16)
    name = symbol[1]
    tests[name] = new_test(name, bin, offset)
  return tests

def asm_to_tests(input_path):
  map, bin = compile_asm(input_path)

  # generate a test for each symbol in the map
  tests = parse_symbol_map(map, bin)

  # parse input, generating input / output registers for each test
  with open(input_path, 'r') as f:
    lines = f.readlines()

  # iterate source, using labels and comments as metadata describing
  # each test
  current_test = None

  for line in lines:
    m = re.match('(test_.+?):', line)
    if m:
      current_test = m.group(1)
      continue
    m = re.match('\s+# REGISTER_(IN|OUT)\s+(\w+?)(\d*?)\s+(.+)', line)
    if m:
      set = (m.group(2) + '_' + m.group(1)).lower()
      idx = int(m.group(3)) if m.group(3) else None
      value = parse_value(m.group(4))
      add_value(tests[current_test], set, idx, value)

  return tests

def list_to_inc(inputs, output_path):
  with open(output_path, 'w') as f:
    for input in inputs:
      tests = asm_to_tests(input)
      for name in tests:
        test = format_test(tests[name])
        f.write(test + '\n')

if __name__ == '__main__':
  list_to_inc(args.input, args.o)
