#!/usr/bin/env python

#
# This file is part of unicore-mx.
# Copyright (C) 2014-2016 Kuldeep Singh Dhaka <kuldeepdhaka9@gmail.com>
#
# uc-def is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# uc-def is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with uc-def.  If not, see <http://www.gnu.org/licenses/>.
#
# Special exception (same as GNU Bison)
# As a special exception, you may create a larger work that contains
# part or all of the uc-def parser skeleton and distribute that work
# under terms of your choice, so long as that work isn't itself a
# parser generator using the skeleton or a modified version thereof
# as a parser skeleton.  Alternatively, if you modify or redistribute
# the parser skeleton itself, you may (at your option) remove this
# special exception, which will cause the skeleton and the resulting
# uc-def output files to be licensed under the GNU General Public
# License without this special exception.

# uC Def.
# Microcontroller register defination

import sys
import logging

logging.basicConfig()
logger = logging.getLogger(__name__)

logger.setLevel(logging.ERROR)

STRICT_C_OUTPUT = False

def verify_access_flags(data):
	for i in data:
		if i not in ('r', 'w', 'c', 's', 'h'):
			raise Exception("'%c' flag of flags '%s' not valid" % (i, data))

def next_useful_line(tag, strip_newline=True, strip_comment=True):
	while True:
		tag['last_useful_pos'] = tag['input_file'].tell()
		tag['last_line_number'] = tag['line_number']
		inp = tag['input_file'].readline()

		if not len(inp):
			break

		if strip_newline:
			inp = inp.rstrip()

		if not len(inp):
			logger.debug("** SKIPPING NEWLINE at %i ***" % tag['line_number'])
			tag['line_number'] += 1
			continue

		if strip_comment:
			if inp[0] == "%":
				tag['line_number'] += 1
				logger.debug("** SKIPPING %i '%s' ***" % (tag['line_number'], inp))
				continue

		logger.debug("** LINE %i '%s' ***" % (tag['line_number'], inp))
		tag['line_number'] += 1
		return inp

	logger.debug("** END OF FILE ***")
	return ''

def restore_last_useful_line(tag):
	tag['line_number'] = tag['last_line_number']
	tag['input_file'].seek(tag['last_useful_pos'])

def get_line_number(tag):
	return tag['line_number']

def we_have_problem(msg, tag):
	raise Exception("%s [line: %i]" % (msg, get_line_number(tag)))

# this will return the last readed line. (if it could not parse it)
def parse_family_member(tag):
	family = {
		'name': None,
		'instance': None,
		'register': None,
		'internal_only': False
	}

	while True:
		inp = next_useful_line(tag)
		if inp == '': break

		if not inp[0] == ' ':
			# revese back and return
			restore_last_useful_line(tag)
			break

		data = inp.split()
		assert(len(data) >= 1)

		if data[0] == "name":
			# name <name>
			family['name'] = data[1]
		elif data[0] == "instance":
			# instance <address>
			assert(family['instance'] is None)
			family['instance'] = " ".join(data[1:])
		elif data[0] == "instance_by_id":
			# instance <id> <address>
			if family['instance'] is None:
				family['instance'] = []
			assert(type(family['instance']) == list)
			family['instance'].append({
				'id': data[1],
				'address': " ".join(data[2:])
			})
		elif data[0] == "register":
			assert(2 <= len(data) <= 3)
			family['register'] = {
				'size': data[1],
				'stride': data[2] if len(data) > 2 else None
			}
		elif data[0] == "internal_only":
			family['internal_only'] = bool(data[1])

	assert(family['name'] is not None)
	return family

def parse_family(tag):
	inp = next_useful_line(tag)
	if inp == '':
		return None

	if not inp[0] == 'f':
		restore_last_useful_line(tag)
		return None

	if not inp == "family":
		# fam <name> <address>
		data = inp.slit()
		assert(len(data) < 2 and data[0] == "fam")
		return {
			'name': data[1],
			'instance': data[2] if len(data) > 2 else None,
			'register': None,
			'internal_only': False
		}
	else:
		return parse_family_member(tag)

