/*
 * 
 * (c) Copyright 1989 OPEN SOFTWARE FOUNDATION, INC.
 * (c) Copyright 1989 HEWLETT-PACKARD COMPANY
 * (c) Copyright 1989 DIGITAL EQUIPMENT CORPORATION
 * To anyone who acknowledges that this file is provided "AS IS"
 * without any express or implied warranty:
 *                 permission to use, copy, modify, and distribute this
 * file for any purpose is hereby granted without fee, provided that
 * the above copyright notices and this notice appears in all source
 * code copies, and that none of the names of Open Software
 * Foundation, Inc., Hewlett-Packard Company, or Digital Equipment
 * Corporation be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior
 * permission.  Neither Open Software Foundation, Inc., Hewlett-
 * Packard Company, nor Digital Equipment Corporation makes any
 * representations about the suitability of this software for any
 * purpose.
 * 
 */
/*
**
**  NAME
**
**      ERRORS.C
**
**  FACILITY:
**
**      Interface Definition Language (IDL) Compiler
**
**  ABSTRACT:
**
**      IDL Error logging and reporting routines.
**
**  VERSION: DCE 1.0
**
*/


#include <nidl.h>
#include <errors.h>
#include <nametbl.h>
#include <frontend.h>
#include <message.h>
#include <nidlmsg.h>
#include <driver.h>

#define MAX_LINE_LEN    256
#define WHERE_TEXT_LEN   20
#define MAX_WARNINGS    5
#define MAX_ERROR_FILES 10


/*
 *  Error log record format.
 */
typedef struct error_log_rec_t
{
    STRTAB_str_t filename;      /* Error file name */
    int     lineno;            /* Source line number */
    long    msg_id;             /* Message identifer */
    char    *arg1, *arg2, *arg3, *arg4, *arg5;  /* Error Arguments */
    union                       /* Tree node or List node */
    {
        struct
        {                                   /* As tree node: */
            struct  error_log_rec_t *left;  /* Left pointer */
            struct  error_log_rec_t *right; /* Right pointer */
        } asBinTree;
        struct
        {                                   /* As list node: */
            struct  error_log_rec_t *next;  /* Ptr to next for this lineno */
        } asList;
    } links;
    struct  error_log_rec_t *first_this_line;   /* 1st addtl msg this line */
    struct  error_log_rec_t *last_this_line;    /* last addtl msg this line */
} error_log_rec_t;



/* Pointers to IDL parser or ACF parser global variables. */

FILE    **yyin_p;           /* Points to yyin or acf_yyin */
int     *yylineno_p;        /* Points to yylineno or acf_yylineno */
int     *yynerrs_p;         /* Points to yynerrs or acf_yynerrs */
char    *yytext_p;          /* Points to yytext or acf_yytext */

boolean ERR_no_warnings;    /* Global copy of -no_warn command line option */

static  int              warnings = 0;      /* Warning count */
static  STRTAB_str_t    error_files[MAX_ERROR_FILES]; /* List of all files with errors */
static  int     error_file_count = 0;       /* Number of files with errors */
static  int     last_error_line = 0;        /* Line of last error */
static  char    *current_file   = NULL;     /* Current source file name */
        error_log_rec_t  *errors = NULL;    /* Tree root for error nodes */
        int     error_count     = 0;        /* Error count */
        STRTAB_str_t    error_file_name_id; /* Id of current source file */

extern void sysdep_cleanup_temp (
    void
);

/*
 *  y y w h e r e
 *
 *  Function:   Returns current input position for yyparse.
 *
 *  Globals:    yy*_p
 *
 *  Notes:      This was adapted from the book
 *                  "Compiler Construction under Unix"
 *              by A. T. Schreiner & H. G. Friedman
 *
 *              Rico 16-Oct-89: Considerably changed original algorithm.
 */

void yywhere
(
    void
)

