'''
A crude compiler for a subset of Joy functions.

I think I'm going about this the wrong way.

The inference algorithm can "collapse" Yin function sequences into
single stack effects which can then be written out as Python functions.
Why not keep track of the new variables introduced as results of Yang
functions, during inference?  Could I write out better code that way?

In any event, I am proceeding with this sort of ad hoc way for now.
'''
from __future__ import print_function
from builtins import next
from builtins import str
from builtins import object
from joy.parser import text_to_expression, Symbol
from joy.utils.stack import concat, iter_stack, list_to_stack
from joy.library import SimpleFunctionWrapper, YIN_STACK_EFFECTS
from functools import reduce


def import_yin():
	from joy.utils.generated_library import *
	return locals()


class InfiniteStack(tuple):

	def _names():
		n = 0
		while True:
			m = yield Symbol('a' + str(n))
			n = n + 1 if m is None else m

	_NAMES = _names()
	next(_NAMES)

	names = lambda: next(_NAMES)
	reset = lambda _self, _n=_NAMES: _n.send(-1)

	def __init__(self, code):
		self.reset()
		self.code = code

	def __iter__(self):
		if not self:
			new_var = self.names()
			self.code.append(('pop', new_var))
			return iter((new_var, self))


def I(expression):
	code = []
	stack = InfiniteStack(code)

	while expression:
		term, expression = expression
		if isinstance(term, Symbol):
			func = D[term]
			stack, expression, _ = func(stack, expression, code)
		else:
			stack = term, stack

	code.append(tuple(['ret'] + list(iter_stack(stack))))
	return code


strtup = lambda a, b: '(%s, %s)' % (b, a)
strstk = lambda rest: reduce(strtup, rest, 'stack')


def code_gen(code):
	#for p in code: print p
	coalesce_pops(code)
	lines = []
	emit = lines.append
	for t in code:
		tag, rest = t[0], t[1:]
		if tag == 'pop': emit(strstk(rest) + ' = stack')
		elif tag == 'call': emit('%s = %s%s' % rest)
		elif tag == 'ret': emit('return ' + strstk(rest[::-1]))
		else:
			raise ValueError(tag)
	return '\n'.join('    ' + line for line in lines)


def coalesce_pops(code):
	code.sort(key=lambda p: p[0] != 'pop')  # All pops to the front.
	try: index = next((i for i, t in enumerate(code) if t[0] != 'pop'))
	except StopIteration: return
	code[:index] = [tuple(['pop'] + [t for _, t in code[:index][::-1]])]


def compile_yinyang(name, text):
	return '''
def %s(stack):
%s
''' % (name, code_gen(I(text_to_expression(text))))


def q():
	memo = {}
	def bar(type_var):
		try:
			res = memo[type_var]
		except KeyError:
			res = memo[type_var] = InfiniteStack.names()
		return res
	return bar


def type_vars_to_labels(thing, map_):
	if not thing:
		return thing
	if not isinstance(thing, tuple):
		return map_(thing)
	return tuple(type_vars_to_labels(inner, map_) for inner in thing)


def remap_inputs(in_, stack, code):
	map_ = q()
	while in_:
		term, in_ = in_
		arg0, stack = stack
		term = type_vars_to_labels(term, map_)
		code.append(('call', term, '', arg0))
	return stack, map_


class BinaryBuiltin(object):

	def __init__(self, name):
		self.name = name

	def __call__(self, stack, expression, code):
		in1, (in0, stack) = stack
		out = InfiniteStack.names()
		code.append(('call', out, self.name, (in0, in1)))
		return (out, stack), expression, code


YIN = import_yin()


D = {
	name: SimpleFunctionWrapper(YIN[name])
	for name in '''
		ccons
		cons
		dup
		dupd
		dupdd
		over
		pop
		popd
		popdd
		popop
		popopd
		popopdd
		rolldown
		rollup
		swap
		swons
		tuck
		unit
		'''.split()
	}


for name in '''
	first
	first_two
	fourth
	rest
	rrest
	second
	third
	uncons
	unswons
	'''.split():

	def foo(stack, expression, code, name=name):
		in_, out = YIN_STACK_EFFECTS[name]
		stack, map_ = remap_inputs(in_, stack, code)
		out = type_vars_to_labels(out, map_)
		return concat(out, stack), expression, code

	foo.__name__ = name
	D[name] = foo


for name in '''
  eq
  ge
  gt
  le
  lt
  ne
  xor
  lshift
  rshift
  and_
  or_
  add
  floordiv
  mod
  mul
  pow
  sub
  truediv
  '''.split():
	D[name.rstrip('-')] = BinaryBuiltin(name)


'''
		stack
		stuncons
		stununcons
		swaack
'''

for name in sorted(D):
	print(name, end=' ')
##    print compile_yinyang(name, name)
print('-' * 100)


print(compile_yinyang('mul_', 'mul'))
print(compile_yinyang('pop', 'pop'))
print(compile_yinyang('ppm', 'popop mul'))
print(compile_yinyang('sqr', 'dup mul'))
print(compile_yinyang('foo', 'dup 23 sub mul'))
print(compile_yinyang('four_mul', 'mul mul mul mul'))
print(compile_yinyang('baz', 'mul dup sub dup'))
print(compile_yinyang('to_the_fifth_power', 'dup dup mul dup mul mul'))
print(compile_yinyang('dup3', 'dup dup dup'))
print(compile_yinyang('df2m', 'dup first_two mul'))
print(compile_yinyang('sqr_first', 'uncons swap dup mul swons'))
print(compile_yinyang('0BAD',      'uncons      dup mul'))
print(compile_yinyang('uncons', 'uncons'))