def parse_register_member(tag):
	register = {
		'name': [],
		'offset': None,
		'size': None,
		'access': None,
		'bit_name': [],
		'bits_name': [],
		'variable': [],
		'internal_only': False
	}

	while True:
		inp = next_useful_line(tag)
		if inp == '': break

		if not inp[0] == ' ':
			# revese back and return
			restore_last_useful_line(tag)
			break

		data = inp.split()
		assert(len(data) >= 1)

		if data[0] == "name":
			# name <name>
			register['name'].append(data[1])
		elif data[0] == "offset":
			# offset <offset>
			assert(register['offset'] is None)
			register['offset'] = " ".join(data[1:])
		elif data[0] == "offset_by_id":
			# offset_by_id <id> <offset>
			if register['offset'] is None:
				register['offset'] = []
			assert(type(register['offset']) == list)
			register['offset'].append({
				'id': data[1],
				'offset': " ".join(data[2:])
			})
		elif data[0] == "size":
			# size <size>
			register['size'] = data[1]
		elif data[0] == "access":
			# access <access>
			for c in data[1]:
				assert(c in ('r', 'w', 'h'))
			register['access'] = data[1]
		elif data[0] == "bit_name":
			# bit_name <bit-name>
			register['bit_name'].append(data[1])
		elif data[0] == "bits_name":
			# bits_name <bits-name>
			register['bits_name'].append(data[1])
		elif data[0] == "variable":
			# variable <variable-name> <variable-equation>
			register['variable'].append({
				'name': data[1],
				'equation': ' '.join(data[2:])
			})
		elif data[0] == "internal_only":
			register['internal_only'] = bool(data[1])

	assert(len(register['name']))

	if not len(register['bit_name']):
		# <bit_name> not provided, fallback and use <name>
		register['bit_name'] = register['name']

	if not len(register['bits_name']):
		# <bits_name> not provided, fallback and use <name>
		register['bits_name'] = register['name']

	return register

def parse_register(tag):
	inp = next_useful_line(tag)
	if inp == '':
		return None

	if inp[0] is not 'r':
		restore_last_useful_line(tag)
		return None

	if inp[3] == ' ':
		# reg <name> <offset> <size>
		data = inp.split()
		assert(2 <= len(data) <= 4)
		assert(data[0] == "reg")

		return {
			'name': [ data[1] ],
			'offset': data[2] if len(data) > 2 else None,
			'size': data[3] if len(data) > 3 else None,
			'access': None,
			'bit_name': [ data[1] ],
			'bits_name': [ data[1] ],
			'variable': [],
			'internal_only': False
		}
	else:
		assert(inp == "register")
		return parse_register_member(tag)

def parse_bit_member(tag):
	bit = {
		'name': [],
		'offset': None,
		'access': None,
		'variable': []
	}

	while True:
		inp = next_useful_line(tag)
		if inp == '': break

		if not inp[0] == ' ':
			# revese back and return
			restore_last_useful_line(tag)
			break

		data = inp.split()
		assert(len(data) >= 1)

		# name <name>
		if data[0] == "name":
			bit['name'].append(data[1])
		elif data[0] == "offset":
			if len(data) == 2:
				# offset <offset>
				assert(bit['offset'] is None)
				bit['offset'] = data[1]
			elif len(data) == 3:
				# offset <id> <offset>
				if bit['offset'] is None:
					bit['offset'] = []
				assert(type(bit['offset']) == list)
				bit['offset'].append({'id': data[1], 'offset': data[2]})
			else:
				we_have_problem("unknown design of offset", tag)
		elif data[0] == "access":
			# access <access>
			verify_access_flags(data[1])
			bit['access'] = data[1]
		elif data[0] == "variable":
			# variable <variable-name> <variable-equation>
			bit['variable'].append({
				'name': data[1],
				'equation': ' '.join(data[2:])
			})

	assert(len(bit['name']))
	return bit

def parse_bit(tag):
	inp = next_useful_line(tag)
	if inp == '':
		return None

	data = inp.split()
	if data[0] != "bit":
		restore_last_useful_line(tag)
		return None

	if len(data) > 1:
		# bit <name> <offset>
		return {
			'name' : [ data[1] ],
			'offset': data[2] if len(data) > 2 else None,
			'access': None,
			'variable': []
		}
	else:
		return parse_bit_member(tag)

