# Copyright, 1999, Regents of the University of California 
# Please see file Legal.htm
"""Characteristics of Fortran compilers."""
import string, os, types
import semantics, configuration

class Typeinfo:
    "A Fortran type translation table."
    def __init__ (self, ctype, pcode, parray):
        self._ctype = ctype
        self._pcode = pcode
        self._parray = parray

    def python_code (self):
        return self._pcode

    def python_array_type (self):
        return self._parray

    def c_type (self):
        return self._ctype

    def __repr__ (self):
        s = str((self.c_type(), self.python_code(), self.python_array_type()))
        return 'Typeinfo' + s
        
t_int     = Typeinfo("int", "i", "PyArray_INT")
t_long    = Typeinfo("long", "l", "PyArray_LONG")
t_float   = Typeinfo("float", "f", "PyArray_FLOAT")
t_double  = Typeinfo("double", "d", "PyArray_DOUBLE")
# note that there is no complex float scalar in Python
# code given is a fake and should fail if not coded around by generator
t_complex = Typeinfo("Py_complex_float", "F", "PyArray_CFLOAT")
# note that Py_BuildValue does not recognize D.
t_dcomplex =Typeinfo("Py_complex", "D", "PyArray_CDOUBLE")
t_char     =Typeinfo("char", "c", "PyArray_STRING")
t_string  = Typeinfo("char*", "s#", "PyArray_STRING")
t_none    = Typeinfo("void", "Error", "Error")

standard_typedict = { 
    "none": t_none,
    "integer": t_long,
    "real" : t_float,
    "real(4)" : t_float,
    "real(8)" : t_double,
    "doubleprecision" : t_double,
    "complex" : t_complex,
    "complex(4)" : t_complex,
    "complex(8)" : t_dcomplex,
    "doublecomplex" : t_dcomplex,
    "character" : t_char,
    "character*(*)": t_string ,
}

alpha_typedict = { \
    "none": t_none,
    "integer": t_int,
    "real" : t_float,
    "real(4)" : t_float,
    "real(8)" : t_double,
    "doubleprecision" : t_double,
    "complex" : t_complex,
    "complex(4)" : t_complex,
    "complex(8)" : t_dcomplex,
    "doublecomplex" : t_dcomplex,
    "character" : t_char,
    "character*(*)": t_string \
}

