/*-------------------------------------------------------------------------
 * A stack of automaton states to handle nested conditionals.
 *
 * Copyright (c) 2000-2024, PostgreSQL Global Development Group
 *
 * src/fe_utils/conditional.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres_fe.h"

#include "fe_utils/conditional.h"

/*
 * create stack
 */
ConditionalStack
conditional_stack_create(void)
{
	ConditionalStack cstack = pg_malloc(sizeof(ConditionalStackData));

	cstack->head = NULL;
	return cstack;
}

/*
 * Destroy all the elements from the stack. The stack itself is not freed.
 */
void
conditional_stack_reset(ConditionalStack cstack)
{
	if (!cstack)
		return;					/* nothing to do here */

	while (conditional_stack_pop(cstack))
		continue;
}

/*
 * destroy stack
 */
void
conditional_stack_destroy(ConditionalStack cstack)
{
	conditional_stack_reset(cstack);
	free(cstack);
}

/*
 * Create a new conditional branch.
 */
void
conditional_stack_push(ConditionalStack cstack, ifState new_state)
{
	IfStackElem *p = (IfStackElem *) pg_malloc(sizeof(IfStackElem));

	p->if_state = new_state;
	p->query_len = -1;
	p->paren_depth = -1;
	p->lex_state = NULL;
	p->next = cstack->head;
	cstack->head = p;
}

/*
 * Destroy the topmost conditional branch.
 * Returns false if there was no branch to end.
 */
bool
conditional_stack_pop(ConditionalStack cstack)
{
	IfStackElem *p = cstack->head;

	if (!p)
		return false;
	cstack->head = cstack->head->next;
	if (p->lex_state)
		free(p->lex_state);
	free(p);
	return true;
}

/*
 * Returns current stack depth, for debugging purposes.
 */
int
conditional_stack_depth(ConditionalStack cstack)
{
	if (cstack == NULL)
		return -1;
	else
	{
		IfStackElem *p = cstack->head;
		int			depth = 0;

		while (p != NULL)
		{
			depth++;
			p = p->next;
		}
		return depth;
	}
}

/*
 * Fetch the current state of the top of the stack.
 */
ifState
conditional_stack_peek(ConditionalStack cstack)
{
	if (conditional_stack_empty(cstack))
		return IFSTATE_NONE;
	return cstack->head->if_state;
}

/*
 * Change the state of the topmost branch.
 * Returns false if there was no branch state to set.
 */
bool
conditional_stack_poke(ConditionalStack cstack, ifState new_state)
{
	if (conditional_stack_empty(cstack))
		return false;
	cstack->head->if_state = new_state;
	return true;
}

/*
 * True if there are no active \if-blocks.
 */
bool
conditional_stack_empty(ConditionalStack cstack)
{
	return cstack->head == NULL;
}

/*
 * True if we should execute commands normally; that is, the current
 * conditional branch is active, or there is no open \if block.
 */
bool
conditional_active(ConditionalStack cstack)
{
	ifState		s = conditional_stack_peek(cstack);

	return s == IFSTATE_NONE || s == IFSTATE_TRUE || s == IFSTATE_ELSE_TRUE;
}

/*
 * Save current query buffer length in topmost stack entry.
 */
void
conditional_stack_set_query_len(ConditionalStack cstack, int len)
{
	Assert(!conditional_stack_empty(cstack));
	cstack->head->query_len = len;
}

/*
 * Fetch last-recorded query buffer length from topmost stack entry.
 * Will return -1 if no stack or it was never saved.
 */
int
conditional_stack_get_query_len(ConditionalStack cstack)
{
	if (conditional_stack_empty(cstack))
		return -1;
	return cstack->head->query_len;
}

/*
 * Save current lexer state in topmost stack entry.
 *
 * The lexer state is presumed to be a single pg_malloc'd chunk.
 * It will be freed automatically when the stack entry is popped.
 */
void
conditional_stack_set_lex_state(ConditionalStack cstack,
								struct PsqlScanStateSave *lex_state)
{
	Assert(!conditional_stack_empty(cstack));
	if (cstack->head->lex_state)	/* free old state, if any */
		free(cstack->head->lex_state);
	cstack->head->lex_state = lex_state;
}

/*
 * Fetch last-recorded lexer state from topmost stack entry.
 * Will return NULL if no stack or it was never saved.
 */
struct PsqlScanStateSave *
conditional_stack_get_lex_state(ConditionalStack cstack)
{
	if (conditional_stack_empty(cstack))
		return NULL;
	return cstack->head->lex_state;
}

/*
 * Save current parenthesis nesting depth in topmost stack entry.
 *
 * (These functions are obsolete, and kept around only to avoid API/ABI
 * breakage in the back branches.)
 */
void
conditional_stack_set_paren_depth(ConditionalStack cstack, int depth)
{
	Assert(!conditional_stack_empty(cstack));
	cstack->head->paren_depth = depth;
}

/*
 * Fetch last-recorded parenthesis nesting depth from topmost stack entry.
 * Will return -1 if no stack or it was never saved.
 */
int
conditional_stack_get_paren_depth(ConditionalStack cstack)
{
	if (conditional_stack_empty(cstack))
		return -1;
	return cstack->head->paren_depth;
}
