#!/usr/pkg/bin/perl
#    This file is part of cqual.
#    Copyright (C) 2003 The Regents of the University of California.
# 
# cqual 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, or (at your option)
# any later version.
# 
# cqual 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 cqual; see the file COPYING.  If not, write to
# the Free Software Foundation, 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.

use File::Temp qw/ tempfile /;
use File::Basename;


# Global config options -- should probably do something smarter with $CC
my $CC = "gcc";
my $cqual = (-f "/usr/pkg/bin/cqual" ? "/usr/pkg/bin/cqual" : "cqual");
my $pkgdatadir = "/usr/pkg/share/cqual"; # Yuck. Is there a better way?
my $scriptname = $0;

sub usage
{
    die <<EOF
Usage: $scriptname [-debug] [-cc <CC>] [-cqual <cqual>] [cqual options]
                            [-- [CC options]] <file1> <file2> ...
        -debug           display commands executed by $scriptname
        -cc <CC>         use <CC> instead of gcc
        -cqual <cqual>   use <cqual> instead of cqual

  For cqual command line options, see the cqual user-guide.
  For gcc command line options, see the gcc man page.
EOF
}

# argument lists shared by gcc and cqual
@cfiles = ();
@ifiles = ();

# Process cqual and script options
if ($#ARGV < 0) {
    usage();
}
$debug=0;
$fpreprocessed = 0;
$fpammode = 0;
$config="";
@cqualargs=();
@preludes=();
while ($arg = shift(@ARGV)) {
    if ($arg eq "--") {
	# Marks beginning of CC options
	last;
    } 
    elsif ($arg eq "-cc") {
	# use a different compiler
	$CC = shift (@ARGV) or die "-cc requires an argument";
    }
    elsif ($arg eq "-cqual") {
	# use a different compiler
	$cqual = shift (@ARGV) or die "-cqual requires an argument";
    }
    elsif ($arg eq "-debug") {
	$debug = 1;
    }
    elsif ($arg eq "-help" || $arg eq "--help" || $arg eq "-h") {
	usage();
    }
    elsif ($arg eq "-config") {
	# Remember this so we don't go looking for
	# default configs later
	$config = shift (@ARGV) or die "-config without file";
    }
    elsif ($arg eq "-prelude") {
	# Store these so we can run them through the preprocessor
	$prelude = shift (@ARGV) or die "-prelude without file";
	push (@preludes, $prelude);
    }
    elsif (basename($arg) eq "prelude.cq") {
	# Cqual considers these preludes, too
	push (@preludes, $arg);
    }
    elsif ($arg =~ /\.c$/) {
	# Store these for preprocessing with CC options
	push (@cfiles, $arg);
    }
    elsif ($arg =~ /\.i$/) {
	# Store these -- no preprocessing
	push (@ifiles, $arg);
    } 
    elsif ($arg eq "-fpreprocessed") {
	# Don't even preprocess .c files.
	$fpreprocessed = 1;
    }
    elsif ($arg eq "-fpam-mode") {
	# We have to glob the file names
	$fpammode = 1;
	push (@cqualargs, $arg);
    }
    else {
	# flags and stuff
	push (@cqualargs, $arg);
    }
}

# Process gcc options
@CCargs = ();
$ofile = "";
$nextisofile = 0;
foreach $arg (@ARGV) {
    if ($nextisofile) {
	# we just saw "-o"
	if ($ofile ne "") { die "Already found object $ofile\n"; }
	$ofile = $arg;
	$nextisofile = 0;
    }
    elsif ($arg =~ /\.c$/) {
	# store for preprocessing
	@cfiles = (@cfile, $arg);
    }
    elsif ($arg =~ /\.i$/) {
	# don't preprocess these
	@ifiles = (@ifiles, $arg);
    }
    elsif ($arg eq "-o") {
	# get ready for an output file
	$nextisofile = 1;
    }
    elsif ($arg eq "-fpreprocessed") {
	# never mind on the preprocessing of .c files
	$fpreprocessed = 1;
    }
    else {
	# flags, etc.
	push(@CCargs, $arg);
    }
}
if ($nextisofile) { die "-o but no file\n"; }

# If we're in pam mode, glob all the filenames
if ($fpammode) {

    if ($config) {
	@configs = glob($config);
	die "Couldn't find config file $config\n" 
	    if ($#configs != 0);
	$config = $configs[0];
    }

    @globbedpreludes = ();
    foreach $prelude (@preludes) {
	push (@globbedpreludes, glob($prelude));
    }
    @preludes = @globbedpreludes;

    @globbedcfiles = ();
    foreach $cfile (@cfiles) {
	push (@globbedcfiles, glob($cfile));
    }
    @cfiles = @globbedcfiles;

    @globbedifiles = ();
    foreach $ifile (@ifiles) {
	push (@globbedifiles, glob($ifile));
    }
    @ifiles = @globbedifiles;

}

# Look for default config/prelude files
if ($config eq "")
{
    @path = ();
    if ($ENV{CQUAL_CONFIG_DIR}) {
	push (@path, $ENV{CQUAL_CONFIG_DIR});
    }
    push (@path, ".", $pkgdatadir);

    foreach $dir (@path) {
	if (open (DUMMY, "< $dir/lattice")) {
	    close (DUMMY);
	    $config = "$dir/lattice";

	    # We only look for preludes in two places:
	    # the command line, and the directory where
	    # we find the lattice file.
	    if ($#preludes < 0) {
		@preludes = glob("$dir/*.cq");
	    }

	    break;
	}
    }
}

# Preprocess the .c files
if ($fpreprocessed) {
    @ifiles = (@ifiles, @cfiles);
} else {
    foreach $cfile (@cfiles) {
	($ifh, $ifile) = tempfile();
	@cmd = ($CC, @CCargs, "-DCQUAL", "-o", $ifile, $cfile, "-E");
	print ("@cmd\n") if ($debug);
	system(@cmd)  == 0
	    or die "$CC @CCargs failed: $?";
	@ifiles = (@ifiles, $ifile);
    }
}

# Preprocess the prelude files
@ipreludes = ();
foreach $prelude (@preludes) {
    ($ifh, $ifile) = tempfile(UNLINK => 1);
    @cmd = ($CC, "-xc", "-E", $prelude, "-o", "$ifile");
    print ("@cmd\n") if ($debug);
    system(@cmd) == 0
	or die "@cmd failed: $?";
    push (@ipreludes, $ifile);
}

# analyze everything with cqual
if ($#ifiles >= 0) {
    @cmd = ($cqual, @cqualargs, @ifiles);
    if (!($config eq "")) {
	push (@cmd, "-config", $config);
    }
    foreach $prelude (@ipreludes) {
	push (@cmd, "-prelude", $prelude);
    }
    print ("@cmd\n") if ($debug);
    system(@cmd) == 0 
	or die "cqual failed with exitcode " . ($?>>8) . ": $!";
}
else
{
    print STDERR ("Could not parse command line args.  No .c or .i files specified?\n");
    usage();
}