{
    boolean have_text = false;  /* True if have source text to output */
    int     text_len = 0;       /* Length of source text to output */
    int     lineno;             /* Source line number of relevant text */
    long    msg_id;             /* ID of message to output */
    char    *near_text;         /* Text of object near error */
    STRTAB_str_t string_id;     /* Entry in string table of near text */

    lineno = *yylineno_p;

    if (*yytext_p)
    {
        for (text_len = 0; text_len < WHERE_TEXT_LEN; ++text_len)
            if (!yytext_p[text_len] || yytext_p[text_len] == '\n')
                break;

        if (text_len > 0)
        {
            have_text = true;
            lineno = lineno - (*yytext_p == '\n' || ! *yytext_p);
        }
    }

    /*
     * If there is some text to show, put it in the string table
     */
    if (have_text)
    {
        string_id = STRTAB_add_string(yytext_p);
        STRTAB_str_to_string(string_id, &near_text);
    }

    if (have_text)
    {
        if (feof(*yyin_p))
            msg_id = NIDL_EOFNEAR;
        else
            msg_id = NIDL_SYNTAXNEAR;
    }
    else
    {
        if (feof(*yyin_p))
            msg_id = NIDL_EOF;
        else
            msg_id = NIDL_SYNTAXNEAR;
    }

    log_error(lineno, msg_id, text_len, near_text);
}

/*
 *  y y e r r o r
 *
 *  Function:   Called by yypaser when a parse error is encountered.
 *
 *  Inputs:     message -  error message to display
 *              token    - expected token
 *
 *  Globals:    yynerrs_p
 *
 *  Notes:      This was adapted from the book
 *                  "Compiler Construction under Unix"
 *              by A. T. Schreiner & H. G. Friedman
 */

void yyerror
(
    char *message
)

{
    static int list = 0;        /* Note: Currently always 0 */

    if (message || !list)
    {
        (*yynerrs_p)++;
        yywhere();

        /*
         * Only print the yacc version of errors if it isn't for a syntax error.
         */
        if (strcmp(message,"syntax error") != 0)
        {
            fputs(message, stderr);
            putc('\n', stderr);
            return;
        }

        return;
    }

    putc('\n', stderr);
    list = 0;
}

/*
 *  a l l o c _ l o g _ r e c
 *
 *  Function:   Allocates and initializes error log record.
 *
 *  Inputs:     lineno - source line number
 *              message - message text
 *
 *  Outputs:    Address of error log record.
 */

static error_log_rec_t *alloc_log_rec
(
    STRTAB_str_t filename,
    int  lineno,
    long msg_id,
    char *arg1,
    char *arg2,
    char *arg3,
    char *arg4,
    char *arg5
)

{
    error_log_rec_t *log_rec_p;
    int             i;


    log_rec_p = (error_log_rec_t *) MALLOC(sizeof(error_log_rec_t));

    log_rec_p->lineno  = lineno;
    log_rec_p->filename = filename;
    log_rec_p->msg_id = msg_id;
    log_rec_p->arg1 = arg1;
    log_rec_p->arg2 = arg2;
    log_rec_p->arg3 = arg3;
    log_rec_p->arg4 = arg4;
    log_rec_p->arg5 = arg5;

    log_rec_p->first_this_line = 0;
    log_rec_p->last_this_line  = 0;

    log_rec_p->links.asBinTree.left  = 0;
    log_rec_p->links.asBinTree.right = 0;

    log_rec_p->links.asList.next     = 0;

    /*
     * If this file is not listed in the error file list
     */
    for (i = 0; i < error_file_count; i++)
        if (error_files[i] == filename) break;

    /*
     * Add the file with errors to the error_file list
     */
    if (i == error_file_count)
        error_files[error_file_count++] = filename;

    /*
     * If we have too many files with errors, just ignore some
     */
    if (error_file_count >= MAX_ERROR_FILES) error_file_count--;

    return  log_rec_p;
}

/*
 *  f r e e _ l o g _ r e c
 *
 *  Function:   Frees an error log record and anything below it
 *
 *  Inputs:     record - error log record to free
 *  Outputs:    none
 */

static void free_log_rec
(
    error_log_rec_t *log_rec_p
)

