#! /usr/bin/env python
# -*- coding: iso-8859-15 -*-
#
# Toolkit for building distributed control systems or any other distributed system.
#
# Copyright (c) 2004-2005 Jens Krger <jkrueger1@users.sf.net>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#
# File:         ResDatabase.py
#
# Project:      Automatic Beamline Alignment
#
# Description:  Generic interface class to the TACO database.
#
# Author(s):    J.Meyer
#               $Author: jkrueger1 $
#
# Original:     Mai 2001
#
# Version:      $Revision: 1.5 $
#
# Date:         $Date: 2007/01/26 07:25:44 $
#

"""
 Read #define's and translate to Python code.
 Handle #include statements.
 Handle #define macros with one argument.
 Anything that isn't recognized or doesn't translate into valid
 Python is ignored.

 Without filename arguments, acts as a filter.
 If one or more filenames are given, output is written to corresponding
 filenames in the local directory, translated to all uppercase, with
 the extension replaced by ".py".

 By passing one or more options of the form "-i regular_expression"
 you can specify additional strings to be ignored.  This is useful
 e.g. to ignore casts to u_long: simply specify "-i '(u_long)'".

 To do:
 - turn trailing C comments into Python comments
 - turn C Boolean operators "&& || !" into Python "and or not"
 - what to do about #if(def)?
 - what to do about macros with multiple parameters?
"""

__author__ = "Jens Meyer, $Author: jkrueger1 $"
__date__ = "$Date: 2007/01/26 07:25:44 $"
__revision__ = "$Revision: 1.5 $"

import sys, re, string, getopt, os

p_define = re.compile('(^[\t ]*#[\t ]*define[\t ]+)([a-zA-Z0-9_]+[\t \n]+)')

p_macro = re.compile('^[\t ]*#[\t ]*define[\t ]+'
  '([a-zA-Z0-9_]+)\(([_a-zA-Z][_a-zA-Z0-9]*)\)[\t ]+')

p_include = re.compile('^[\t ]*#[\t ]*include[\t ]+<([a-zA-Z0-9_/\.]+)')

p_comment = re.compile('/\*([^*]+|\*+[^/])*(\*+/)?')
p_cpp_comment = re.compile('//.*')

ignores = [p_comment, p_cpp_comment]

p_char = re.compile("'(\\\\.[^\\\\]*\|[^\\\\])'")

filedict = {}

try:
    searchdirs = string.splitfields(os.environ['include'],';')
except KeyError:
    try:
        searchdirs = string.splitfields(os.environ['INCLUDE'],';')
    except KeyError:
        try:
            if string.find( sys.platform, "beos" ) == 0:
                searchdirs = string.splitfields(os.environ['BEINCLUDES'],';')
            else:
                raise KeyError
        except KeyError:
            searchdirs = ['/usr/include']

def main():
    """
    Here starts the action 
    """
    global filedict
    opts, args = getopt.getopt(sys.argv[1:], 'i:')
    for o, a in opts:
        if o == '-i':
            ignores.append(re.compile(a))
    if not args:
        args = ['-']
    for filename in args:
        if filename == '-':
            sys.stdout.write('# Generated by h2py from stdin\n')
            process(sys.stdin, sys.stdout)
        else:
            fp = open(filename, 'r')
            outfile = os.path.basename(filename)
            i = string.rfind(outfile, '.')
            if i > 0: 
                outfile = outfile[:i]
            outfile = string.upper(outfile)
            outfile = outfile + '.py'
            outfp = open(outfile, 'w')
            outfp.write('# Generated by h2py from %s\n' % filename)
            filedict = {}
            for dir in searchdirs:
                if filename[:len(dir)] == dir:
                    filedict[filename[len(dir)+1:]] = None	# no '/' trailing
                    break
            process(fp, outfp)
            outfp.close()
            fp.close()

def process(fp, outfp, env = {}):
    """
    This function processes the opened file fp and write the result into the
    opened file outfp, using the environment given by env
    """
    lineno = 0
    while 1:
        line = fp.readline()
        if not line: 
            break
        lineno += 1
        n = p_define.match(line, 0)
        if n >= 0:
            # gobble up continuation lines
            while line[-2:] == '\\\n':
                nextline = fp.readline()
                if not nextline:
                    break
                lineno += 1
                line += nextline
            name = n.group(2)
            body = line[n.end(0):]
            # replace ignored patterns by spaces
            for p in ignores:
                body = re.sub(p, ' ', body)
            # replace char literals by ord(...)
            body = re.sub(p_char, 'ord(\\0)', body)
            stmt = '%s = %s\n' % (name, string.strip(body))
            try:
                exec stmt in env
            except:
                sys.stderr.write('Skipping: %s' % line)
            else:
                outfp.write(stmt)
        n = p_macro.match(line)
        if n >= 0:
            macro, arg = n.group(1, 2)
            while line[-2:] == '\\\n':
                nextline = fp.readline()
                if not nextline: 
                    break
                lineno += 1
                line += nextline
            body = line[1 + n.end(2):]
            for p in ignores:
                body = re.sub(p, ' ', body)
            body = re.sub(p_char, 'ord(\\0)', body)
            stmt = 'def %s(%s): return %s\n' % (macro, arg, body)
            try:
                exec stmt in env
            except:
                sys.stderr.write('Skipping: %s' % line)
            else:
                outfp.write(stmt)
        n = p_include.match(line) 
        if n >= 0:
            filename = n.group(1)
            if not filedict.has_key(filename):
                filedict[filename] = None
                inclfp = None
                for dir in searchdirs:
                    try:
                        inclfp = open(dir + '/' + filename, 'r')
                        break
                    except IOError:
                        pass
                if inclfp:
                    outfp.write('\n# Included from %s\n' % filename)
                    process(inclfp, outfp, env)
                else:
                    sys.stderr.write('Warning - could not find file %s' % filename)

main()
