/* Compiler driver for agfl. Checks installation and configuration of system
 * and availability of auxiliary tools. Implements make-like functionality for
 * grammar and lexicon.
 *
 * Copyright 2001, KUN.
 *
 * 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 Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/* "$Id: gen.c,v 1.50 2005/08/18 13:48:00 olafs Exp $" */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>

/*------------------------------------------------------------------------------
// Platform specific installation details. Define
//	WIN32		Windows NT
//	LINUX		Linux
//	SOLARIS		Solaris
//	SUNOS		SunOS
//	AIX		AIX
//	PARIX		Parix
//----------------------------------------------------------------------------*/

#if defined(WIN32)
#  define DIR_SEP   "\\"
#  define EXE       ".exe"
#  define OUT_EXE   ".exe"
#  ifndef GENBASE
#    define GENBASE "\\agfl"
#  endif
#else
#  define DIR_SEP   "/"
#  define EXE       ""
#  define OUT_EXE   ""
#endif

/*-----------------------------------------------------------------
// GENAS is the assembly command used by the AGFL compiler.
//---------------------------------------------------------------*/
#ifndef GENAS
#ifdef DEBUG
#  define GENAS "gcc -c -x assembler-with-cpp -g"
#else
#  define GENAS	"gcc -c -x assembler-with-cpp"
#endif
#endif

/*
** GENCC is the C compiler used for linking with the run-time library.
*/

#ifndef GENCC
#  define GENCC	"gcc"
#endif

/*---------------------------------------------------------------------
// Developers may use mygen instead of gen. It has more options
// available and does not pass optimization options to agfl by default.
// Furthermore, mygen passes debug options to the linker.
---------------------------------------------------------------------*/

#if MAINTAINER_MODE
#  define DIRECTORS_FLAG	0
#  define LEFTFAC_FLAG		0
#  define NEGMEMO_FLAG		0
#  define POSMEMO_FLAG		0
#  define STRIP_FLAG		0
#else
#  define DIRECTORS_FLAG	1
#  define LEFTFAC_FLAG		0
#  define NEGMEMO_FLAG		1
#  define POSMEMO_FLAG		1
#  define STRIP_FLAG		1
#endif

/*
// On a.out platforms, agfl must prefix labels in the code with underscores.
// TODO (con)figure it out automatically - some (old) NetBSD versions may
// still have it. __ELF__ may not be the perfect discriminator.
*/

#if LEADING_UNDERSCORE
#  define USCORE_FLAG	1
#else
#  define USCORE_FLAG	0
#endif

void output_error (const char* fmt, ...)
{
    va_list argp;

    fputs("agfl: ", stdout);

    va_start(argp, fmt);
    vfprintf(stdout, fmt, argp);
    va_end(argp);

    fputs("\n", stdout);
}

void output_log (const char* fmt, ...)
{
    va_list argp;

    va_start(argp, fmt);
    vfprintf(stdout, fmt, argp);
    va_end(argp);

    fputs("\n", stdout);
}

static void show_version()
{
    printf("%s version %s, Copyright 2005, Radboud University of Nijmegen.\n", PACKAGE, VERSION);
}

static char gen_help_mesg[] =
  "Usage: agfl [ -a ] [ -c ] [ -f ] [ -G ] [ -h ] [ -m ] [ -p ] [ -v ] [ -w ] <grammar>\n"
  "\n"
  "Options are:\n"
  "    -a           analyze additional properties\n"
  "    -c           show counters after parsing\n"
  "    -f           force new generation\n"
  "    -G           generate generative grammar\n"
  "    -h           show this help, and exit\n"
  "    -m           prefix nonterminals with module name\n"
  "    -p           profile grammar\n"
  "    -V           show version\n"
  "    -v           be verbose\n"
  "    -w           suppress compiler warnings\n"
  "\n"
  "Arguments are:\n"
  "    <grammar>    name of grammar file  (.gra)\n"
  "\n";

#if MAINTAINER_MODE
static char mygen_help_mesg[] =
  "Additional options for debug version:\n"
  "    -e           link Electric Fence\n"
  "    -g           debug\n"
  "    -l           write log file\n"
  "    -s           strip executable\n"
  "    -S           leave generated assembler file\n"
  "    -t           show compile time\n"
  "    -A           suppress well-formedness analysis\n"
  "    -D           use director set lookahead\n"
  "    -F           left-factorize grammar\n"
  "    -P           use positive memoizing\n"
  "    -N           use negative memoizing\n"
  "    -T           trace\n"
  "    -_           toggle underscore flag\n"
  "\n";
#endif