{
    if (log_rec_p->links.asBinTree.left  != NULL)
    {
        free_log_rec(log_rec_p->links.asBinTree.left);
        log_rec_p->links.asBinTree.left = NULL;
    }

    if (log_rec_p->links.asBinTree.right  != NULL)
    {
        free_log_rec(log_rec_p->links.asBinTree.right);
        log_rec_p->links.asBinTree.right = NULL;
    }


    /*
     * Free all errors logged for this line number
     */
    while (log_rec_p->first_this_line != NULL)
    {
        error_log_rec_t *ep;
        ep = log_rec_p->first_this_line;
        log_rec_p->first_this_line = log_rec_p->links.asList.next;
        FREE(ep);
    }

    FREE(log_rec_p);
}

/*
 *  q u e u e _ e r r o r
 *
 *  Function:   Adds an error message to the end of a list of
 *              errors queued for a particular line.
 *
 *  Inputs:     log_rec_p - a pointer to the header error log rec.
 *              message   - the error message.
 */

static void queue_error
(
    error_log_rec_t *log_rec_p,
    STRTAB_str_t filename,
    long msg_id,
    char *arg1,
    char *arg2,
    char *arg3,
    char *arg4,
    char *arg5
)

{
    error_log_rec_t *new_log_rec_p;

    new_log_rec_p = alloc_log_rec(filename, log_rec_p->lineno, msg_id, arg1, arg2, arg3, arg4, arg5);

    if (log_rec_p->first_this_line == NULL)
    {
        log_rec_p->first_this_line = (struct error_log_rec_t *) new_log_rec_p;
        log_rec_p->last_this_line  = (struct error_log_rec_t *) new_log_rec_p;
        return;
    }

    log_rec_p->last_this_line->links.asList.next =
        (struct error_log_rec_t *) new_log_rec_p;
    log_rec_p->last_this_line =
        (struct error_log_rec_t *) new_log_rec_p;
}

/*
 *  a d d _ e r r o r _ l o g _ r e c
 *
 *  Function:   Adds an error log to the sorted binary tree of
 *              error messages.
 *
 *  Inputs:     log_rec_p - pointer to current root of tree.
 *              lineno    - line number on which error occurred.
 *              message   - the error message.
 */

static void add_error_log_rec
(
    error_log_rec_t *log_rec_p,
    STRTAB_str_t filename,
    int lineno,
    long msg_id,
    char *arg1,
    char *arg2,
    char *arg3,
    char *arg4,
    char *arg5
)

{
    if (log_rec_p->lineno < lineno)
    {
        if (log_rec_p->links.asBinTree.right != NULL)
            add_error_log_rec(log_rec_p->links.asBinTree.right, filename, lineno,
                    msg_id, arg1, arg2, arg3, arg4, arg5);
        else
            log_rec_p->links.asBinTree.right = alloc_log_rec(filename, lineno,
                    msg_id, arg1, arg2, arg3, arg4, arg5);

        return;
    }

    if (log_rec_p->lineno > lineno)
    {
        if (log_rec_p->links.asBinTree.left != NULL)
            add_error_log_rec(log_rec_p->links.asBinTree.left, filename, lineno,
                    msg_id, arg1, arg2, arg3, arg4, arg5);

        else
            log_rec_p->links.asBinTree.left = alloc_log_rec(filename, lineno,
                    msg_id, arg1, arg2, arg3, arg4, arg5);
        return;
    }

    if (log_rec_p->lineno == lineno)

queue_error(log_rec_p, filename, msg_id, arg1, arg2, arg3, arg4, arg5);
}

/*
 *  l o g _ s o u r c e _ e r r o r
 *
 *  Function:   Accumulates an error message for later printout.
 *              All accumulated errors are printed by print_errors.
 *              Errors are kept sorted by line number and source
 *              file name.
 *
 *  Inputs:
 *              filename - STRTAB_str_t of full source file name
 *              lineno  - the line number of the error.
 *              msg_id - the error message ID.
 *              [arg1,...,arg5] - 0 to 5 arguments for error message formatting
 *
 *  Outputs:    An error log record is inserted in the error tree.
 *
 */