def parse_bits_member(tag):
	bits = {
		'name': [],
		'offset': None,
		'size': None,
		'access': None,
		'values': [],
		'variable': []
	}

	while True:
		inp = next_useful_line(tag)
		if inp == '': break

		if not inp[0] == ' ':
			# revese back and return
			restore_last_useful_line(tag)
			break

		data = inp.split()
		assert(len(data) >= 1)

		# name <name>
		if data[0] == "name":
			bits['name'].append(data[1])
		elif data[0] == "offset":
			if len(data) == 2:
				# offset <offset>
				assert(bits['offset'] is None)
				bits['offset'] = data[1]
			elif len(data) == 3:
				# offset <id> <offset>
				if bits['offset'] is None:
					bits['offset'] = []
				assert(type(bits['offset']) == list)
				bits['offset'].append({'id': data[1], 'offset': data[2]})
			else:
				we_have_problem("unknown design of offset", tag)
		elif data[0] == "size":
			# size <size>
			bits['size'] = data[1]
		elif data[0] == "value":
			assert(len(data) >= 1)
			# value <value-name> <value-value>
			bits['values'].append({
				'name': data[1],
				'value': data[2] if len(data) > 2 else None
			})
		elif data[0] == "access":
			# access <access>
			verify_access_flags(data[1])
			bits['access'] = data[1]
		elif data[0] == "variable":
			# variable <variable-name> <variable-equation>
			bits['variable'].append({
				'name': data[1],
				'equation': ' '.join(data[2:])
			})

	assert(bits['name'] is not None)
	assert(bits['size'] is not None)
	return bits

def parse_bits(tag):
	inp = next_useful_line(tag)
	if inp == '':
		return None

	data = inp.split()
	if data[0] != "bits":
		restore_last_useful_line(tag)
		return None

	if len(data) > 1:
		assert(len(data) > 2)
		# bits <name> <size> <offset>
		return {
			'name' : [ data[1] ] ,
			'size': data[2],
			'offset': data[3] if len(data) > 3 else None,
			'access': None,
			'values': [],
			'variable': []
		}
	else:
		return parse_bits_member(tag)

def parse_input(input_file):
	data = []
	inside_block = False

	tag = {
		'input_file': input_file,
		'last_line_number': 1,
		'line_number': 1,
		'last_useful_pos': 0
	}

	while True:
		inp = next_useful_line(tag, strip_newline=inside_block, \
										strip_comment=False)
		if not len(inp):
			break

		if inside_block:
			if inp.strip()[0:6] == "#endif":
				inside_block = False
				continue
		elif inp.strip()[0:3] == "#if" and inp.find("__UCD__") != -1:
			inside_block = True
			continue
		else:
			data.append({'type': 'extra', 'payload': inp})
			continue

		# parse code
		if inp[0] == "%":
			fmt = "/* %s */" if STRICT_C_OUTPUT else "// %s"
			res = fmt % inp[1:].strip()
			data.append({'type': 'extra', 'payload': res + "\n\n"})
			continue

		restore_last_useful_line(tag)
		if inp[0] == "f":
			family = parse_family(tag)
			if family is None:
				break
			data.append({'type': 'family', 'payload': family})
		elif inp[0] == "r":
			register = parse_register(tag)
			if register is None:
				break
			data.append({'type': 'register', 'payload': register})
		elif inp[0] == "b":
			bit = parse_bit(tag)
			if bit is not None:
				data.append({'type': 'bit', 'payload': bit})
			else:
				bits = parse_bits(tag)
				if bits is None:
					break
				data.append({'type': 'bits', 'payload': bits})
		else:
			we_have_problem("unknown input: \"%s\"" % inp, tag)
			break
	return data

def generate_family(family, output_file):

	# if instance are not provided
	if family['instance'] is None:
		return

	if not family['internal_only']:
		if type(family['instance']) == list:
			for i in family['instance']:
				fam_id = '%s%s' % (family['name'], i['id'])
				output_file.write("#define %s (%s)\n" % (fam_id, i['address']))
		else:
			addr = str(family['instance'])
			name = family['name']
			output_file.write("#define %s (%s)\n" % (name, addr))

		# a final newline to seperate things
		output_file.write("\n")

