#! /usr/bin/python
#
# COPYRIGHT 2006, Avery D Andrews 3rd
#
# License: GPL2
#
# Purpose: this script converts macro 'declarations' in the style
#   of the accompanying macros.txo to a chain of TeX macros with
#   optional arguments, with [#d=X] indicating an optional argument
#   with default value X.  Allowed delimiters are [], (), <>; Input
#   verification not very thorough.

#
#  changelog at end

import sys, string, os, re

#
# would be an idea to subclass 'file', but then I wouldn't
#   know how to make it work with sys.stdin
#
class Source:

    def __init__(self, source):
        self.linenum = 0
        self.file = source
        self.nextline = source.readline()

    def readline(self):
        self.linenum = self.linenum+1
        returnline = self.nextline
        self.nextline = self.file.readline()
        return returnline

class ProgramError(Exception):

    def __init__(self, value):
        self.value = value+'\n'
        #
        # UGH: there must be a better way to do this
        #
        global error_occurred
        error_occurred=1

    #
    # UMM: actually this doesn't seem to do what I expect it to (be
    #   the spelling of an error instance in backquotes)
    #
    def __str__(self):
        return self.value

#
# complain and set error flag but don't stop
#
def ProgramWhinge(issue):
    sys.stderr.write(issue)
    global error_occurred
    error_occurred=1


blanklinepattern = re.compile("^\s*(%.*)?$")

#
# pulls out three fields, the first ought be \def, \gdef etc.
#    the second is the name, preceeding by backslash
#    the third is the arguments.
# they can be separated by whitespace, but don't have to be
#
deflinepattern = re.compile("^\s*(\\\\[a-zA-Z]+)\s*(\\\\[a-zA-Z@]+)\s*(\S*)\s*$")

#    [ indicates [, (, <, likewise ].  1 indicates any digit
#
#         #1 or [#1#], not followed by a closing bracket  
#                            
oblargpattern = re.compile("([\[\(<])?#(\d)(?![=\]\)>])(#[\]\)>])?(.*)")
#                                 1      2                 3        4
#
#                      [#1(=opt)]
#
optargpattern = re.compile("([\[\(<])#(\d)(=[^[([\]\)>]*)?([([\]\)>])(.*)")
#                               1       2        3            4       5
#
def process_file(infile, outfile):
    global error_occurred
    error_occurred =0
    while 1:
        line = infile.readline()
        if line == '':
            break;
        blankmatch = blanklinepattern.match(line)
        if blankmatch:
            pass
        else:
            defmatch = deflinepattern.match(line)
            if defmatch:
                (defin, name, arguments) = defmatch.group(1,2,3)
                outfile.write("\n%%\n%%    %s\n%%\n%%\n"%name)
                produce_definition(defin, name, arguments, 1, [], infile, outfile)
            else:
                raise ProgramError("line %d ill-formed"%infile.linenum)        

def produce_definition(defcom, name, arguments, count, arglist, infile, outfile):
    blankmatch = blanklinepattern.match(arguments)
    if blankmatch:
        write_definition(defcom, name, arglist, outfile)
        return
    #
    # obligatory arguments
    #
    argmatch = oblargpattern.match(arguments)
    if argmatch:
#        print "%s: %s"%(name,argmatch.group(3))
        if argmatch.group(1):
            if not argmatch.group(3):
                raise ProgramError("no closing bracket in line %d"%infile.linenum)
        if argmatch.group(3):
            if not argmatch.group(2):
                raise ProgramError("no opening bracket in line %d"%infile.linenum)
        if count!=int(argmatch.group(2)):
            raise ProgramError("argument number sequence error line %d"%infile.linenum)
        if argmatch.group(1):
#            print `argmatch.group(2)`
            arg = "%s#%d%s"%(argmatch.group(1), count, argmatch.group(3))
            arglist.append((arg,arg))
        else:
            arglist.append(("#%d"%count, "{#%d}"%count))
        produce_definition(defcom,name,argmatch.group(4),count+1, arglist,infile, outfile)
        return
    #
    # optional arguments
    #
    argmatch = optargpattern.match(arguments)
    if argmatch:
        write_definition_start(defcom, name, arglist, outfile)
        nextname = '\@'+name[1:]
        outfile.write("{\@ifnextchar %s\n  {%s"%(argmatch.group(1), nextname))
        #
        #  iftrue
        #
        write_args_in_def(arglist,outfile)
        outfile.write("}")
        outfile.write("\n")
        #
        # iffalse
        #
        if argmatch.group(3):
            defaultval = argmatch.group(3)[1:]
        else:
            defaultval = ""
#        print "default: %s"%defaultval
        outfile.write("  {%s"%nextname)
        write_args_in_def(arglist,outfile)
        outfile.write("%s%s%s}\n}\n"%(argmatch.group(1),defaultval,argmatch.group(4)))
        #
        # next argument
        #
        arg ="%s#%s%s"%argmatch.group(1,2,4)
        arglist.append((arg, arg))
        produce_definition(defcom, nextname, argmatch.group(5), count+1, arglist, infile, outfile)

    
def write_args_in_def(arglist, outfile):
    for arg in arglist:
        outfile.write(arg[1])

def write_definition_start(defcom, name, arglist, outfile):
    outfile.write("%s%s"%(defcom, name))
    for arg in arglist:
        outfile.write(arg[0])

def write_definition(defcom, name, arglist, outfile):
    write_definition_start(defcom, name, arglist, outfile)
    outfile.write("{\n\n}\n")

if __name__=="__main__":
    argv=sys.argv
#    print 'cwd:'+os.getcwd()
    if len(argv)<2:# run as filter
        infile=sys.stdin
        outfile=sys.stdout
        errfilename = 'texoptarg.err'
#        errfile=open("texoptarg.err","w")
    else:
        infile=open(argv[1]+'.txo','r')
        outfile=open(argv[1]+'.tex','w')
        errfilename = argv[1]+'.err'
#        errfile=open(argv[1]+'.err','w')

    try:
        process_file(Source(infile), outfile)
    except ProgramError, error:
        sys.stderr.write(error.value)
        errfile=open(errfilename,'w')
        errfile.write(error.value)
    if error_occurred:
        sys.exit(1)
    sys.stderr.write("texoptarg ran without issues\n")
    
#    
# Changelog:
#
#   Jan 24 2006:  first apparently working version
#