class Compiler:
    """Abstract interface to compiler name mangling, etc.
       Each realization must define attributes "dirlist" and "liblist" giving the
       locations and names of the run-time libraries for passing to linker.
    """

    def link_name (self, name):
        "Return the linker's name for a Fortran routine named name."  
        return name

    def executable_name (self):
        "Return the name you type to execute the Fortran compiler."
        raise RuntimeError, "executable_name method missing, contact Pyfort author."
            
    def transpose_option (self):
        "Return the default value for TRANSPOSE_OPTION."
        return 1

    def mirror_option (self):
        "Return the default value for MIRROR_OPTION."
        return 2

    def header(self):
        "Return a string containing anything needed to add to the header."
        return ''

    def c_type (self, dec):
        "Return the basic C type for a type."
        assert semantics.is_FortranDeclaration(dec)
        ftype = dec.type
        f = str(ftype)
        try:
            c = self.typedict[f].c_type()
        except KeyError:
            print "Unknown Fortran type: " + f
            raise SystemExit, 1
        return c

    def python_code (self, dec):
        "Return the PyArg_ParseTuple code for a declaration."
        assert semantics.is_FortranDeclaration(dec)
        ftype = dec.type
        f = str(ftype)
        try:
            return self.typedict[f].python_code()
        except KeyError:
            print "Unknown Fortran type: " + f
            raise SystemExit, 1

    def python_array_type (self, dec):
        "Return the PyArray_... type for a declaration."
        assert semantics.is_FortranDeclaration(dec)
        ftype = dec.type
        f = str(ftype)
        try:
            return self.typedict[f].python_array_type()
        except KeyError:
            print "Unknown Fortran type: " + f
            raise SystemExit, 1

    def modify_routine(self, routine):
        # first replace any floating-type arguments with length-one arrays
        # this will ensure uniform treatment of casting, work around
        # problems of no Py_complex_float, etc.
        for a in routine.arguments():
            d = routine.declaration(a)
            ct = self.c_type(d)
            if ct in ["float","double","Py_complex_float","Py_complex"]:
                if not d.rank():
                    d.dimlist = ["1"]

    def actual_argument_list(self, routine):
        "Return the actual argument list to be used in a call to routine."
        formal_argument_list = routine.arguments()
        aal = []
        for a in formal_argument_list:
            d = routine.declaration(a)
            c = self.c_type(d)
            n = string.upper(a)
            if d.rank():
                aa = '(' + c + '*) (a' + n + '->data)'
            elif c == "char*":
                aa = n
            else:
                aa = '&' + n
            aal.append(aa)

        for a in formal_argument_list:
            d = routine.declaration(a)
            c = self.c_type(d)
            n = string.upper(a)
            if c == "char*":
                aal.append('n' + n)
        return aal

    def parser_argument_list(self, routine):
        "Return the argument list portion of the PyArg_ParseTuple call."
        python_argument_list = routine.python_inputs()
        pal = []
        for a in python_argument_list:
            d = routine.declaration(a)
            c = self.c_type(d)
            n = string.upper(a)
            if c == "char*":
                aa = '&' + n + ', &n' + n
            else:
                aa = '&' + n 
            pal.append(aa)
        if pal:
            return string.join(pal, ", ")
        else:
            return ''
        
    def build_argument_list(self, routine, flag):
        "Return the argument list portion of the Py_BuildValue call."
        return_value_list = routine.python_outputs()
        bal = []
        for a in return_value_list:
            d = routine.declaration(a)
            c = self.c_type(d)
            if routine.is_function() and a == routine.routine_name():
            # The is_function() check is needed because a function may have
            # an argument a with the same name as the routine.
                if flag:
                    n = "rfortran_result_array"
                else:
                    n = "fortran_result"
            elif d.rank():
                n = 'r' + string.upper(a) 
            else:
                n = string.upper(a)
            if c == "char*":
                aa = n + ', n' + string.upper(a)
            else:
                aa = n
            bal.append(aa)
        if bal:
            return "," + string.join(bal, ", ")
        else:
            return ''
        
    def prototype(self, routine):
        """Return the prototype for the Fortran call"""
        routine_name = routine.routine_name()
        link_name = self.link_name (routine_name)
        c_return_type = self.c_type(routine.declaration(routine_name))
        D = {}
        D['link_name'] = link_name
        D['return_type'] = c_return_type
        prototype_body = ''
        prototrail = ''
        formal_argument_list = routine.arguments()
        for a in formal_argument_list:
            d = routine.declaration (a)
            c = self.c_type(d)
            n = string.upper(a)
            if c == "char*":
                pr = c 
                st = ', int n' + n     
            else:
                pr = c + '*'
                st = ''
            if prototype_body: 
                prototype_body = prototype_body + ', '
            prototype_body = prototype_body + \
                pr + ' ' + string.upper(a)
            prototrail = prototrail + st
        prototype_total = prototype_body + prototrail
        if not prototype_total: prototype_total = 'void'
        D['prototype'] = prototype_total
        text = \
"""
extern %(return_type)s %(link_name)s(%(prototype)s);
"""
        return text % D


class F77Compiler (Compiler):
    def __init__ (self, typedict=standard_typedict, 
            ppu_option = 0, 
            case_option="lower",
            dirlist=[],
            liblist=[],
            ename= "f77"):
        self.typedict = typedict
        self.ppu_option = ppu_option
        self.case_option = case_option 
        self.dirlist = dirlist
        self.liblist = liblist
        self._e = ename

    def executable_name (self):
        return self._e

    def set_ppu (self, ppu):
        self.ppu_option = ppu
 
    def set_case (self, to_case):
        self.case_option = to_case
 
    def header(self):
        "Return a string containing anything needed to add to the header."
        text = \