void log_source_error
(
    STRTAB_str_t filename,
    int lineno,
    long msg_id,
    char *arg1,
    char *arg2,
    char *arg3,
    char *arg4,
    char *arg5
)

{
    ++error_count;

    if (errors == NULL)
        errors = alloc_log_rec(filename, lineno, msg_id, (char *)arg1, (char *)arg2, (char *)arg3, (char *)arg4, (char *)arg5);
    else
        add_error_log_rec(errors, filename, lineno, msg_id, (char *)arg1, (char *)arg2, (char *)arg3, (char *)arg4, (char *)arg5);
}

/*
 *  l o g _ s o u r c e _ w a r n i n g
 *
 *  Function:   Accumulates a warning message for later printout.
 *              All accumulated errors are printed by print_errors.
 *              Errors are kept sorted by line number and source
 *              file name.
 *
 *  Inputs:
 *              filename - STRTAB_str_t of full source file name
 *              lineno  - the line number of the error.
 *              msg_id - the error message ID.
 *              [arg1,...,arg5] - 0 to 5 arguments for error message formatting
 *
 *  Outputs:    An error log record is inserted in the error tree.
 *
 */

void log_source_warning
(
    STRTAB_str_t filename,
    int lineno,
    long msg_id,
    char *arg1,
    char *arg2,
    char *arg3,
    char *arg4,
    char *arg5
)

{
    /* Return if warnings are suppressed. */
    if (ERR_no_warnings)
        return;

    ++warnings;

    if (errors == NULL)
        errors = alloc_log_rec(filename, lineno, msg_id, (char *)arg1, (char *)arg2, (char *)arg3, (char *)arg4, (char *)arg5);
    else
        add_error_log_rec(errors, filename, lineno, msg_id, (char *)arg1, (char *)arg2, (char *)arg3, (char *)arg4, (char *)arg5);
}

/*
 *  l o g _ e r r o r
 *
 *  Function:   Accumulates an error message for later printout.
 *              All accumulated errors are printed by print_errors.
 *              Errors are kept sorted by line number.  The error
 *              is logged on the file for which set_name_for_errors
 *              was most recently called.
 *
 *  Inputs:     lineno  - the line number of the error.
 *              msg_id - the error message ID.
 *              [arg1,...,arg5] - 0 to 5 arguments for error message formatting
 *
 *  Outputs:    An error log record is inserted in the error tree.
 *
 */

void log_error
(
    int lineno,
    long msg_id,
    char *arg1,
    char *arg2,
    char *arg3,
    char *arg4,
    char *arg5
)

{
    log_source_error (error_file_name_id, lineno, msg_id, arg1, arg2, arg3, arg4, arg5);
}

/*
 *  l o g _ w a r n i n g
 *
 *  Function:   Accumulates a warning message for later printout.
 *              All accumulated errors are printed by print_errors.
 *              Errors are kept sorted by line number.  The warning
 *              is logged on the file for which set_name_for_errors
 *              was most recently called.
 *
 *  Inputs:     lineno  - the line number of the warning.
 *              msg_id - the error message ID.
 *              [arg1,...,arg5] - 0 to 5 arguments for error message formatting
 *
 *  Outputs:    An error log record is inserted in the error tree.
 *
 */

void log_warning
(
    int lineno,
    long msg_id,
    char *arg1,
    char *arg2,
    char *arg3,
    char *arg4,
    char *arg5
)

{
    log_source_warning (error_file_name_id, lineno, msg_id, arg1, arg2, arg3, arg4, arg5);
}


/*
 *  s e e k _ f o r _ l i n e
 *
 *  Function:   Reads the line specified by lineno.
 *
 *  Inputs:     source - the file descriptor for the source file.
 *              lineno - the number of the line in error.
 *
 *  Outputs:    source_line - the source line is returned through here
 *
 *  Globals:    last_error_line
 */