void show_help(FILE* file)
{
    show_version();
    fputs(gen_help_mesg, stdout);

#if MAINTAINER_MODE
    fputs(mygen_help_mesg, stdout);
#endif
}

typedef struct
{
    int additional_analysis;            /* -a */
    int counters;                       /* -c */
    int electric_fence;                 /* -e */
    int force;                          /* -f */
    int debug;                          /* -g */
    int generative_grammar;             /* -G */
    int write_log;                      /* -l */
    int profile;                        /* -p */
    int strip;                          /* -s */
    int keep_assembler_file;            /* -S */
    int show_time;                      /* -t */
    int verbose;                        /* -v */
    int suppress_warnings;              /* -w */
    int suppress_analysis;              /* -A */
    int directors;                      /* -D */
    int left_factorize;                 /* -F */
    int positive_memo;                  /* -P */
    int negative_memo;                  /* -N */
    int trace;                          /* -T */
    int underscore;                     /* -_ */
    const char*	grammar_file;
    int module_names;                   /* -m */
} Options;

void init_options(Options* options)
{
    options->additional_analysis = 0;
    options->counters = 0;
    options->electric_fence = 0;
    options->force = 0;
    options->debug = 0;
    options->generative_grammar = 0;
    options->write_log = 0;
    options->profile = 0;
    options->strip = STRIP_FLAG;
    options->keep_assembler_file = 0;
    options->show_time = 0;
    options->verbose = 0;
    options->suppress_warnings = 0;
    options->suppress_analysis = 0;
    options->directors = DIRECTORS_FLAG;
    options->left_factorize = LEFTFAC_FLAG;
    options->positive_memo = POSMEMO_FLAG;
    options->negative_memo = NEGMEMO_FLAG;
    options->trace = 0;
    options->underscore = USCORE_FLAG;
    options->grammar_file = NULL;
    options->module_names = 0;
}

void process_options(int argc, const char** argv, Options* options)
{
    const char*	opt;
    char c;

/*
** Check whether options specified
*/
    if (argc == 1) {
        output_error("compiler driver for %s version %s", PACKAGE, VERSION);
        exit(1);
    }

/*
** Process options
*/
    while((opt = *++argv)) {
        if ((opt[0] == '-') && (opt[1] != '\0')) {
            while((c = *++opt)) {
                switch (c) {
                    case 'a':
                        options->additional_analysis++;
                        break;
                    case 'c':
                        options->counters++;
                        break;
                    case 'f':
                        options->force++;
                        break;
                    case 'G':
			fprintf(stdout, "This version of AGFL does not yet support building generating grammars.\n");
			exit(1);
                        options->generative_grammar = 1;
                        options->directors = 0;
                        options->positive_memo = 0;
                        options->negative_memo = 0;
                        break;
                    case 'h':
                        show_help(stdout);
                        exit(1);
                    case 'm':
                        options->module_names = 1;
                        break;
                    case 'p':
                        options->profile++;
                        break;
                    case 'V':
                        show_version();
                        exit(0);
                    case 'v':
                        options->verbose++;
                        break;
                    case 'w':
                        options->suppress_warnings++;
                        break;
#if MAINTAINER_MODE
                    case 'e':
                        options->electric_fence++;
                        break;
                    case 'g':
                        options->debug++;
                        break;
                    case 'l':
                        options->write_log++;
                        break;
                    case 's':
                        options->strip++;
                        break;
                    case 'S':
                        options->keep_assembler_file++;
                        break;
                    case 't':
                        options->show_time++;
                        break;
                    case 'A':
                        options->suppress_analysis++;
                        break;
                    case 'D':
                        options->directors++;
                        break;
                    case 'F':
                        options->left_factorize++;
                        break;
                    case 'P':
                        options->positive_memo++;
                        break;
                    case 'N':
                        options->negative_memo++;
                        break;
                    case 'T':
                        options->trace++;
                        break;
                    case '_':
                        options->underscore = !options->underscore;
                        break;
#endif /* MAINTAINER_MODE */
                    default:
                        output_error("fatal: unknown option `-%c'", c);
                        output_error("use `agfl -h' for help");
                        exit(1);
                }
            }
        } else {
            if(options->grammar_file == NULL) {
                options->grammar_file = opt;
            } else {
                output_error("fatal: duplicate input file specified"
                            " (`%s' and `%s')", options->grammar_file, opt);
                exit(1);
            }
        }
    }

/*
** Check whether options valid
*/
    if (options->grammar_file == NULL) {
        output_error("fatal: no input file specified");
        exit(1);
    }

/*
 * 2002-03-19  PB  reporting of input options added
 */

    if (options->verbose >= 2) {
	fprintf(stdout, "  %s %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s %s\n",
#if MAINTAINER_MODE
	    "agflmaint",
#else
	    "agfl",
#endif /* MAINTAINER_MODE */
	options->additional_analysis ? " -a" : "",
	options->counters            ? " -c" : "",
	options->electric_fence      ? " -e" : "",
	options->force               ? " -f" : "",
	options->debug               ? " -g" : "",
	options->generative_grammar  ? " -G" : "",
	options->write_log           ? " -l" : "",
	options->profile             ? " -p" : "",
	options->strip               ? " -s" : "",
	options->keep_assembler_file ? " -S" : "",
	options->show_time           ? " -t" : "",
	options->verbose > 0         ? " -v" : "",
	options->verbose > 1         ? " -v" : "", 
	options->suppress_warnings   ? " -w" : "",
	options->suppress_analysis   ? " -A" : "",
	options->directors           ? " -D" : "",
	options->left_factorize      ? " -F" : "",
	options->positive_memo       ? " -P" : "",
	options->negative_memo       ? " -N" : "",
	options->trace               ? " -T" : "",
	options->underscore          ? " -_" : "",
	options->module_names        ? " -m" : "",
	options->grammar_file        ? options->grammar_file : "");
    }
}