"""
/* 
    Built by PyFort for compiler with link conventions:
       Case option: %s
       Post-position underscore: %d
       Strings passed as address plus extra int argument for length.
*/
"""
        return text % (self.case_option, self.ppu_option)

    def link_name (self, name):
        if self.case_option == "as-s":
            n = name
        elif self.case_option == "upper":
            n = string.upper(name)
        else:
            n = string.lower(name)
        if self.ppu_option:
            n = n + '_'
        return n


class AbsoftCompiler (F77Compiler):
    def __init__ (self,
            dirlist,
            case_option="lower",
            liblist=['V77','fio','U77','f77math'],
            ename='f77'):
        F77Compiler.__init__ (self, typedict=standard_typedict,
                                ppu_option=0,
                                case_option=case_option,
                                dirlist=dirlist,
                                liblist=liblist,
                                ename=ename)

    def modify_routine(self, routine):
        "Absoft compiler needs special handling of character arguments"
        Compiler.modify_routine (self, routine)
        if routine.return_type().name=='character':
            routine.make_procedure()


class G77Compiler (Compiler):
    def __init__ (self, typedict=standard_typedict):
        self.dirlist = []
        self.liblist = ['g2c']
        self.typedict = typedict
    
    def executable_name (self):
        return "g77"

    def header(self):
        "Return a string containing anything needed to add to the header."
        text = \
"""
/* 
    Built by PyFort for g77 compiler.
*/
"""
        return text 

    def link_name (self, name):
        j = string.find(name, "_")
        if j < 0:
            n = name + '_'
        else:
            n = name + '__'
        return string.lower(n)


class Fort77Compiler (Compiler):
    def __init__ (self, typedict=standard_typedict,lib='f2c'):
        self.dirlist = []
        self.liblist = [lib]
        self.typedict = typedict
    
    def executable_name (self):
        return "fort77"

    def header(self):
        "Return a string containing anything needed to add to the header."
        text = \
"""
/* 
    Built by PyFort for the fort77 (GNU Darwin) compiler.
*/
"""
        return text 

    def modify_routine(self, routine):
        "Fort77 compiler needs special handling of character arguments"
        Compiler.modify_routine (self, routine)
        intent = "hidden"
        type = semantics.FortranType ("integer")
        if routine.return_type().name=='character':
            routine.make_procedure()
        for argument in routine.arguments():
            if self.c_type(routine.dict[argument]) == "char":
                hidden_argument = 'n' + argument + 'hidden'
                routine.add_argument (hidden_argument, type, intent, argument)

    def link_name (self, name):
        j = string.find(name, "_")
        if j < 0:
            n = name + '_'
        else:
            n = name + '__'
        return string.lower(n)

    def actual_argument_list(self, routine):
        "Return the actual argument list to be used in a call to routine."
        formal_argument_list = routine.arguments()
        aal = []
        if routine.return_type().name=='character':
            aa = '&fortran_result'
            aal.append(aa)
        for a in formal_argument_list:
            d = routine.declaration(a)
            c = self.c_type(d)
            n = string.upper(a)
            if d.rank():
                aa = '(' + c + '*) (a' + n + '->data)'
                aal.append(aa)
            elif c == "char*":
                aa = n
                aal.append(aa)
            else:
                aa = '&' + n
                aal.append(aa)

        for a in formal_argument_list:
            d = routine.declaration(a)
            c = self.c_type(d)
            n = string.upper(a)
            if c == "char*":
                aal.append('n' + n)
        return aal


class CCompiler (Compiler):
    def __init__ (self, typedict=standard_typedict):
        self.dirlist = []
        self.liblist = []
        self.typedict = typedict
    
    def executable_name (self):
        return "cc"  # this is used in generator.py.

    def transpose_option (self):
        "Return the default value for TRANSPOSE_OPTION."
        return 0

    def mirror_option (self):
        "Return the default value for MIRROR_OPTION."
        return 0

    def header(self):
        "Return a string containing anything needed to add to the header."
        text = \