void seek_for_line
(
    FILE *source_file,
    int lineno,
    char *source_line
)

{
    int lines_to_skip;
    int i;

    /*
     * If the FILE is NULL then can't get the source
     */
    if (source_file == NULL)
    {
        source_line[0] = 0;
        return;
    }

    lines_to_skip = lineno - last_error_line;

    for (i=0; i<lines_to_skip; i++)
        (void) fgets(source_line, MAX_LINE_LEN, source_file);

    /* Strip off newline. */
    i = strlen(source_line) - 1;
    if (source_line[i] == '\n')
        source_line[i] = '\0';

    last_error_line = lineno;
}

/*
 *  p r i n t _ e r r o r s _ f o r _ l i n e
 *
 *  Function:   Prints out a source line and accumulated errors
 *              for that line.
 *
 *  Inputs:     fd - file descriptor for source file
 *              log_rec_ptr - a pointer to the header log rec for the line
 *              source - source file name
 */

void print_errors_for_line
(
    FILE *fd,
    char *source,
    STRTAB_str_t    source_id,
    error_log_rec_t *log_rec_ptr
)

{
    char            source_line[MAX_LINE_LEN];
    boolean         source_printed = false;
    error_log_rec_t *erp;


    /* Print the error only if it in this file */
    if (source_id == log_rec_ptr->filename)
    {
        source_printed = true;
        seek_for_line(fd, log_rec_ptr->lineno, source_line);
        message_print(
            NIDL_FILESOURCE, source, log_rec_ptr->lineno, source_line
        );
        message_print(
            log_rec_ptr->msg_id, log_rec_ptr->arg1, log_rec_ptr->arg2,
            log_rec_ptr->arg3, log_rec_ptr->arg4, log_rec_ptr->arg5
        );
    }


    for (erp = log_rec_ptr->first_this_line; erp; erp=erp->links.asList.next)
    {
        /* Print the error only if it in this file */
        if (source_id == erp->filename)
        {
            /* If we haven't output the source line text yet, the do so */
            if (!source_printed)
            {
                source_printed = true;
                seek_for_line(fd, log_rec_ptr->lineno, source_line);
                message_print(
                    NIDL_FILESOURCE, source, log_rec_ptr->lineno, source_line
                );
            }

            /* Now print out the actual error message */
            message_print(
                erp->msg_id, erp->arg1, erp->arg2, erp->arg3, erp->arg4, erp->arg5
            );
        }
    }
}

/*
 *  p r i n t _ e r r o r _ m e s s a g e s
 *
 *  Function:   Recursively prints all accumulated error messages.
 *
 *  Inputs:     fd          - file descriptor for source file
 *              source      - name of source file
 *              log_rec_ptr - root of error log tree
 */

void print_error_messages
(
    FILE *fd,
    char *source,
    STRTAB_str_t    source_id,
    error_log_rec_t *log_rec_ptr
)

{
    if (log_rec_ptr->links.asBinTree.left != NULL)
        print_error_messages(fd, source, source_id, log_rec_ptr->links.asBinTree.left);

    print_errors_for_line(fd, source, source_id, log_rec_ptr);

    if (log_rec_ptr->links.asBinTree.right != NULL)
        print_error_messages(fd, source, source_id, log_rec_ptr->links.asBinTree.right);
}

/*
 *  p r i n t _ e r r o r s
 *
 *  Function:   Prints all accumulated error messages.
 *
 *  Inputs:     source - String table ID of source file name
 *
 *  Functional value:   true - if any errors were printed
 *                      false otherwise
 *
 */

boolean print_errors
(
    void
)

