#!/usr/bin/env python2.4

#
# CTL IDL compiler, based on idlParse.py (Copyright 2003, Paul McGuire)
#
# Copyright 2005, 2006 Boris Buegling <boris@icculus.org>
#
# See also:
#		cpp/native/ci2jni.py
#		matlab/cpp/ctlmex.py

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

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

typelist1	= Suppress('(') + Suppress(')') + Suppress(Optional('const')) + \
	Suppress(',') + Suppress('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 convertIdentifierJava (ident):
	if ident in ['any']:
		return 'CTL.CCompat.AnyObj'
	elif ident in ['array', 'vector']:
		return 'CTL.CCompat.CArray'
	elif ident in ['bool']:
		return 'Boolean'
	elif ident in ['int2', 'short', 'uint2']:
		return 'Short'
	elif ident in ['int4', 'int', 'uint4']:
		return 'Integer'
	elif ident in ['int8', 'long', 'uint8']:
		return 'Long'
	elif ident in ['location', 'reference', 'status', 'tupel']:
		return 'CTL.Types.'+ident.capitalize()
	elif ident in ['real4']:
		return 'Float'
	elif ident in ['real8', 'double']:
		return 'Double'
	elif ident in ['string', 'char']:
		return ident.capitalize()
	elif ident in ['void', 'S', 'T']: # common template typenames
		return ident
	elif ident in ['ctl_B']: # for testing only
		return 'Integer'
	elif replace.has_key(ident):
		return replace[ident]
	else:
		if (not quiet):
			print >>sys.stderr, 'Unknown identifier: %s' % ident
		return ident

def parseIdentifierJava (inp, loc, tokens):
	ident = ''.join(tokens)
	if re.match('.*::.*', ident):
		ident = ident.split('::')[-1]
	if re.match('.*<.*>', ident):
		if ident == 'cstring<Char>':
			return 'String'
		tmp = ident.split('<')
		return convertIdentifierJava(tmp[0])+'<'+'<'.join(tmp[1:])
	return convertIdentifierJava(ident)

def parseTypeJava (inp, locs, tokens):
	res = []
	for token in tokens:
		if token == 'const':
			res.append('@CTL.Annotate.const_')
		elif token == '(':
			res.append('<')
		elif token == ')':
			res.append('>')
		else:
			res.append(token)
	return ' '.join(res)

# TODO: Exceptions are not yet supported 
def parseExceptionJava (inp, locs, tokens):
	return 'throws CTL.Types.CTLException'

def parseConstructorJava (inp, locs, tokens):
	size, exception = len(tokens), ''
	if tokens[-1].startswith('throws'):
		size -= 1
		exception = tokens[-1]
	arglist = ['%s arg%i' % (tokens[i], i-1) for i in range(1, size)]
	return """@CTL.Annotate.cexport @ReflWrap.sCID(%i) public %s (%s) %s
{
} """ % (int(tokens[0]), curClassName, ', '.join(arglist), exception)

def parseMethodTmplJava (inp, locs, tokens):
	idx = list(tokens).index(',')
	tok_def = tokens[:idx]		# Method definition
	idx += 1
	tok_param = tokens[idx:]	# Template parameters
	templ = 'public <%s extends Object>' % ', '.join(tok_param)
	res = parseMethodJava(inp, locs, tok_def)
	res = res.replace('public', templ)
	return res

def parseMethodJava (inp, locs, tokens):
	if tokens[2] == 'operator':
		return ''
	size, exception = len(tokens), ''
	if tokens[-1].startswith('throws'):
		size -= 1
		exception = tokens[-1]
	arglist = ['%s arg%i' % (tokens[i], i-3) for i in range(3, size)]
	if tokens[1] == 'void':
		returns = ''
	else:
		returns = 'return null;'
	return """@CTL.Annotate.export @ReflWrap.sFID(%i) public %s %s (%s) %s
{
%s
}""" % (int(tokens[0]), tokens[1], tokens[2], ', '.join(arglist), exception, returns)

# TODO: Does not support exceptions
# TODO: Find a way to merge this with parseMethodJava
def parseStaticMethodJava (inp, locs, tokens):
	arglist = ['%s arg%i' % (tokens[i], i-3) for i in range(3, len(tokens))]
	if tokens[1] == 'void':
		returns = ''
	else:
		returns = 'return null;'
	return """@ReflWrap.sFID(%i) public static %s %s (%s)
{
%s
}""" % (int(tokens[0]), tokens[1], tokens[2], ', '.join(arglist), returns)

def parseClassName (inp, loc, tokens):
	global curClassName
	curClassName = tokens[0]

def parseClassJava (inp, loc, tokens):
	name = tokens[0]
	if name[-2:] == 'RI':
		name = name[:-2]
	return """public class %s
{
@CTL.Annotate.cexport @ReflWrap.sCID(0) public %s ()
{
}
%s
}""" % (name, name, '\n'.join(tokens[1:]))

def parseLibrary (inp, loc, tokens):
	# TODO: Global functions in a library are unsupported (see mandel.ci)
	global curPkgName
	curPkgName = tokens[0]
	return tokens[1:]

def ri2java (fname):
	# TODO: Templates are not supported for code-generation (see numtheory.ri)
	# TODO: Packages with multiple classes not supported (see numtheory.ri)
	ident.setParseAction(parseIdentifierJava)
	type.setParseAction(parseTypeJava)
	exception.setParseAction(parseExceptionJava)
	constructor.setParseAction(parseConstructorJava)
	function.setParseAction(parseMethodJava)
	functiontmpl.setParseAction(parseMethodTmplJava)
	method.setParseAction(parseMethodJava)
	s_method.setParseAction(parseStaticMethodJava)
	classname.setParseAction(parseClassName)
	classdecl.setParseAction(parseClassJava)
	library.setParseAction(parseLibrary)
	res = interface.parseFile(fname)
	
	global curPkgName
	if repPkgName:
		curPkgName = repPkgName
	if functional:
		out = """package _default;

%s
@CTL.Annotate.Library public class %s
{
%s
}
""" % (__stubComment, curPkgName, '\n'.join(res).replace('public', 'public static'))
	else:
		out = 'package %s;\n\n%s\n%s' % (curPkgName, __stubComment, '\n'.join(res))
	for repl in replace.keys():
		out = re.sub(repl, replace[repl], out)
	print out

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

	quiet		= False
	functional	= False
	curPkgName 	= 'Impl'
	repPkgName 	= None
	replace		= {}
	for arg in sys.argv[1:-1]:
		if arg.startswith('-P'):	# change package name
			repPkgName = arg[2:]
		elif arg.startswith('-D'):	# define a new name for something
			tmp = arg[2:].split('=')
			replace[tmp[0]] = tmp[1]
		elif arg.startswith('-F'):	# functional ci
			functional = True
		elif arg.startswith('-q'):	# be quieter
			quiet = True

	try:
		ri2java(sys.argv[-1])
	except ParseException, err:
		print >>sys.stderr, err.line
		print >>sys.stderr, " "*(err.column-1) + "^"
		print >>sys.stderr, err
		sys.exit(1)