void
strip_extension(char* path, const char* ext)
{
    int len1 = strlen(path);
    int len2 = strlen(ext);

    if (len1 > len2 && strcmp(path + len1 - len2, ext) == 0) {
        path[len1 - len2] = '\0';
    }
}

int
file_exists(const char* path)
{
    struct stat sbuf;
    return stat(path, &sbuf) >= 0;
}

/*
// S_ISREG should be in sys/stat.h
*/

#ifndef S_ISREG
#define S_ISREG(m) (((m)&S_IFMT) == S_IFREG)
#endif

int is_regular_file(const char* path)
{
    struct stat sbuf;
    return stat(path, &sbuf) >= 0 && S_ISREG(sbuf.st_mode);
}

int is_directory(const char* path)
{
  struct stat sbuf;
  return stat(path, &sbuf) >= 0 && S_ISDIR(sbuf.st_mode);
}

void get_last_dir(char* name, const char *path)
{
    const char*	dir_sep = DIR_SEP;
    const char*	last_dir;
    const char*	p;

    last_dir = path;
    for (p = path; p[0] != '\0'; p++) {
        if (p[0] == *dir_sep && p[1] != '\0') {
            last_dir = p;
        }
    }

    strcpy(name, last_dir);
}

void
get_base_name(char* base, const char* name, const char* ext)
{
    char	path[PATH_MAX];

    strcpy(base, name);
    strip_extension(base, ext);
    snprintf(path, PATH_MAX, "%s%s", base, ext);

    if (!file_exists(path)) {
        if (is_directory(base)) {
            char name[PATH_MAX];

            get_last_dir(name, base);
            strcat(base, DIR_SEP);
            strcat(base, name);
            snprintf(path, PATH_MAX, "%s%s", base, ext);
        }

        if (!file_exists(path)) {
            output_error("fatal: cannot locate input file `%s'", path);
            exit(1);
        }
    }

    if (!is_regular_file(path)) {
        output_error("fatal: `%s' is not a regular file", path);
        exit(1);
    }
}

/*
** Paths to files of AGFL system, to be prefixed with base directory
*/

#define GENBIN		DIR_SEP "bin"
#define GENLIB		DIR_SEP "lib"
#define GENINCLUDE	DIR_SEP "include"

#define LEXGEN		GENBIN DIR_SEP "agfl-lexer" EXE
#define AGFL		GENBIN DIR_SEP "agfl-coder" EXE
#define LIBNMRTS	GENLIB DIR_SEP "libnmrts.a"
#define LINKNMRTS       "-lnmrts"
#define LIBPMRTS	GENLIB DIR_SEP "libpmrts.a"
#define LINKPMRTS       "-lpmrts"
#define LIBRTSLOG	GENLIB DIR_SEP "librtslog.a"
#define LINKGENRTS      "-lgenrts"
#define LIBGENRTS       GENLIB DIR_SEP "libgenrts.a"
#define LINKDEBUGRTS    "-ldebugrts -lpthread -lgtk -lgdk -lgthread"
#define LIBDEBUGRTS     GENLIB DIR_SEP "libdebugrts.a"
#define MACHDEP		GENINCLUDE DIR_SEP "machdep.h"

