/*	$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
}