{
    FILE    *fd;
    char    *fn;
    int     i;
    STRTAB_str_t   source_id;
    error_log_rec_t *error_root;

    /*
     * If there are no errors, just return
     */
     if (errors == NULL) return 0;


    /*
     * Copy the root of the error tree and null it out so that
     * it looks like all the errors have been printed if we get
     * an error while printing them out.
     */
     error_root = errors;
     errors = NULL;

    /*
     * Loop through all files with errors
     */
    for (i = 0; i < error_file_count; i++)
    {
        source_id = error_files[i];
        STRTAB_str_to_string(source_id, &fn);
        fd = fopen(fn, "r");
        print_error_messages(fd, fn, source_id, error_root);
        last_error_line = 0;
    }

    /*
     * Free the tree of errors,
     */
    free_log_rec(error_root);
    error_file_count = 0;


    /*
     * Return true if we found any errors.
     */
    return (error_file_count != 0);
}

/*
 *  e r r o r
 *
 *  Function:   Prints the specifed error message and terminates the program
 *
 *  Inputs:     msg_id - the error message ID.
 *              [arg1,...,arg5] - 0 to 5 arguments for error message formatting
 *
 *  Notes:      This call terminates the calling program with a failure status
 *
 */

void error
(
    long msg_id,
    char *arg1,
    char *arg2,
    char *arg3,
    char *arg4,
    char *arg5
)

{
    if (current_file)
        message_print(NIDL_LINEFILE, current_file, *yylineno_p);
    message_print(msg_id, arg1, arg2, arg3, arg4, arg5);

#ifndef HASPOPEN
    sysdep_cleanup_temp();
#endif

    nidl_terminate();
}

/*
 *  e r r o r _ l i s t
 *
 *  Function:   Prints the specifed error message(s) and terminates the program
 *
 *  Inputs:     vecsize - number of messages
 *              errvec - pointer to one or more message info elements
 *              exitflag - TRUE => exit program
 *
 *  Notes:      This call terminates the calling program with a failure status
 *
 */

void error_list
(
    int vecsize,
    idl_error_list_p errvec,
    boolean exitflag
)

{
    int i;

    if (current_file)
        message_print(NIDL_LINEFILE, current_file, *yylineno_p);

    for (i = 0; i < vecsize; i++)
        message_print(errvec[i].msg_id, errvec[i].arg1, errvec[i].arg2,
                      errvec[i].arg3, errvec[i].arg4, errvec[i].arg5);

    if (!exitflag) return;

#ifndef HASPOPEN
    sysdep_cleanup_temp();
#endif

    nidl_terminate();
}

/*
 *  w a r n i n g
 *
 *  Function:   Prints the specifed error message. Terminates if the
 *              error count excees the threshold.
 *
 *  Inputs:     msg_id - the error message ID.
 *              [arg1,...,arg5] - 0 to 5 arguments for error message formatting
 *
 *  Globals:    yylineno_p
 *
 *  Notes:      This call terminates the calling program with a status of -2.
 *
 */

void warning
(
    long msg_id,
    char *arg1,
    char *arg2,
    char *arg3,
    char *arg4,
    char *arg5
)

{
    /* Return if warnings are suppressed. */
    if (ERR_no_warnings)
        return;

    if (current_file)
        message_print(NIDL_LINEFILE, current_file, *yylineno_p);
    message_print(msg_id, arg1, arg2, arg3, arg4, arg5);

    if (++warnings > MAX_WARNINGS)
    {
        message_print(NIDL_MAXWARN, MAX_WARNINGS);
        nidl_terminate();
    }
}

/*
 *  s e t _ n a m e _ f o r _ e r r o r s
 *
 *  Function:   Records the name of the file being processed.  This name
 *              will be prepended onto error messages.
 *
 *  Inputs:     name - a pointer to the file name.
 *
 */

void set_name_for_errors
(
    char *filename
)

{
    if (filename != NULL)
    {
        error_file_name_id = STRTAB_add_string(filename);
        STRTAB_str_to_string(error_file_name_id,&current_file);
    }
    else current_file = NULL;
}


/*
 *  i n q _ n a m e _ f o r _ e r r o r s
 *
 *  Function:   Returns the name of the file being processed.
 *
 *  Outputs:   name - the file name is copied through this.
 *
 */

void inq_name_for_errors
(
    char *name
)

{
    if (current_file)
        strcpy(name, current_file);
    else
        *name = '\0';
}
