/************************************************************************/
/*                                                                      */
/* Copyright (c) 1988 -                                                 */
/*     The Rector and Visitors of the University of Virginia            */
/*                                                                      */
/* This code may not be distributed further without permission          */
/*      from the University of Virginia.                                */
/* This code is distributed WITHOUT ANY WARRANTY.  No claims are        */
/*      made as to whether it serves any particular purpose or even     */
/*      works at all.                                                   */
/*                                                                      */
/************************************************************************/

/*
 * this file contains code that maintains a data structure (LGRAPH)
 * that is used to ensure that goto statements are legal.
 * An LGRAPH node is essentially a pointer to its parent LGRAPH
 * node.  A new LGRAPH is started upon entering a function or
 * procedure.  A new node is generated whenever the parser drops inside a
 * compound-statement, if-statement, while-statement, etc.  The
 * LGRAPH simply provides a means to ensure that a goto-statement
 * is jumping to a label that is located in a surrounding code block.
 * When a label is encountered, the current LGRAPH node is associated with
 * it.  When a goto statement is later encountered, the current LGRAPH is
 * traversed looking for the label's LGRAPH node.  If the label's node
 * is on the chain, the goto is legal.  When a goto statement is
 * encountered before the target statement label, the goto's LGRAPH
 * node must be saved and checked later when the label is reached.
 */

#include "types.h"
#include "object.h"
#include "ident.h"
#include "error.h"

#define LB_CHUNK 50

LGRAPH *lb_current;

/*
 * lb_new() returns a unique integer to be used as a label.
 */

int
lb_new()
{
	static int curlabel=0;
	return(++curlabel);
}

/*
 * lb_alloc() gets space to store a node in a LGRAPH.
 */

LGRAPH *
lb_alloc()
{
	static unsigned count=0;
	static LGRAPH *next;
	char *calloc();
	
	if (count == 0) {
		next = (LGRAPH *) calloc(count=LB_CHUNK, sizeof(LGRAPH));
		if (next == LB_NULL) {
			sprintf(err_text, err_nomem, "lb_alloc()");
			err_msg(E_INTERNAL, E_CURSYMBOL);
		}
	}
	count--;
	return(next++);
}

/*
 * lb_descend() creates a new LGRAPH node when the parser drops
 * into a new code block (such as an if-statement).  The new node
 * points to the LGRAPH node that was generated when the
 * immediately enclosing code block was entered.
 */

lb_descend()
{
	LGRAPH *lb;

	lb = lb_alloc();
	lb->lb_level = ob_level;
	lb->lb_outer = lb_current;
	lb_current = lb;
}

/*
 * lb_ascend() sets the current LGRAPH node to the parent node.
 * This is called upon leaving a block of code (such as an if-statement).
 */

lb_ascend()
{
	lb_current = lb_current->lb_outer;
}

/*
 * lb_deflabel() defines an object in the symbol table for a
 * Pascal label.  This is called when parsing a label-declaration-part
 */

lb_deflabel(labelid)
	IDENT *labelid;
{
	OBJECT *label;
	label = ob_define(labelid, OB_LABEL, 0);
	label->ob.label.stmt = LB_NULL;
	label->ob.label.gotolist = LB_NULL;
	label->ob.label.id = lb_new();
}

/*
 * lb_goto() emits a jump to a Pascal label.  But first it checks to
 * see if the goto is legal.  The label was stored in the symbol table
 * when it was declared.  If the label has not yet been used as a
 * statement label, we cannot tell if the goto is legal.  We put the
 * current LGRAPH node on a linked list that is stored with the label's
 * symbol table entry.  If we have seen the label, we chase up the
 * current LGRAPH to find the label's LGRAPH entry.
 */
lb_goto(labelid)
	IDENT *labelid;
{
	OBJECT *lb;
	LGRAPH *lgoto, *lstmt;
	if (!(lb = ob_searchall(labelid))) {
		if (labelid != id_noname) {
			sprintf(err_text, err_undef, "label", labelid->id_name);
			err_msg(E_FATAL, E_PREVSYMBOL);
		}
	}
	else if (lstmt = lb->ob.label.stmt) {
		/*
		 * Have seen a statment with this label, so this goto statement 
		 * is in the same subprogram as the label.  The statement sequence
		 * with the labeled statement must enclose the goto statement.
		 */
		for (lgoto = lb_current; lgoto && lgoto != lstmt;
			lgoto = lgoto->lb_outer)
			;
		if (lgoto != lstmt) {
			sprintf (err_text, "cannot \"goto %s\" from here",
				lb->ob_name);
			err_msg(E_FATAL, E_PREVSYMBOL);
		}
	}
	else {
		/*
		 * have not seen the label.  save goto info for later.
		 * Note how LGRAPH struct serves double duty here.
		 * the lb_prev pointers form a list and the lb_outer
		 * pointers point to the actual LGRAPH nodes.
		 */
		lgoto = lb_alloc();
		lgoto->lb_outer = lb_current;
		lgoto->lb_prev = lb->ob.label.gotolist;
		lb->ob.label.gotolist = lgoto;
			
	}
	if (lb) {
		c_goto (lb->ob.label.id, lb->ob_level);
		lb->ob_flags |= OB_USED;
	}
}

/*
 * lb_label() is called when a labeled statement is encountered.
 * The label must be declared in the current level.  We may have
 * already seen goto statements to the label, in which case we
 * must run down the gotolist to be sure they are all legal.
 * Also save the current LGRAPH node in the symbol table entry
 * so that we can test the legality of subsequent goto statements
 */
lb_label(labelid)
	IDENT *labelid;
{
	OBJECT *lb;
	LGRAPH *lbp, *lgoto, *lstmt;
	int errflag=0;

	/* label must be declared at this block level */

	if (!(lb = ob_search(labelid, ob_level))) {
		if (labelid != id_noname) {
			sprintf(err_text, err_undef, "label", labelid->id_name);
			err_msg(E_FATAL, E_PREVSYMBOL);
		}
	}
	else if (lb->ob.label.stmt) {
		sprintf(err_text, "another statement has label \"%s\"",
			lb->ob_name);
		err_msg(E_FATAL, E_PREVSYMBOL);
	}
	else {
		lb->ob_flags |= OB_INIT;
		lstmt = lb->ob.label.stmt = lb_current;
		/*
		 * make sure any previous gotos to this statement are legal
		 */
		for (lbp = lb->ob.label.gotolist; lbp; lbp = lbp->lb_prev) {
			lgoto = lbp->lb_outer;
			if (lgoto->lb_level != lstmt->lb_level) {
				/*
				 * the goto statement is in an enclosed subprogram so the
				 * labeled statement must be in the outermost statement
				 * sequence in this subprogram.
				 */
				if (lstmt->lb_outer) {
					errflag++;
					break;
				}
			}
			else {
				/*
				 * the goto statement is in the same subprogram so the
				 * statement sequence with the labeled statement must
				 * enclose the goto statement.
				 */
				for (; lgoto && lgoto != lstmt; lgoto = lgoto->lb_outer)
					;
				if (lgoto != lstmt) {
					errflag++;
					break;
				}
			}
		}
		if (errflag) {
			sprintf(err_text,
				"previous \"goto %s\" statement(s) cannot jump here.",
				lb->ob_name);
			err_msg(E_FATAL, E_PREVSYMBOL);
		}
		c_label(lb->ob.label.id);
	}
}
