/*
 * Copyright (c) 1980 Regents of the University of California. All rights
 * reserved.  The Berkeley software License Agreement specifies the terms and
 * conditions for redistribution. 
 */

#ifndef lint
char            copyright[] =
"@(#) Copyright (c) 1980 Regents of the University of California.\n\
 All rights reserved.\n";
#endif not lint

#ifndef lint
static char     sccsid[] = "@(#)indent.c    5.4 (Berkeley) 9/10/85";
#endif not lint

/*-
  
        Copyright (C) 1976
                by the
        Board of Trustees
                of the
        University of Illinois
  
        All rights reserved
  
  
NAME:
indent main program
  
FUNCTION:
This is the main program of the indent program.  Indent will take a C
program source and reformat it into a semi-reasonable form.
  
ALGORITHM:
The routine lexi scans tokens and passes them back one at a time to the
main routine.  The subroutine parse takes care of much of the work of
figuring indentation level.  
  
1) Call lexi
2) Enter a monster switch statement on the code returned by lexi.  If 
the indentation level for the line yet to be printed should be 
changed, set the variable ps.ind_level.  If the indentation level for
the following line should be changed, set the variable ps.i_l_follow.
  
*/

#include "indent_g.h";
#include "indent_c.h";

char           *in_name = "Standard Input";     /* will always point to name
                                                 * of input file */
char           *out_name = "Standard Output";   /* will always point to name
                                                 * of output file */
char            bakfile[32] = "";

int             dec_ind;/* current indentation for declarations */
int             di_stack[20];   /* a stack of structure indentation
                                 * levels */
int             flushed_nl;     /* used when buffering up comments to
                                 * remember that a newline was passed
                                 * over */
int             force_nl;       /* when true, code must be broken */
int             hd_type;/* used to store type of stmt for if (...),
                         * for (...), etc */
int             scase;  /* set to true when we see a case, so we will
                         * know what to do with the following colon */
int             sp_sw;  /* when true, we are in the expressin of
                         * if(...), while(...), etc. */
int             squest; /* when this is positive, we have seen a ?
                         * without the matching : in a <c>?<s>:<s>
                         * construct */
int             type_code;      /* the type of token, returned by
                                 * lexi */

int             last_else = 0;  /* true iff last keyword was an else */

int             is_procname;