def generate_register(register, family, offset, output_file):
	# calculate register size
	size = 32
	if register['size'] is not None:
		size = int(register['size'], 0)
	elif family['register'] is not None and family['register']['size'] is not None:
		size = int(family['register']['size'], 0)

	mmio = "MMIO%i" % size

	# calculate register offset
	if register['offset'] is not None:
		offset = register['offset']

	if not register['internal_only']:
		extra_arg = ""
		extra_body = ""
		if register['variable']:
			for i in register['variable']:
				extra_arg += ", %s" % i['name']
				extra_body += " + (%s)" % i['equation']

		#base macros of style PERPH_REG(base)
		#  if not instance is provided or multiple instance are there
		if family['instance'] is None or type(family['instance']) is list:
			register_name = register['name'][0]
			actual_name = "%s_%s" % (family['name'], register_name)
			output_file.write("#define %s(base%s) %s((base) + (%s)%s)\n" % (actual_name, extra_arg, mmio, offset, extra_body))
			output_file.write("\n")

			# alias of regiser (that accept base)
			if len(register['name'][1:]):
				output_file.write("/* Alias of %s */\n" % actual_name)
			for register_name in register['name'][1:]:
				alias_name = "%s_%s" % (family['name'], register_name)
				output_file.write("#define %s(base%s) %s(base%s)\n" % \
					(alias_name, extra_arg, actual_name, extra_arg))
			if len(register['name'][1:]):
				output_file.write("\n")

			# instace that send base to above macros
			if family['instance'] is not None: # ie family['instance'] type is of list
				for instance in family['instance']:
					for register_name in register['name']:
						frm = "%s%s_%s" % (family['name'], instance['id'], register_name)
						to = "%s_%s" % (family['name'], register_name)
						addr = "%s%s" % (family['name'], instance['id'])
						_extra_arg = "(%s)" % extra_arg[2:] if len(extra_arg) else ""
						output_file.write("#define %s%s %s(%s%s)\n" % (frm, _extra_arg, to, addr, extra_arg))
				output_file.write("\n")

		else:
			#single instance
			register_name = register['name'][0]
			actual_name = "%s_%s" % (family['name'], register_name)
			_extra_arg = "(%s)" % extra_arg[2:] if len(extra_arg) else ""
			output_file.write("#define %s%s %s((%s) + (%s)%s)\n" % \
				(actual_name, _extra_arg, mmio, family['instance'], offset, extra_body))
			output_file.write("\n")

			if len(register['name'][1:]):
				output_file.write("/* Alias of %s */\n" % actual_name)
			for register_name in register['name'][1:]:
				alias_name = "%s_%s" % (family['name'], register_name)
				output_file.write("#define %s%s %s%s" % (alias_name, _extra_arg, actual_name, _extra_arg))
				output_file.write("\n")
			if len(register['name'][1:]):
				output_file.write("\n")

	stride = size
	if family['register'] is not None and family['register']['stride'] is not None:
		stride = int(family['register']['stride'], 0)

	try: next_offset = int(offset) + stride
	except ValueError: next_offset = "%s + %i" % (offset, stride)

	return next_offset, size

def generate_bit(bit, register, family, offset, register_size, output_file):

	if bit['offset'] is not None:
		offset = bit['offset']

	try: assert(int(offset) < register_size)
	except ValueError: pass # cannot be converted to integer

	extra_arg = ""
	extra_offset = ""

	if bit["variable"]:
		extra_arg = []

		for v in bit["variable"]:
			extra_arg.append(v['name'])
			extra_offset += " + (" + v['equation'] + ")"

		extra_arg = ", ".join(extra_arg)
		extra_arg = "(%s)" % extra_arg

	actual_name = None
	for register_name in register['bit_name']:
		for bit_name in bit['name']:
			if register['bit_name'].index(register_name) == bit['name'].index(bit_name) == 0:
				actual_name = "%s_%s_%s" % (family['name'], register_name, bit_name)
				output_file.write("#define %s_SHIFT%s (%s%s)\n" % (actual_name, extra_arg, offset, extra_offset))
				output_file.write("#define %s%s (1 << %s_SHIFT%s)\n" % (actual_name, extra_arg, actual_name, extra_arg))
			else:
				alias_name = "%s_%s_%s" % (family['name'], register_name, bit_name)
				output_file.write("/* Alias of %s */\n" % actual_name)
				output_file.write("#define %s_SHIFT%s %s_SHIFT%s\n" % (alias_name, extra_arg, actual_name, extra_arg))
				output_file.write("#define %s%s %s%s\n" % (alias_name, extra_arg, actual_name, extra_arg))

			output_file.write("\n")
			output_file.write("\n")

	try: return (int(offset) + 1)
	except ValueError: return "%s + 1" % offset

