#!/usr/bin/env python3

## qc - a compiler for Qu-Prolog
## Copyright (C) 2000-Sun 14 May 2023 11:05:00 AEST 
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program 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 General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
## 02110-1301, USA.

import sys
import getopt
import subprocess
import shutil
import os
import stat

##############################

preprocess='@QPHOME@/bin/qppp'
expand='@QPHOME@/bin/qg'
qpcompile='@QPHOME@/bin/qc1'
compversion='qup'
assemble='@QPHOME@/bin/qa'
link='@QPHOME@/bin/ql'
execute='@QPHOME@/bin/qem'
libqofiles="@QPHOME@/prolog/compiler/*.qo @QPHOME@/prolog/library/*.qo"

##############################


options = 'Zd:p:rs:u:a:B:O:I:n:i:C:e:h:T:H:GD:ER:Sco:vX'

version3 = sys.version.startswith('3')

version3_print = """
def printit(text):
    print(text)
"""
version2_print = """
def printit(text):
    print text
"""
if version3:
    exec(version3_print)
else:
    exec(version2_print)

def usage():
    printit("Usage: qc [%s] file.q[ligso] ... [-- options for Qu-Prolog programs]" % options)
 
def verbose(flag, text):
    if flag:
        printit(text)

def shcall(call):
    retcode = subprocess.call(call, shell=True)
    if retcode != 0:
        printit("Error in %s" % ' '.join(call))
        sys.exit()  

def wrapq(filename):
    return '"'+filename+'"'

