#!/usr/bin/env python2.4

#
# CTL IDL compiler, based on idlParse.py (Copyright 2003, Paul McGuire)
#
# Copyright 2005, 2006, 2007 Boris Buegling <boris@icculus.org>
#
# Licensed under the GNU General Public License version 2.
#

import re, sys
from pyparsing import alphas, alphanums, CharsNotIn, cStyleComment, Forward, \
	Literal, nums, OneOrMore, Optional, ParseException, restOfLine, Suppress, \
	Word, ZeroOrMore

# Comment which is put into all stub classes
__stubComment = """/** This stub code was autogenerated by the CTL IDL compiler.
Written by Boris Buegling, licensed under the GNU General Public license. 
Thanks to Paul McGuire for writing pyparsing. */"""

# Keywords
define	= Literal('#') + Literal('define')
include	= Literal('#') + Literal('include')

# CI grammar
c_ident	= Word(alphas + '_', alphanums + '_')
ident	= Forward()
ident	<< Optional(c_ident + '::') + c_ident + Optional('<' + ident + '>')

numargs = Suppress(Word(nums))
id		= Word(nums)

oper = Literal('operator') + (Literal('()') | Literal('+=') | Literal('-=') | \
	Literal('*=') | Literal('/=') | Literal('%=') | Literal('^=') | \
	Literal('&=') | Literal('|=') | Literal('<<') | Literal('>>') | \
	Literal('<<=') | Literal('>>=') | Literal('==') | Literal('!=') | \
	Literal('<=') | Literal('>=') | Literal('&&') | Literal('||') | \
	Literal('++') | Literal('--') | Literal('->*') | Literal('[]') | \
	Literal('new') | Literal('new[]') | Literal('delete') | \
	Literal('delete[]')) # :: . .* ?: sizeof typeid
funcname = oper | c_ident

type	= Forward()
type1	= Optional('const') + ident + Suppress(Optional(Literal('*') | Literal('&')))
type2	= Suppress(Literal('(')) + Optional('const') + ident + Suppress(Literal(',')) + \
	Literal('(') +  type + ZeroOrMore(Literal(',') + type) + Literal(')') + \
	Suppress(Literal(',') + numargs + Literal(')'))
type	<< (type1 | type2)

typelist1	= Literal('(') + Literal(')') + Optional('const') + Literal(',') + Literal('0')
typelist2	= Suppress('(') + type + ZeroOrMore(Suppress(',') + type) + Suppress(')') + \
	Suppress(Optional('const')) + Suppress(',') + numargs
typelist	= typelist1 | typelist2

funcsign	= type + Suppress(',') + funcname + Suppress(',') + typelist
exception	= id + 'Throws' + typelist

constructor = Suppress(define + 'CTL_Constructor') + id + typelist + \
	Optional(Suppress(define + 'CTL_Constructor') + exception)
method		= Suppress(define + 'CTL_Method') + id + funcsign + \
	Optional(Suppress(define + 'CTL_Method') + exception)
s_method	= Suppress(define + 'CTL_StaticMethod') + id + funcsign + \
	Optional(Suppress(define + 'CTL_StaticMethod') + exception)

function		= Suppress(define + 'CTL_Function') + id + funcsign + \
	Optional(define + 'CTL_Function' + exception)
functiontmpl	= Suppress(define + 'CTL_FunctionTmpl') + id + funcsign + ',' + \
	typelist + Optional(define + 'CTL_FunctionTmpl' + exception)

classentry 	= method | s_method | constructor
classbody	= Suppress(include + 'CTL_ClassBegin') + OneOrMore(classentry) + \
	Suppress(include + 'CTL_ClassEnd')
classname	= Suppress(define + Literal('CTL_Class')) + c_ident
classnameT	= Suppress(define + Literal('CTL_ClassTmpl')) + c_ident
classdecl	= classname + classbody
classtmpl	= classnameT + ',' + typelist + classbody

libentry	= classdecl | classtmpl | function | functiontmpl
libbody		= Suppress(include + 'CTL_LibBegin') + OneOrMore(libentry) + \
	Suppress(include + 'CTL_LibEnd')
libname		= Suppress(define + Literal('CTL_Library')) + c_ident
library		= libname + libbody

interface	= Suppress(Optional('#include <ctl.h>')) + (library | classdecl | classtmpl)

# Ignore inline C code
inlcode		= Forward()
inlcode		<< '{' + ZeroOrMore(inlcode | CharsNotIn('{}')) + '}' + Optional(';')

classdecl2	= 'class' + restOfLine
classdef	= 'class' + Optional(c_ident) + inlcode
comment		= ('//' + restOfLine) | cStyleComment
defn		= ((Literal('#ifndef') | Literal('#ifdef')) + restOfLine + \
	'#define' + restOfLine) | '#endif' + restOfLine