typedef struct
{
    char agfl_lexer[PATH_MAX];
    char agfl_coder[PATH_MAX];
    char include[PATH_MAX];
    char machdep[PATH_MAX];
    char lib[PATH_MAX];
    char librts[PATH_MAX];
    char librtslog[PATH_MAX];
    char libgenrts[PATH_MAX];
    char genas[PATH_MAX];
    char gencc[PATH_MAX];
} GenFiles;

void
check_file_installed(const char* path, const char* descr)
{
    if (!file_exists(path)) {
        output_error("installation problem: cannot find %s `%s'", descr, path);
        exit(1);
    }
}

void
check_installation(GenFiles* files)
{
    char* genbase;
    char* genas;
    char* gencc;

    genbase = getenv("GENBASE");
    if (genbase == NULL) {
        genbase = GENBASE;
    }

    snprintf(files->agfl_lexer,PATH_MAX, "%s%s", genbase, LEXGEN);
    snprintf(files->agfl_coder,PATH_MAX, "%s%s", genbase, AGFL);
    snprintf(files->machdep,   PATH_MAX, "%s%s", genbase, MACHDEP);
    snprintf(files->include,   PATH_MAX, "%s%s", genbase, GENINCLUDE);
    snprintf(files->lib,       PATH_MAX, "%s%s", genbase, GENLIB);
    snprintf(files->librts,    PATH_MAX, "%s%s", genbase, LIBNMRTS);
    snprintf(files->librtslog, PATH_MAX, "%s%s", genbase, LIBRTSLOG);
    snprintf(files->libgenrts, PATH_MAX, "%s%s", genbase, LIBGENRTS);

    check_file_installed(files->agfl_coder, "agfl compiler");
    check_file_installed(files->agfl_lexer, "agfl-lexer command");
    check_file_installed(files->machdep, "include file");
    check_file_installed(files->librts, "run-time library");

// Not yet build by automake/conf
//  check_file_installed(files->librtslog, "run-time library");

    genas = getenv("GENAS");
    if (genas == NULL) {
        genas = GENAS;
    }

    strcpy(files->genas, genas);
    /* check_file_installed(files->genas, "assembler command"); */

    gencc = getenv("GENCC");
    if (gencc == NULL) {
        gencc = GENCC;
    }

    strcpy(files->gencc, gencc);

    /* check_file_installed(files->gencc, "C compiler"); */
}

int
file_ext_exists(const char* base, const char* ext)
{
    char path[PATH_MAX];

    snprintf(path, PATH_MAX, "%s%s", base, ext);

    return file_exists(path);
}

int
have_lexicon(const char* base)
{
    return file_ext_exists(base, ".lif");
}

time_t
get_file_time(const char* path)
/*
// Return creation or update time of file with path,
// or 0 if file does not exist.
*/
{
    struct stat sbuf;

    if (stat(path, &sbuf) < 0) {
        return 0;
    } else {
        return sbuf.st_mtime;
    }
}

int
file_is_newer(const char* path1, const char* path2)
/*
// Check whether file with path1 is newer than file with path2.
// Return false if file with path1 does not exist.
*/
{
    time_t time1 = get_file_time(path1);
    time_t time2 = get_file_time(path2);

    return time1 > time2;
}

void
check_file_exists(const char* path)
{
    if (!file_exists(path)) {
        output_error("fatal: cannot locate input file `%s'", path);
        exit(1);
    }
}

void
delete_file(const char* path, int verbose)
{
    if (verbose >= 2) {
        output_log("delete file '%s'", path);
    }

    remove(path);
}

int
execute(const char* cmd, int verbose)
{
    int	error;

    if (verbose >= 2) {
        output_log(cmd);
    }
    error = system(cmd);

    return error;
}

void
make_lexicon(const char* base, const Options* opt, const GenFiles* files)
{
    char blf_file[PATH_MAX];
    char cmd[PATH_MAX];
    int error;

    snprintf(blf_file, PATH_MAX, "%s%s", base, ".blf");

    snprintf(cmd, PATH_MAX, "%s%s%s%s %s", files->agfl_lexer,
            opt->verbose ?     " -v" : "",
            opt->verbose > 1 ? " -v" : "",
            opt->force ?       " -f" : "",
            base);
    error = execute(cmd, opt->verbose);

    if (error) {
        delete_file(blf_file, opt->verbose);
        exit(1);
    }
}