def main():
    args = sys.argv[1:]
    qlfiles = []
    qlefiles=[]
    qifiles=[]
    qgfiles=[]
    qgefiles=[]
    qsfiles=[]
    qofiles=[]
    
    newqifiles=[]
    newqgfiles=[]
    newqgefiles=[]
    newqsfiles=[]
    newqofiles=[]

    eflag = False
    gflag = False
    sflag = False
    cflag = False
    vflag = False
    rlflag = False

    Goptions = []
    Poptions = []
    Coptions = []
    Aoptions = []
    Loptions = []
    Eoptions = []
    exfile = 'a.out'
    compversion = 'qup'

    try:
        optlist, args = getopt.getopt(args, options)
    except:
        usage()
        sys.exit()
    
    for (option, arg) in optlist:
        option = option[1:]
        if option == 'Z':
            compversion = 'boot'
        elif option == 'E':
            eflag = True
        elif option == 'G':
            gflag = True
        elif option == 'S':
            sflag = True
        elif option == 'c':
            cflag = True
        elif option == 'v':
            vflag = True
        elif option == 'r':
            rlflag = True
        elif option == 'o':
            exfile = arg
            if arg == '':
                printit('qc: null output file name')
                sys.exit()
            elif arg.endswith('.qg') or  arg.endswith('.ql') or  arg.endswith('.qs'):
                printit('qc: dangerous output file name: %s' % arg)
                sys.exit()
            elif arg.endswith('.qge') or  arg.endswith('.qle') or  arg.endswith('.qse'):
                printit('qc: dangerous output file name: %s' % arg)
                sys.exit()
        elif option == 'D':
            if arg.startswith('-'):
                usage()
                sys.exit()
            Poptions.append("-%s%s" % (option, arg))
        elif option == 'R':
            if arg.startswith('-'):
                usage()
                sys.exit()
            Goptions.append("-%s %s" % (option, arg))
        elif option in list('dpa'):
            Loptions.append("-%s %s" % (option, arg))
            Eoptions.append("-%s %s" % (option, arg))
        elif option in list('su'):
            Coptions.append("-%s %s" % (option, arg))
            Loptions.append("-%s %s" % (option, arg))
            Eoptions.append("-%s %s" % (option, arg))            
        elif option in list('BOICehTHni'):
            Coptions.append("-%s %s" % (option, arg))
        elif option == 'X':
            Eoptions.append("-%s %s" % (option, arg)) 
        else:
            usage()
            sys.exit()

    otherargs = []
    for i, arg in enumerate(args):
        if arg.endswith('.ql'):
            qlfiles.append(arg)
        elif arg.endswith('.pl'):
            qlfiles.append(arg)
        elif arg.endswith('.qle'):
            qlefiles.append(arg)
        elif arg.endswith('.qi'):
            qifiles.append(arg)
        elif arg.endswith('.qg'):
            qgfiles.append(arg)
        elif arg.endswith('.qge'):
            qgefiles.append(arg)
        elif arg.endswith('.qs'):
            qsfiles.append(arg)
        elif arg.endswith('.qo'):
            qofiles.append(arg)
        else:
            if arg == '--': continue
            otherargs.append(arg)


    allfiles = qlfiles + qlefiles + qifiles + qgfiles + qgefiles + qsfiles + qofiles
    if allfiles == []:
        usage()
        sys.exit()

    verbose(vflag, "preprocessing ...")

    for arg in qlfiles:
        verbose(vflag, "      %s" % arg)
        stem = arg.rsplit('.', 1)[0]
        newfile = stem+'.qi'
        try:
            fp = open(arg, 'r')
        except:
            printit("Can't open %s" % arg)
            sys.exit()
        p = subprocess.Popen([preprocess]+Poptions, stdin=fp, stdout=subprocess.PIPE)
        try:
            ofp = open(newfile, 'w')
        except:
            printit("Can't open %s" % newfile)
            sys.exit()
        text = p.communicate()[0]
        ofp.write(text.decode())
        fp.close()
        ofp.close()
        newqifiles.append(newfile)

    if eflag: return
 
    #
    # first pass for encoded files: expand (.qle -> .qge)
    #

    verbose(vflag, "expanding encoded ...")
    
    for arg in qlefiles:
        verbose(vflag, "      %s" % arg)
        stem = arg.rsplit('.', 1)[0]
        call = [expand]+Coptions+['--']+Goptions+[stem+'.qle']
        shcall(call)
        newqgefiles.append(stem+'.qge')

    #
    # second pass: expand (.qi -> .qg)
    #
    verbose(vflag, "expanding ...")
    for arg in qifiles+newqifiles:
        verbose(vflag, "      %s" % arg)
        stem = arg.rsplit('.', 1)[0]
        if compversion == 'boot':
            shutil.copy(arg, stem+'.qg')
        else:
            call = [expand]+Coptions+['--']+Goptions+[wrapq(arg)]
            shcall([' '.join(call)])
        newqgfiles.append(stem+'.qg')
       
    for f in newqifiles:
        os.remove(f)

    if gflag: return

    #
    # third pass: compile (.qge -> .qs)
    #
    verbose(vflag, "compiling ...")
    for arg in qgefiles+newqgefiles:
        verbose(vflag, "      %s" % arg)
        stem = arg.rsplit('.', 1)[0]
        call = [qpcompile+'.'+compversion]+Coptions+[wrapq(arg)]
        shcall([' '.join(call)])
        newqsfiles=stem+'.qs'
    #
    # third pass (unencoded): compile (.qg -> .qs)
    #
    for arg in qgfiles+newqgfiles:
        verbose(vflag, "      %s" % arg)
        stem = arg.rsplit('.', 1)[0]
        call = [qpcompile+'.'+compversion]+Coptions+[wrapq(arg)]
        shcall([' '.join(call)])
        newqsfiles.append(stem+'.qs')

    for f in newqgfiles:
        os.remove(f)

    if sflag: return

    #
    # fourth pass: assemble (.qs -> .qo)
    #
    verbose(vflag, "assembling ...")
    for arg in qsfiles+newqsfiles:
        verbose(vflag, "      %s" % arg)
        stem = arg.rsplit('.', 1)[0]
        outfile = stem+'.qo'
        if exfile != "a.out" and cflag:
            call = [assemble]+Aoptions+['-i', wrapq(arg), '-o', 
                                        wrapq(exfile)]
        else:
            call = [assemble]+Aoptions+['-i', wrapq(arg), '-o', 
                                        wrapq(outfile)]
        shcall([' '.join(call)])
        newqofiles.append(stem+'.qo')

    for f in newqsfiles:
        os.remove(f)

    if cflag: return
        

    verbose(vflag, "compiling ...")
    # make an absolute version of exfile
    exfile = os.path.expandvars(exfile)
    exfile = os.path.abspath(exfile)
    savefile = exfile+'.qx'
    call = [link, '-o', wrapq(savefile)] + Loptions + [libqofiles] + \
        [wrapq(f) for f in qofiles + newqofiles]
    shcall([' '.join(call)])
    parts = os.path.split(exfile)
    base = parts[0]
    exname = parts[1]
    rl_commands_file = os.path.join(base, 'rl_commands')

    for f in newqofiles:
        os.remove(f)

    fp = open(exfile, 'w')
    if rlflag:
        fp.write('if [ "`which rlwrap`" != "" ]; then\n')
        fp.write("exec rlwrap -b ' ' -C %s -f %s " % (exname, rl_commands_file))
        fp.write(execute)
        fp.write(' ')
        fp.write(' '.join(Eoptions))
        fp.write(" -Q '"+savefile+"'"+' "$@" -- %s\n' % ' '.join(otherargs))
        fp.write('else\n')
        fp.write('exec ')
        fp.write(execute)
        fp.write(' ')
        fp.write(' '.join(Eoptions))
        fp.write(" -Q '"+savefile+"'"+' "$@" -- %s\n' % ' '.join(otherargs))
        fp.write('fi\n')
    else:
        fp.write('exec ')
        fp.write(execute)
        fp.write(' ')
        fp.write(' '.join(Eoptions))
        fp.write(" -Q '"+savefile+"'"+' "$@" -- %s\n' % ' '.join(otherargs))
    fp.close()
    os.chmod(exfile, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | \
                 stat.S_IRGRP | stat.S_IXGRP | \
                 stat.S_IROTH |stat.S_IXOTH) 
    
  
    
  

if __name__ == '__main__': 
    main()
