# -*- Mode: Python; tab-width: 4 -*-

# transform code tree to vectors of byte-codes.

def integer_as_word (n):
	result = []
	for i in range(2):
		result.append (n & 0xff)
		n = n >> 8
	return result

def integer_as_long (n):
	result = []
	for i in range(4):
		result.append (n & 0xff)
		n = n >> 8
	return result

import string

class bytecoder:
	
	opcodes = {
		# opcodes with arguments:
		'proc'			: 'P',
		'save'			: 'S',
		'lit'			: 'L',
		'test'			: '?',
		'dynref'		: 'd',
		'define'		: 'D',
		'varref' 		: 'v',
		'assign'		: 'V',
		'primop'		: '%',
		'jump'			: 'J',
		'dynassign'		: '=',

		# no-argument opcodes
		'restore'		: 'R',
		'push'			: '>',
		'move_acc2'		: '2',
		'invoke'		: 'I',
		}
		
	primops = {
		'+'				: '+',
		'-'				: '-',
		'*'				: '*',
		'/'				: '/',
		'=='			: '=',
		'>'				: '>',
		'<'				: '<',
		}

	def vectorize (self, code):
		"flatten to a vector of bytes"

		# [proc nargs <code>]
		# [save <code1> <code2>]
		# [test <then_code> <else_code>]

		# places where we need to convert immediates to 'inline'
		# integers:
		#
		# [proc <code_len>] 16 bits
		# [save <code_len>] 16 bits
		# [test <code_len>] 16 bits
		# [lit <integer>] 32
		# [varref <depth> <index>] 16 16
		# [assign <depth> <index>] 16 16
		# [define name] <8-bit length> <string>
		# [dynref name] <8-bit length> <string>
		# [dynassign name] <8-bit length> <string>
		
		result = []
		
		for insn in code:
			opcode = insn[0]
			bytecode = self.opcodes[opcode]

			result.append (ord(bytecode))

			if opcode == 'lit':
				[ignore, value] = insn
				if value == None:
					# the compiler generates this for 'undefined'
					value = 0
				result = result + integer_as_long (value)

			if opcode == 'proc':
				[ignore, nargs, code] = insn
				proc_code = self.vectorize (code)
				# we'll drop <nargs> for now.
				result = result + integer_as_word (len(proc_code))
				result = result + proc_code

			elif opcode == 'save':
				[ignore, code1, code2] = insn
				code1 = self.vectorize (code1)
				code2 = self.vectorize (code2)
				result = result + integer_as_word (len(code1))
				result = result + code1 + code2

			elif opcode == 'test':
				[ignore, then_code, else_code] = insn
				then_code = self.vectorize (then_code)
				else_code = self.vectorize (else_code)
				result = result + integer_as_word (len (then_code))
				result = result + then_code + else_code

			elif opcode == 'primop':
				[ignore, primop] = insn
				result.append (ord (self.primops[primop]))

			elif opcode in ('varref', 'assign'):
				[ignore, (depth, index)] = insn
				result = result + integer_as_word (depth)
				result = result + integer_as_word (index)

			elif opcode in ('dynref', 'define', 'dynassign'):
				[ignore, name] = insn
				name = name + '\000'
				result.append (len(name))
				result = result + map (ord, name)

			else:
				# the rest are no-argument instructions:
				# restore, push, move_acc2, invoke
				pass

		return result
		
	def dump (self, code, file):
		code = self.vectorize (code)
		data = string.join (map (chr, code), '')
		print 'bytecode: ', repr(data)
		file.write (str(len(data)) + ' ')
		file.write (data)

import compiler
import os
import sys

class read_eval_print:
	"Interact with the C version of the Virtual Machine through a pipe"

	compiler = compiler.code_compiler()
	squisher = bytecoder()

	def run (self):
		pipe = os.popen ('./machine', 'w')
		while 1:
			sys.stdout.write ('--> ')
			sys.stdout.flush()

			try:
				exp = raw_input()
			except EOFError:
				print
				break

			code = self.compiler.compile (exp)
			self.compiler.print_code (code)
			self.squisher.dump (code, pipe)
			while 1:
				line = pipe.readline()
				if not line:
					break
				elif line[:3] == '=> ':
					break
				else:
					print line
			sys.stdout.flush()

if __name__ == '__main__':
	repl = read_eval_print()
	repl.run()