enumdef		= 'enum' + inlcode
namespace	= 'using namespace' + restOfLine
struct		= 'struct' + Optional(c_ident) + Optional(':' + c_ident + c_ident) + inlcode
typedef 	= 'typedef' + restOfLine
modifier	= (Literal('public') | Literal('private')) + restOfLine

interface.ignore(classdef)
interface.ignore(classdecl2)
interface.ignore(comment)
interface.ignore(defn)
interface.ignore(enumdef)
interface.ignore(namespace)
interface.ignore(struct)
interface.ignore(typedef)
interface.ignore(modifier)

######################################################################
def countTemplArgs (type):
	return len(re.findall(',', re.sub('<.*?>', '', re.finditer('<.*>', type).next().group()[1:-1])))+1

def convertType (type):
	if type == 'array':
		return 'ctl::vector'
	if type == 'int4':
		return 'int'
	if type == 'real4':
		return 'float'
	if type == 'real8':
		return 'double'
	if type == 'tupel':
		return 'std::pair'
	#if type == 'reference':
	#	return 'std::auto_ptr'

	if type in [',']:
		return type
	if type in ['reference']:
		return 'ctl::'+type
	if type in ['string']:
		return 'std::'+type

	if type == '(':
		return '<'
	if type == ')':
		return '>'

	#print type
	return type

def defaultValue (type):
	for type2 in ['ctl::vector', 'std::vector', 'myVector']:
		if type.startswith(type2):
			return '%s(%i)' % (type, countTemplArgs(type))
	for type2 in ['ctl::reference', 'std::auto_ptr', 'matrix']:
		if type.startswith(type2):
			return '%s()' % type
	if type in ['int', 'float', 'double']:
		return 0
	raise Exception('No default value found for \'%s\'.' % type)

def convertMe (n, m):
	return 'mexConvert(arrargs[%i], arg%i);' % (n, m)

def convertMeBack ():
	return 'mexConvert(res, arrresults[0]);'

def parseTemplMethod (inp, locs, tokens):
	i = list(tokens).index(',')
	parms = tokens[i+1:]
	tokens = tokens[:i]
	for param in parms:
		# FIXME: double is assumed for template parameters.
		tokens = [tok.replace(param, 'double') for tok in tokens]
	funcname = tokens.pop(2) + '<double>'
	tokens.insert(2, funcname)
	#print tokens
	return parseMethod(inp, locs, tokens)

def parseMethod (inp, locs, tokens):
	global first_func
	if not first_func:
		print >>sys.stderr, 'WARNING: Multiple functions not supported.'
		return ''
	first_func = False

	id, ret, name = tokens[:3]
	
	decodeArgs = ''
	for n in range(len(tokens)-3):
		tok = tokens[n+3].replace('>>', '> >')
		decodeArgs += """\t%s arg%i = %s;
\tif (nargs > %i)
\t\t%s
""" % (tok, n, defaultValue(tok), n, convertMe(n, n))

	argList = ','.join(['arg%i' % n for n in range(len(tokens)-3)])

	return """#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <mex.h>

#include <mexhelper.h>
#include "%s"

extern "C"
{
void mexFunction (int nresults, mxArray **arrresults, int nargs,
	const mxArray **arrargs)
{
%s

	try
	{
		initEnv();
		%s res = %s::%s(%s);

		if (nresults > 0)
		{
			%s
		}
	}
	catch (ctl::exception &e)
	{
		std::cerr << e << "\\n";
	}
}
}""" % (ciFile, decodeArgs, ret, curPkgName, name, argList, convertMeBack())

def parseLibrary (inp, loc, tokens):
	global curPkgName
	curPkgName = tokens[0]
	return ''

def parseType (inp, loc, tokens):
	ret = ''.join([convertType(token) for token in tokens if not token == 'const'])
	#print >>sys.stderr, ret
	return ret

def parseClass (inp, loc, tokens):
	print >>sys.stderr, 'WARNING: Classes are not supported.',
	return ''

def ri2matlab (fname):
	global first_func
	first_func = True
	classdecl.setParseAction(parseClass)
	libname.setParseAction(parseLibrary)
	function.setParseAction(parseMethod)
	functiontmpl.setParseAction(parseTemplMethod)
	type.setParseAction(parseType)
	res = interface.parseFile(fname)
	print ''.join(res)

if __name__ == '__main__':
	if len(sys.argv) == 1:
		print "Usage: %s [CI]" % sys.argv[0]
		sys.exit(1)

	curPkgName 	= 'Impl'
	ciFile = sys.argv[-1]

	try:
		ri2matlab(ciFile)
	except ParseException, err:
		print >>sys.stderr, err.line
		print >>sys.stderr, " "*(err.column-1) + "^"
		print >>sys.stderr, err
		sys.exit(1)
