/*      $NetBSD: parse.c,v 1.85 2025/01/07 03:55:00 rillig Exp $        */

/*-
* SPDX-License-Identifier: BSD-4-Clause
*
* Copyright (c) 1985 Sun Microsystems, Inc.
* Copyright (c) 1980, 1993
*      The Regents of the University of California.  All rights reserved.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
*    must display the following acknowledgement:
*      This product includes software developed by the University of
*      California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
*    may be used to endorse or promote products derived from this software
*    without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/

#include <sys/cdefs.h>
__RCSID("$NetBSD: parse.c,v 1.85 2025/01/07 03:55:00 rillig Exp $");

#include <stdlib.h>

#include "indent.h"

/* Replace the top 2 symbols with the given symbol. */
static void
ps_psyms_replace2(parser_symbol psym)
{
       ps.psyms.len--;
       ps.psyms.sym[ps.psyms.len - 1] = psym;
       ps.ind_level_follow = ps.psyms.ind_level[ps.psyms.len - 1];
}

static int
left_justify_decl_level(void)
{
       int level = 0;
       for (size_t i = ps.psyms.len - 2; i > 0; i--)
               if (ps.psyms.sym[i] == psym_decl)
                       level++;
       return level;
}

void
ps_psyms_push(parser_symbol psym, int ind_level)
{
       if (ps.psyms.len == ps.psyms.cap) {
               ps.psyms.cap += 16;
               ps.psyms.sym = nonnull(realloc(ps.psyms.sym,
                       sizeof(ps.psyms.sym[0]) * ps.psyms.cap));
               ps.psyms.ind_level = nonnull(realloc(ps.psyms.ind_level,
                       sizeof(ps.psyms.ind_level[0]) * ps.psyms.cap));
       }
       ps.psyms.len++;
       ps.psyms.sym[ps.psyms.len - 1] = psym;
       ps.psyms.ind_level[ps.psyms.len - 1] = ind_level;
}

/*
* Repeatedly try to reduce the top two symbols on the parse stack to a single
* symbol, until no more reductions are possible.
*/
static void
psyms_reduce(void)
{
again:
       if (ps.psyms.len >= 2 && ps.psyms.sym[ps.psyms.len - 1] == psym_stmt) {
               switch (ps.psyms.sym[ps.psyms.len - 2]) {
               case psym_decl:
               case psym_stmt:
               case psym_for_exprs:
               case psym_if_expr_stmt_else:
               case psym_switch_expr:
               case psym_while_expr:
                       ps_psyms_replace2(psym_stmt);
                       goto again;
               case psym_if_expr:
                       ps_psyms_replace2(psym_if_expr_stmt);
                       goto again;
               case psym_do:
                       ps_psyms_replace2(psym_do_stmt);
                       goto again;
               default:
                       return;
               }
       }
       if (ps.psyms.sym[ps.psyms.len - 1] == psym_while_expr &&
           ps.psyms.sym[ps.psyms.len - 2] == psym_do_stmt) {
               ps.psyms.len -= 2;
               goto again;
       }
}

static bool
is_lbrace(parser_symbol psym)
{
       return psym == psym_lbrace_block
           || psym == psym_lbrace_struct
           || psym == psym_lbrace_union
           || psym == psym_lbrace_enum;
}