"""
/* 
    Built by PyFort for C compiler.
*/
"""
        return text 

    def modify_routine(self, routine):
        "No modifications are needed for the C compiler"
        pass

    def actual_argument_list(self, routine):
        "Return the actual argument list to be used in a call to routine."
        formal_argument_list = routine.arguments()
        aal = []
        for a in formal_argument_list:
            d = routine.declaration(a)
            c = self.c_type(d)
            n = string.upper(a)
            if d.rank():
                if d.rank() > 1 and routine.dict[a].allocatable:
                    aa = d.rank()*'p' + 'a' + n
                else:
                    aa = '(' + c + '*'*d.rank() + ') (a' + n + '->data)'
            elif routine.dict[a].intent=='out':
                aa = '&'+n
            else:
                aa = n
            aal.append(aa)

        for a in formal_argument_list:
            d = routine.declaration(a)
            c = self.c_type(d)
            n = string.upper(a)
            if c == "char*":
                aal.append('n' + n)
        return aal

    def prototype(self, routine):
        """Return the prototype for the C call"""
        routine_name = routine.routine_name()
        link_name = self.link_name (routine_name)
        c_return_type = self.c_type(routine.declaration(routine_name))
        c_return_type = c_return_type.replace('*','')
        D = {}
        D['link_name'] = link_name
        D['return_type'] = c_return_type
        prototype_body = ''
        prototrail = ''
        formal_argument_list = routine.arguments()
        for a in formal_argument_list:
            d = routine.declaration (a)
            c = self.c_type(d)
            n = string.upper(a)
            if c == "char*":
                pr = c 
                st = ', int n' + n     
            elif d.rank():
                pr = c + '*'*d.rank()
                st = ''
            elif routine.dict[a].intent=='out':
                pr = c + '*'
                st = ''
            else:
                pr = c
                st = ''
            if prototype_body: 
                prototype_body = prototype_body + ', '
            prototype_body = prototype_body + \
                pr + ' ' + string.upper(a)
            prototrail = prototrail + st
        prototype_total = prototype_body + prototrail
        if not prototype_total: prototype_total = 'void'
        D['prototype'] = prototype_total
        text = \
"""
extern %(return_type)s %(link_name)s(%(prototype)s);
"""
        return text % D


class   VF77Compiler (F77Compiler):

    def actual_argument_list(self, routine):
        "Return the actual argument list to be used in a call to routine."
        formal_argument_list = routine.arguments()
        aal = []
        for a in formal_argument_list:
            d = routine.declaration(a)
            c = self.c_type(d)
            n = string.upper(a)
            if d.rank():
                aa = '(' + c + '*) (a' + n + '->data)'
            elif c == "char*":
                aa = n
            else:
                aa = '&' + n
            aal.append(aa)
            if c == "char*":
                aal.append('n' + n)
        return aal

    def prototype(self, routine):
        """Return the prototype for the Fortran call"""
        routine_name = routine.routine_name()
        link_name = self.link_name (routine_name)
        c_return_type = self.c_type(routine.declaration(routine_name))
        D = {}
        D['link_name'] = link_name
        D['return_type'] = c_return_type
        prototype_body = ''
        formal_argument_list = routine.arguments()
        for a in formal_argument_list:
            d = routine.declaration (a)
            c = self.c_type(d)
            n = string.upper(a)
            if c == "char*":
                pr = c 
                st = ', int n' + n     
            else:
                pr = c + '*'
                st = ''
            if prototype_body: 
                prototype_body = prototype_body + ', '
            prototype_body = prototype_body + \
                pr + ' ' + string.upper(a) + st
                        
        if not prototype_body: prototype_body = 'void'
        D['prototype'] = prototype_body
        text = \
"""
extern __declspec(dllimport) %(return_type)s __stdcall %(link_name)s(%(prototype)s);
"""
        return text % D

compiler_ids = [
    'default',
    'solaris',
    'gcc',
    'cc',
    'absoft77',
    'absoft90',
    'pgf77',
    'pgf90',
    'g77',
    'f77',
    'g77alpha',
    'sgi',
    'vf',
    'f77_OSF1',
    'macg77',
    'fort77'
    ]