main(argc, argv)
        int             argc;
        char          **argv;
{
        register char  *t_ptr;  /* used for copying tokens */
        register int i;

        sp_sw = force_nl = false;
        dec_ind = 0;
        squest = 0;
        di_stack[ps.dec_nest = 0] = 0;
        scase = ps.pcase = false;


        /*-----------------------------------------------*\
        |                 INITIALIZATION                  |
        \*-----------------------------------------------*/

        ps.p_stack[0] = stmt;   /* this is the parser's stack */
        ps.last_nl = true;      /* this is true if the last thing scanned was
                                 * a newline */
        ps.last_token = semicolon;
        combuf[0] = codebuf[0] = labbuf[0] = ' ';       /* set up code, label,
                                                         * and comment buffers */
        combuf[1] = codebuf[1] = labbuf[1] = '\0';
        s_lab = e_lab = labbuf + 1;
        s_code = e_code = codebuf + 1;
        s_com = e_com = combuf + 1;

        buf_ptr = buf_end = in_buffer;
        line_no = 1;
        had_eof = ps.in_decl = ps.decl_on_line = break_comma = false;
        ps.in_or_st = false;
        ps.bl_line = true;
        ps.want_blank = ps.in_stmt = ps.ind_stmt = false;


        sc_end = 0;
        bp_save = 0;
        be_save = 0;

        output = 0;


        /*--------------------------------------------------*\
        |   COMMAND LINE SCAN
        \*--------------------------------------------------*/

        set_defaults();

        /*
         * Unfortunately, we must look for -npro here because the profiles
         * are read before the command line arguments. 
         */
        for (i = 1; i < argc; ++i)
                if (strcmp(argv[i], "-npro") == 0)
                        break;
        if (i >= argc)
                set_profile();

        input = 0;              /* cancel -st if it was in the profiles, */
        output = 0;             /* as it doesn't make any sense there. */

        for (i = 1; i < argc; ++i) {

                /*
                 * look thru args (if any) for changes to defaults 
                 */

                if (argv[i][0] != '-') {        /* no flag on parameter */
                        if (input == 0) {       /* we must have the input
                                                 * file */
                                in_name = argv[i];      /* remember name of
                                                         * input file */
                                input = fopen(in_name, "r");
                                if (input == 0) {       /* check for open error */
                                        fprintf(stderr, "indent: can't open %s\n", argv[i]);
                                        exit(1);
                                }
                                continue;
                        } else if (output == 0) {       /* we have the output
                                                         * file */
                                out_name = argv[i];     /* remember name of
                                                         * output file */
                                if (strcmp(in_name, out_name) == 0) {   /* attempt to overwrite
                                                                         * the file */
                                        fprintf(stderr, "indent: input and output files must be different\n");
                                        exit(1);
                                }
                                output = fopen(out_name, "w");
                                if (output == 0) {      /* check for create
                                                         * error */
                                        fprintf(stderr, "indent: can't create %s\n", argv[i]);
                                        exit(1);
                                }
                                continue;
                        }
                        fprintf(stderr, "indent: unknown parameter: %s\n", argv[i]);
                        exit(1);
                } else
                        set_option(argv[i]);

        }                       /* end of for */
        if (input == 0) {
                printf("Usage: indent file [ outfile ] [ options ]\n");
                exit(1);
        }
        if (output == 0)
                if (troff)
                        output = stdout;
                else {
                        out_name = in_name;
                        bakcopy();
                }

        /*
         * Adjust parameters that are out of range, or set defaults if no
         * values were specified. 
         */
        if (ps.com_ind <= 1)
                ps.com_ind = 2; /* dont put normal comments before column 2 */
        if (block_comment_max_col <= 0)
                block_comment_max_col = max_col;
        if (ps.decl_com_ind <= 0)       /* if not specified by user, set this */
                ps.decl_com_ind = ps.ljust_decl ? ps.com_ind - 8 : ps.com_ind;
        if (ps.decl_com_ind <= 1)
                ps.decl_com_ind = 2;
        if (continuation_indent == 0)
                continuation_indent = ps.ind_size;
        fill_buffer();          /* get first batch of stuff into input buffer */

        parse(semicolon);

        dosomething();

        if (troff) { /* this isn't going to work, but since there isn't
                        much call for troff on the PC, I don't care.  I'll
                        leave it here though, for your sake. */
                register char  *p = in_name, *beg = in_name;

                while (*p)
                        if (*p++ == '/')
                                beg = p;
                fprintf(output, ".Fn \"%s\"\n", beg);
        }


        /*
         * START OF MAIN LOOP 
         */

        for (;;) {              /* this is the main loop.  it will go until
                                 * we reach eof */

                type_code = lexi();     /* lexi reads one token.  The actual
                                         * characters read are stored in
                                         * "token". lexi returns a code
                                         * indicating the type of token */
                is_procname = ps.procname[0];

                /*
                 * The following code moves everything following an if (),
                 * while (), else, etc. up to the start of the following stmt
                 * to a buffer.  This allows proper handling of both kinds of
                 * brace placement. 
                 */

                flushed_nl = false;
                while (ps.search_brace) {       /* if we scanned an if(),
                                                 * while(), etc., we might
                                                 * need to copy stuff into a
                                                 * buffer we must loop,
                                                 * copying stuff into
                                                 * save_com, until we find
                                                 * the start of the stmt
                                                 * which follows the if, or
                                                 * whatever */
                        switch (type_code) {
                        case newline:
                                ++line_no;
                                flushed_nl = true;
                        case form_feed:
                                break;  /* form feeds and newlines found here
                                         * will be ignored */

                        case lbrace:    /* this is a brace that starts the
                                         * compound stmt */
                                if (sc_end == 0) {      /* ignore buffering if a
                                                         * comment wasn't stored
                                                         * up */
                                        ps.search_brace = false;
                                        goto check_type;
                                }
                                if (btype_2) {
                                        save_com[0] = '{';      /* we either want to put
                                                                 * the brace right after
                                                                 * the if */
                                        goto sw_buffer; /* go to common code to
                                                         * get out of this loop */
                                }
                        case comment:   /* we have a comment, so we must copy
                                         * it into the buffer */
                                if (!flushed_nl) {
                                        if (sc_end == 0) {      /* if this is the first
                                                                 * comment, we must set
                                                                 * up the buffer */
                                                save_com[0] = save_com[1] = ' ';
                                                sc_end = &(save_com[2]);
                                        } else {
                                                *sc_end++ = '\n';       /* add newline between
                                                                         * comments */
                                                *sc_end++ = ' ';
                                                --line_no;
                                        }
                                        *sc_end++ = '/';        /* copy in start of
                                                                 * comment */
                                        *sc_end++ = '*';

                                        for (;;) {      /* loop until we get to
                                                         * the end of the
                                                         * comment */
                                                *sc_end = *buf_ptr++;
                                                if (buf_ptr >= buf_end)
                                                        fill_buffer();

                                                if (*sc_end++ == '*' && *buf_ptr == '/')
                                                        break;  /* we are at end of
                                                                 * comment */

                                                if (sc_end >= &(save_com[sc_size])) { /* check for temp buffer
                                                                               * overflow */
                                                        diag(1, "Internal buff overflow - Move too large comment after if, while.");
                                                        fflush(output);
                                                        exit(1);
                                                }
                                        }
                                        *sc_end++ = '/';        /* add ending slash */
                                        if (++buf_ptr >= buf_end)       /* get past / in buffer */
                                                fill_buffer();
                                        break;
                                }
                        default:        /* it is the start of a normal
                                         * statment */
                                if (flushed_nl) /* if we flushed a newline,
                                                 * make sure it is put back */
                                        force_nl = true;
                                if (type_code == sp_paren && *token == 'i'
                                    && last_else && ps.else_if
                                  || type_code == sp_nparen && *token == 'e'
                                    && e_code != s_code && e_code[-1] == '}')
                                        force_nl = false;

                                if (sc_end == 0) {      /* ignore buffering if
                                                         * comment wasnt saved
                                                         * up */
                                        ps.search_brace = false;
                                        goto check_type;
                                }
                                if (force_nl) { /* if we should insert a nl
                                                 * here, put it into the
                                                 * buffer */
                                        force_nl = false;
                                        --line_no;      /* this will be
                                                         * re-increased when the
                                                         * nl is read from the
                                                         * buffer */
                                        *sc_end++ = '\n';
                                        *sc_end++ = ' ';
                                        if (verbose && !flushed_nl)     /* print error msg if
                                                                         * the line was not
                                                                         * already broken */
                                                diag(0, "Line broken");
                                        flushed_nl = false;
                                }
                                for (t_ptr = token; *t_ptr; ++t_ptr)
                                        *sc_end++ = *t_ptr;     /* copy token into temp
                                                                 * buffer */

                sw_buffer:
                                ps.search_brace = false;        /* stop looking for
                                                                 * start of stmt */
                                bp_save = buf_ptr;      /* save current input
                                                         * buffer */
                                be_save = buf_end;
                                buf_ptr = save_com;     /* fix so that
                                                         * subsequent calls to
                                                         * lexi will take tokens
                                                         * out of save_com */
                                *sc_end++ = ' ';        /* add trailing blank,
                                                         * just in case */
                                buf_end = sc_end;
                                sc_end = 0;
                                break;
                        }       /* end of switch */
                        if (type_code != 0)     /* we must make this check,
                                                 * just in case there was an
                                                 * unexpected EOF */
                                type_code = lexi();     /* read another token */
                        is_procname = ps.procname[0];
                }               /* end of while (ps.search_brace) */
                last_else = 0;
check_type:
                if (type_code == 0) {   /* we got eof */
                        if (s_lab != e_lab || s_code != e_code
                            || s_com != e_com)  /* must dump end of line */
                                dump_line();
                        if (ps.tos > 1) /* check for balanced braces */
                                diag(1, "Stuff missing from end of file.");

                        if (verbose) {
                                printf("There were %d output lines and %d comments\n",
                                       ps.out_lines, ps.out_coms);
                                printf("(Lines with comments)/(Lines with code): %6.3f\n",
                                       (1.0 * ps.com_lines) / code_lines);
                        }
                        fflush(output);
                        exit(ps.tos <= 1);
                }
                if (
                    (type_code != comment) &&
                    (type_code != newline) &&
                    (type_code != preesc) &&
                    (type_code != form_feed)) {
                        if (
                            force_nl
                            &&
                            (type_code != semicolon) &&
                            (
                             type_code != lbrace
                             ||
                             !btype_2
                             )) {       /* we should force a broken line here */
                                if (verbose && !flushed_nl)
                                        diag(0, "Line broken");
                                flushed_nl = false;
                                dump_line();
                                ps.want_blank = false;  /* dont insert blank at
                                                         * line start */
                                force_nl = false;
                        }
                        ps.in_stmt = true;      /* turn on flag which causes
                                                 * an extra level of
                                                 * indentation. this is
                                                 * turned off by a ; or '}' */
                        if (s_com != e_com) {   /* the turkey has embedded a
                                                 * comment in a line. fix it */
                                *e_code++ = ' ';
                                for (t_ptr = s_com; *t_ptr; ++t_ptr)
                                        *e_code++ = *t_ptr;
                                *e_code++ = ' ';
                                *e_code = '\0'; /* null terminate code sect */
                                ps.want_blank = false;
                                e_com = s_com;
                        }
                } else if (type_code != comment)        /* preserve force_nl
                                                         * thru a comment */
                        force_nl = false;

                /*
                 * cancel forced newline after newline, form feed, etc 
                 */

                switch_on (type_code);

                *e_code = '\0'; /* make sure code section is null terminated */
                if (type_code != comment && type_code != newline && type_code != preesc)
                        ps.last_token = type_code;
        }                       /* end of main for(;;) loop */
}