def generate_bits(bits, register, family, offset, register_size, output_file):
	if bits['offset'] is not None:
		offset = bits['offset']

	size = int(bits['size'], 0)

	try: assert((int(offset) + size) <= register_size)
	except ValueError: pass # offset not a integer value

	mask = hex(pow(2, size) - 1)

	extra_arg = ""
	bracketed_extra_arg = "" #if len(extra_arg) > 0
	all_arg = "v"
	extra_offset = ""

	if bits["variable"]:
		extra_arg = []

		for v in bits["variable"]:
			extra_arg.append(v['name'])
			extra_offset += " + (" + v['equation'] + ")"

		extra_arg = ", ".join(extra_arg)

		all_arg = "v, %s" % extra_arg
		bracketed_extra_arg = "(%s)" % extra_arg

	actual_name = None
	for register_name in register['bits_name']:
		for bits_name in bits['name']:
			if (bits['name'].index(bits_name) == 0) and \
				(register['bits_name'].index(register_name) == 0):
				#print original macro
				actual_name = "%s_%s_%s" % (family['name'], register_name, bits_name)
				output_file.write("#define %s_SHIFT%s (%s%s)\n" % (actual_name, bracketed_extra_arg, offset, extra_offset))
				output_file.write("#define %s_MASK%s (%s << (%s_SHIFT%s))\n" % \
					(actual_name, bracketed_extra_arg, mask, actual_name, bracketed_extra_arg))
				output_file.write("#define %s(%s) (((v) << (%s_SHIFT%s)) & (%s_MASK%s))\n" % \
					(actual_name, all_arg, actual_name, bracketed_extra_arg, actual_name, bracketed_extra_arg))
				output_file.write("#define %s_GET(%s) (((v) & (%s_MASK%s)) >> (%s_SHIFT%s))\n" % \
					(actual_name, all_arg, actual_name, bracketed_extra_arg, actual_name, bracketed_extra_arg))

				# values
				value_value = 0
				for v in bits['values']:
					if v['value'] is not None:
						value_value = int(v['value'], 0)
					_arg = hex(value_value)
					if len(extra_arg):
						_arg = "%s, %s" % (_arg, extra_arg)
					output_file.write("#define %s_%s%s %s(%s)\n" % \
						(actual_name, v['name'], bracketed_extra_arg, actual_name, _arg))
					value_value += 1
			else:
				#print alias macro
				alias_name = "%s_%s_%s" % (family['name'], register_name, bits_name)
				output_file.write("/* Alias of %s */\n" % actual_name)
				output_file.write("#define %s_SHIFT%s %s_SHIFT%s\n" %
					(alias_name, bracketed_extra_arg, actual_name, bracketed_extra_arg))
				output_file.write("#define %s_MASK%s %s_MASK%s\n" %
					(alias_name, bracketed_extra_arg, actual_name, bracketed_extra_arg))
				output_file.write("#define %s(%s) %s(%s)\n" %
					(alias_name, all_arg, actual_name, all_arg))
				output_file.write("#define %s_GET(%s) %s_GET(%s)\n" %
					(alias_name, all_arg, actual_name, all_arg))

				# alias values
				for v in bits['values']:
					output_file.write("#define %s_%s%s %s_%s%s\n" % \
						(alias_name, v['name'], bracketed_extra_arg, actual_name,
							v['name'], bracketed_extra_arg))

			output_file.write("\n")
			output_file.write("\n")


	try: return (int(offset) + size)
	except ValueError: return "%s + %i" % (offset, size)

def generate_output(data, output_file):
	family = None
	register = None
	register_size = None
	register_offset = None
	register_next_bit = None

	for d in data:
		if d['type'] == 'extra':
			output_file.write(d['payload'])

		elif d['type'] == 'family':
			family = d['payload']
			register_offset = 0 # reset register offset
			generate_family(family, output_file)

		elif d['type'] == 'register':
			register = d['payload']
			register_next_bit = 0
			register_offset, register_size = generate_register(  \
					register, family, register_offset, output_file)
		elif d['type'] == 'bit':
			register_next_bit = generate_bit(d['payload'], register, family, \
				register_next_bit, register_size, output_file)
		elif d['type'] == 'bits':
			register_next_bit = generate_bits(d['payload'], register, family, \
				register_next_bit, register_size, output_file)


if __name__ == "__main__":
	assert (len(sys.argv) > 1)
	input_file = open(sys.argv[1])
	output_file = open(sys.argv[2], 'w+') if len(sys.argv) > 2 else sys.stdout
	data = parse_input(input_file)
	generate_output(data, output_file)