void
make_parser(const char* base, const Options* opt, const GenFiles* files)
{
    char lex_file[PATH_MAX];
    char lif_file[PATH_MAX];
    char gra_file[PATH_MAX];
    char asm_file[PATH_MAX];
    char obj_file[PATH_MAX];
    char exe_file[PATH_MAX];
    char cmd[PATH_MAX];
    int error;
    int agfl_coder;
    int link;
    char* linklibrary;

    snprintf(lif_file, PATH_MAX, "%s%s", base, ".lif");
    snprintf(lex_file, PATH_MAX, "%s%s", base, ".lex");
    snprintf(gra_file, PATH_MAX, "%s%s", base, ".gra");
    snprintf(asm_file, PATH_MAX, "%s%s", base, ".S");
    snprintf(obj_file, PATH_MAX, "%s%s", base, ".o");
    snprintf(exe_file, PATH_MAX, "%s%s", base, OUT_EXE);

    check_file_exists(gra_file);
    agfl_coder = opt->force
            || !file_exists(exe_file)
            || file_is_newer(gra_file, exe_file)
            || file_is_newer(lex_file, exe_file)
            || file_is_newer(files->agfl_coder, exe_file);
    link = agfl_coder
           || file_is_newer(files->librts, exe_file)
           || file_is_newer(files->librtslog, exe_file)
           || file_is_newer(files->libgenrts, exe_file);

    if (link && !agfl_coder && !file_exists(obj_file)) {
        agfl_coder = 1;
    }

    if (agfl_coder) {
        snprintf(cmd, PATH_MAX,
		"%s \"%s\" \"-I%s\"%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
                files->agfl_coder, gra_file, files->include,
                opt->underscore ?          " -_" : "",
                opt->additional_analysis ? " -a" : "",
                opt->counters ?            " -c" : "",
                opt->debug ?               " -g" : "",
                opt->generative_grammar ?  " -G" : "",
                opt->write_log ?           " -l" : "",
                opt->module_names ?        " -m" : "",
                opt->profile ?             " -p" : "",
                opt->show_time ?           " -t" : "",
                opt->verbose ?             " -v" : "",
                opt->verbose > 1 ?         " -v" : "",
                opt->suppress_warnings ?   " -w" : "",
                opt->suppress_analysis ?   " -A" : "",
                opt->directors ?           " -D" : "",
                opt->left_factorize ?      " -F" : "",
                opt->positive_memo ?       " -P" : "",
                opt->negative_memo ?       " -N" : "",
                opt->trace ?               " -T" : "");

        error = execute(cmd, opt->verbose);

	if (!error) {
	    /* Assemble the file */

	    snprintf(cmd, PATH_MAX, "%s \"%s\" -o \"%s\"",
		    files->genas, asm_file, obj_file);
	    error = execute(cmd, opt->verbose);

	    if (!opt->keep_assembler_file) {
		delete_file(asm_file, opt->verbose);
	    }
	}

        if (error) {
            delete_file(obj_file, opt->verbose);
            exit(1);
        }
    }

    if (opt->counters) {
        assert(!"No counters support yet!");
        linklibrary = NULL;
    } else if (opt->generative_grammar) {
        linklibrary = LINKGENRTS;
    } else if (opt->positive_memo) {
        linklibrary = LINKPMRTS;
    } else if (opt->trace) {
        linklibrary = LINKDEBUGRTS;
    } else {
        linklibrary = LINKNMRTS;
    }
    if (link) {
	char rpath_opt[PATH_MAX];

#if defined(RPATH_OPT)
	/*
	 * configure sets for instance
	 * #define RPATH_OPT "-Wl,--rpath -Wl,%s"
	 */
	snprintf(rpath_opt, PATH_MAX, RPATH_OPT, files->lib);
#else
	rpath_opt[0] = '\0';
#endif
        check_file_exists(obj_file);
        snprintf(cmd, PATH_MAX,
		"%s \"%s\" -o \"%s\"%s%s \"-L%s\" %s %s%s -llexicon -labase %s",
                files->gencc, obj_file, exe_file,
                opt->debug ?   " -g" : "",
#ifdef DEBUG
		"",
#else
                opt->strip ?   " -s" : "",
#endif
                files->lib,
                rpath_opt,
                linklibrary,
                opt->electric_fence ? " -lefence" : "",
#if defined(HAVE_LIBREADLINE)
                "-lreadline"
#else
                ""
#endif
                );
        error = execute(cmd, opt->verbose);

        if (error) {
            output_error("fatal: link error, help!");
            exit(1);
        }
    } else {
        output_error("`%s' is up to date.", base);
    }
}

int
main(int argc, const char* argv[])
{
    GenFiles files;
    Options options;
    char base_name[PATH_MAX];

    init_options(&options);
    process_options(argc, argv, &options);
    check_installation(&files);

    get_base_name(base_name, options.grammar_file, ".gra");
    make_parser(base_name, &options, &files);

    /* agfl-lexer decides if it needs to make a lexicon..: */
    make_lexicon(base_name, &options, &files);

    return 0;
}