/*
* Shift the token onto the parser stack, then try to reduce it by combining it with
* previous tokens.
*/
void
parse(parser_symbol psym)
{
       debug_blank_line();
       debug_println("parse: %s", psym_name[psym]);

       if (psym != psym_else) {
               while (ps.psyms.sym[ps.psyms.len - 1] == psym_if_expr_stmt) {
                       ps.psyms.sym[ps.psyms.len - 1] = psym_stmt;
                       psyms_reduce();
                       ps.ind_level = ps.ind_level_follow
                           = ps.psyms.ind_level[ps.psyms.len - 1];
               }
       }

       switch (psym) {

       case psym_lbrace_block:
       case psym_lbrace_struct:
       case psym_lbrace_union:
       case psym_lbrace_enum:
               ps.break_after_comma = false;
               if (ps.psyms.sym[ps.psyms.len - 1] == psym_decl
                   || ps.psyms.sym[ps.psyms.len - 1] == psym_stmt)
                       ps.ind_level_follow++;
               else if (code.len == 0) {
                       /* It is part of a while, for, etc. */
                       ps.ind_level--;

                       /* for a switch, brace should be two levels out from
                        * the code */
                       if (ps.psyms.sym[ps.psyms.len - 1] == psym_switch_expr
                           && opt.case_indent >= 1.0F)
                               ps.ind_level--;
               }

               ps_psyms_push(psym, ps.ind_level);
               ps_psyms_push(psym_stmt, ps.ind_level_follow);
               break;

       case psym_rbrace:
               /* stack should have <lbrace> <stmt> or <lbrace> <decl> */
               if (!(ps.psyms.len >= 2
                       && is_lbrace(ps.psyms.sym[ps.psyms.len - 2]))) {
                       diag(1, "Statement nesting error");
                       break;
               }
               ps_psyms_replace2(psym_stmt);
               ps.ind_level = ps.ind_level_follow;
               break;

       case psym_decl:
               if (ps.psyms.sym[ps.psyms.len - 1] == psym_decl)
                       break;  /* only put one declaration onto stack */

               ps.break_after_comma = true;
               ps_psyms_push(psym_decl, ps.ind_level_follow);

               if (opt.left_justify_decl) {
                       ps.ind_level = left_justify_decl_level();
                       ps.ind_level_follow = ps.ind_level;
               }
               break;

       case psym_stmt:
               ps.break_after_comma = false;
               ps_psyms_push(psym_stmt, ps.ind_level);
               break;

       case psym_if_expr:
               if (ps.psyms.sym[ps.psyms.len - 1] == psym_if_expr_stmt_else
                   && opt.else_if_in_same_line) {
                       ps.ind_level_follow =
                           ps.psyms.ind_level[ps.psyms.len - 1];
                       ps.psyms.len--;
               }
               /* FALLTHROUGH */
       case psym_do:
       case psym_for_exprs:
               ps.ind_level = ps.ind_level_follow;
               ps.ind_level_follow = ps.ind_level + 1;
               ps_psyms_push(psym, ps.ind_level);
               break;

       case psym_else:
               if (ps.psyms.sym[ps.psyms.len - 1] != psym_if_expr_stmt) {
                       diag(1, "Unmatched 'else'");
                       break;
               }
               ps.ind_level = ps.psyms.ind_level[ps.psyms.len - 1];
               ps.ind_level_follow = ps.ind_level + 1;
               ps.psyms.sym[ps.psyms.len - 1] = psym_if_expr_stmt_else;
               break;

       case psym_switch_expr:
               ps_psyms_push(psym_switch_expr, ps.ind_level_follow);
               ps.ind_level_follow += (int)opt.case_indent + 1;
               break;

       case psym_while_expr:
               if (ps.psyms.sym[ps.psyms.len - 1] == psym_do_stmt) {
                       ps.ind_level = ps.psyms.ind_level[ps.psyms.len - 1];
                       ps.ind_level_follow = ps.ind_level;
                       ps_psyms_push(psym_while_expr, ps.ind_level);
               } else {
                       ps_psyms_push(psym_while_expr, ps.ind_level_follow);
                       ps.ind_level_follow++;
               }
               break;

       default:
               diag(1, "Unknown code to parser");
               return;
       }

#if debug
       static struct buffer before, after;
       buf_clear(&before);
       ps_psyms_to_string(&before, &ps);
#endif
       psyms_reduce();
#if debug
       buf_clear(&after);
       ps_psyms_to_string(&after, &ps);
       if (strcmp(before.s, after.s) != 0) {
               debug_println("psyms before: %s", before.s);
               debug_println("psyms after:  %s", after.s);
       } else
               debug_println("psyms: %s", after.s);
#endif
}