dosomething()
{
        register char  *p = buf_ptr;
        register        col = 1;

        for (;;) {
                if (*p == ' ')
                        col++;
                else if (*p == '\t')
                        col = ((col - 1) & ~7) + 9;
                else
                        break;
                p++;
        }
        if (col > ps.ind_size)
                ps.ind_level = ps.i_l_follow = col / ps.ind_size;
}

/*
 * copy input file to backup file.  If in_name is /blah/blah/blah/file.foo, 
 * then backup file will be "file.BAK".  Then make the backup file the input
 * and original input file the output. 
 */
bakcopy()
{
        int             n;
        FILE           *bakchn;
        char extsave[16];        /* MS DOS extension */
        register char  *p;
        char           *r;
        char           *strchr();


        while ((p = strchr(in_name, '\\')) != NULL) {
                /* copy from `\' to end */
                p++;
                in_name = p;
        }
        p = in_name;

        /* wack off stuff after `.' */
        if ((r = strchr(p, '.')) != NULL) {
                strcpy (extsave, (r + 1));
                *r = NULL;      /* terminate string there */
        }
        
        sprintf(bakfile, "%s.BAK", p);

        /* restore original in_name */
        sprintf(in_name,"%s.%s", in_name, extsave);

        /* copy in_name to backup file */
        if ((bakchn = fopen(bakfile, "w")) == NULL) {
                fprintf(stderr, "indent: can't create backup file \"%s\"\n", bakfile);
                exit(1);
        }

        /* copy input file to backup file */
        while ((n = getc(input)) != EOF)
                putc(n, bakchn);

        if ((n = fclose(bakchn)) == EOF) {
                fprintf(stderr, "indent: error closeing backup file\n");
                exit(1);
        }
        if ((n = fclose(input)) == EOF) {
                fprintf(stderr, "indent: error closeing input file\n");
                exit(1);
        }
        /* re-open backup file as the input file */
        if ((input = fopen(bakfile, "r")) == NULL) {
                fprintf(stderr, "indent: can't re-open backup file\n");
                exit(1);
        }
        /* now the original input file will be the output */
        output = fopen(in_name, "w");
        if (output == NULL) {
                fprintf(stderr, "indent: can't create %s\n", in_name);
                unlink(bakfile);
                exit(1);
        }
}