def get_compiler (compiler_id):
    "Return a compiler object based on compiler_id."
    if compiler_id == 'default':
        compiler_id = configuration.default_compiler
    if compiler_id not in compiler_ids:
        print "Compiler named", compiler_id, "is not defined."
        raise SystemExit, 1

    elif compiler_id == 'solaris':
        return F77Compiler(typedict=standard_typedict, ppu_option=1,
                              dirlist=['/opt/SUNWspro/SC4.2/lib'], 
                              liblist=['F77','M77','sunmath','m','c']
                              )

    elif compiler_id == 'gcc':
        return CCompiler()

    elif compiler_id == 'cc':
        return CCompiler()

    elif compiler_id == 'absoft77':
        try:
            ABSOFT = os.environ.get('ABSOFT')
        except KeyError:
            print "You must set environment variable ABSOFT to use the ABSOFT compilers."
            raise SystemExit, 1
        return AbsoftCompiler(dirlist=[ABSOFT+'/lib'],
                              liblist=['V77','fio','U77','f77math'],
                              ename='f77')

    elif compiler_id == 'absoft90':
        try:
            ABSOFT = os.environ.get('ABSOFT')
        except KeyError:
            print "You must set environment variable ABSOFT to use the ABSOFT compilers."
            raise SystemExit, 1
        return AbsoftCompiler(dirlist=[ABSOFT+'/lib'],
                              case_option='upper',
                              liblist=['U77','fio','f77math','f90math'],
                              ename = "f90")

    elif compiler_id == 'pgf77':
        try:
            PGI = os.environ.get('PGI')
        except KeyError:
            print "You must set environment variable PGI to use the PG compilers."
            raise SystemExit, 1
        return F77Compiler(typedict=standard_typedict, ppu_option=1,
                                dirlist=[PGI + '/linux86/lib'], 
                                liblist=['pgftnrtl','pgc'],
                                ename = 'pgf77'
                              )

    elif compiler_id == 'pgf90':
        try:
            PGI = os.environ.get('PGI')
        except KeyError:
            print "You must set environment variable PGI to use the PG compilers."
            raise SystemExit, 1
        return F77Compiler(typedict=standard_typedict, ppu_option=1,
                    dirlist = [PGI + '/linux86/lib'],
                    liblist = ['pgf90',
                               'pgf90_rpm1',
                               'pgf902',
                               'pgf90rtl',
                               'pghpf',
                               'pgc'],
                    ename = 'pgf90'
                    )

    elif compiler_id == 'g77':
        return G77Compiler()

    elif compiler_id == 'f77':
        return F77Compiler()

    elif compiler_id == 'g77alpha':
        return G77Compiler(typedict = alpha_typedict)

    elif compiler_id == 'sgi':
        return F77Compiler(typedict=standard_typedict, ppu_option=1,
                    dirlist = ['/usr/lib32'],
                    liblist = ['ftn'],
                    ename = "f77")

    elif compiler_id == 'vf': #win32, Visual Fortran
        from configuration import vfroot
        return VF77Compiler(typedict=standard_typedict, ppu_option=0,
                    case_option="upper",
                    dirlist=[os.path.join(vfroot, 'Lib')],
                    liblist =['dfor'],
                    ename= os.path.join(vfroot, 'Bin', 'df.exe')
                    )
    
    elif compiler_id == 'f77_OSF1': #Dec alpha
        return F77Compiler(typedict=alpha_typedict, ppu_option=1,
                              dirlist=['/usr/ccs/lib/cmplrs/fortrtl'], 
                              liblist=['for','Ufor','Futil'],
                              ename = 'f77'
                              )    

    elif compiler_id == 'fort77': # GNU Darwin fortran compiler for Macintosh
        return Fort77Compiler()

    elif compiler_id == 'macg77': # GNU fortran compiler g77 on the Macintosh
        return Fort77Compiler(lib='g2c')


syntax highlighted by Code2HTML, v. 0.9.1