/*
 * C compiler
 * ==========
 *
 * Copyright 1989, 1990, 1991 Christoph van Wuellen.
 * Credits to Matthew Brandt.
 * All commercial rights reserved.
 *
 * This compiler may be redistributed as long there is no
 * commercial interest. The compiler must not be redistributed
 * without its full sources. This notice must stay intact.
 *
 * History:
 *
 * 1989   starting an 68000 C compiler, starting with material
 *        originally by M. Brandt
 * 1990   68000 C compiler further bug fixes
 *        started i386 port (December)
 * 1991   i386 port finished (January)
 *        further corrections in the front end and in the 68000
 *        code generator.
 *        The next port will be a SPARC port
 */

#include <ctype.h>
#include "chdr.h"
#include "expr.h"
#include "cglbdec.h"
#include "proto.h"

#if defined(__STDC__) || defined(__cplusplus)
#define P_(s) s
#else
#define P_(s) ()
#endif

/* stmt.c */
static	STMT *	mk_stmt		P_((STMTTYPE));
static	void	check_cases	P_((const STMT *));
static	STMT *	whilestmt	P_((void));
static	STMT *	dostmt		P_((void));
static	STMT *	forstmt		P_((void));
static	STMT *	ifstmt		P_((void));
static	STMT *	casestmt	P_((void));
static	STMT *	switchstmt	P_((void));
static	STMT *	retstmt		P_((void));
static	STMT *	breakstmt	P_((void));
static	STMT *	contstmt	P_((void));
static	STMT *	exprstmt	P_((void));
static	STMT *	labelstmt	P_((void));
static	STMT *	gotostmt	P_((void));
static	STMT *	statement	P_((void));
static	STMT *	compound	P_((void));
#ifdef TRACE
static	STMT *	tracestmt	P_((void));
#endif

#undef P_

/*
 * the statement module handles all of the possible c statements and builds a
 * parse tree of the statements.
 *
 * each routine returns a pointer to a statement parse node which reflects the
 * statement just parsed.
 */

static BOOL return_found;
static int break_lvl=0;
static int cont_lvl=0;
static int case_lvl=0;

static STMT   *
mk_stmt P1( STMTTYPE, st)
{
    STMT   *snp;
    snp = (STMT *) xalloc((size_t) sizeof(STMT));
    snp->stype = st;
    snp->next = NIL_STMT;
    snp->exp = NIL_EXPR;
    snp->s1 = NIL_STMT;
#ifdef DEBUGOPT
    snp->line = act_line;
    snp->linetxt = mk_string(act_linetxt);
#endif /*DEBUGOPT*/
    return snp;
}

/*
 * check to see if the expression is a constant expression ... used
 * to see if conditional expressions are "invariant".
 */
static void
check_unconditional P2(const EXPR *,ep, const char *, str)
{
    if (ep == NIL_EXPR)
	return ;
    if (tst_const(ep))
	message(WARN_CONST, str);
}

/*
 * whilestmt parses the c while statement.
 */
static STMT   *
whilestmt P0(void)
{
    STMT   *snp;
    snp = mk_stmt(st_while);
    break_lvl++;
    cont_lvl++;
    getsym();
    needpunc(tk_openpa);
    snp->exp = condition_expression();
    needpunc(tk_closepa);
    snp->s1 = statement();
    need_label = FALSE;
    break_lvl--;
    cont_lvl--;
    return snp;
}

/*
 * dostmt parses the c do-while construct.
 */
static STMT   *
dostmt P0(void)
{
    STMT   *snp;
    break_lvl++;
    cont_lvl++;
    snp = mk_stmt(st_do);
    getsym();
    snp->s1 = statement();
    needpunc(kw_while);
    needpunc(tk_openpa);
    snp->exp = condition_expression();
    needpunc(tk_closepa);
    needpunc(tk_semicolon);
    need_label = FALSE;
    break_lvl--;
    cont_lvl--;
    return snp;
}

static STMT   *
forstmt P0(void)
{
    STMT   *snp;
    snp = mk_stmt(st_for);
    break_lvl++;
    cont_lvl++;
    getsym();
    needpunc(tk_openpa);
    snp->exp =  expression();
    sequence_point();
    check_discard(snp->exp);
    needpunc(tk_semicolon);
    if (lastst != tk_semicolon) {
	snp->v1.e = condition_expression();
    } else
	snp->v1.e = NIL_EXPR;
    needpunc(tk_semicolon);
    snp->v2.e =  expression();
    sequence_point();
    check_discard(snp->v2.e);
    needpunc(tk_closepa);
    snp->s1 = statement();
    need_label = FALSE;
    break_lvl--;
    cont_lvl--;
    return snp;
}

/*
 * ifstmt parses the c if statement and an else clause if one is present.
 */
static STMT   *
ifstmt P0(void)
{
    STMT   *snp;
    snp = mk_stmt(st_if);
    getsym();
    if (lastst == tk_openpa) {
	BOOL needlab;
	getsym();
	snp->exp = condition_expression();
	check_unconditional(snp->exp,"if");
	needpunc(tk_closepa);
	if (lastst == tk_semicolon)
	    message (WARN_EMPTY);
	snp->s1 = statement();
	needlab = need_label;
	need_label = FALSE;
	if (lastst == kw_else) {
	    getsym();
	    snp->v1.s = statement();
	} else {
	    snp->v1.s = NIL_STMT;
	    /* check for a dangling else statement ... bad coding style? */
	    if (snp->s1 && (snp->s1->stype == st_if) && snp->s1->v1.s)
		message(WARN_ELSE);
	}
	need_label = need_label && needlab;
    } else
	message(ERR_EXPREXPECT);
    return snp;
}

/*
 * consider the following piece of code:
 *
 *	switch (i) {
 *		case 1:
 *			if (j) {
 *				.....
 *			} else
 *		case 2:
 *			....
 *	}
 *
 * case statements may be deep inside, so we need a global variable
 * last_case to link them
 */
static STMT *last_case;		/* last case statement within this switch */
static TYP  *last_switch_type;	/* type of switch controlling expression */

/*
 * cases are returned as seperate statements. for normal cases label is the
 * case value and v1.i is zero. for the default case v1.i is nonzero.
 */
static STMT   *
casestmt P0(void)
{
    STMT   *snp;
    if (lastst == kw_case) {
	getsym();
	snp = mk_stmt(st_case);
	snp->v2.i = intexpr();
	/*
	 * The constant expression in each case label is converted to the
	 * promoted type of the controlling expression.
	 */
#if 0
	if (last_switch_type)
	    check_representable (snp->v2.i, promote(last_switch_type));
#endif
    } else {
	/* lastst is kw_default */
	getsym();
	snp = mk_stmt(st_default);
    }
    snp->s1 = NIL_STMT;
    if (last_case)
	last_case->s1 = snp;
    last_case = snp;
    needpunc(tk_colon);
    snp->v1.s = statement();
    if (case_lvl == 0) {
	message(ERR_CASE);
	snp=snp->v1.s;
    }
    return snp;
}

/*
 * check_cases will check to see if any duplicate cases exist in the case list
 * pointed to by casehead.
 */
static void
check_cases P1(const STMT *, casehead)
{
    const STMT   *top, *cur;
    for (top = casehead; top != NIL_STMT; top = top->s1) {
	for (cur = top->s1; cur != NIL_STMT; cur = cur->s1) {
	    if (cur->stype == st_default) {
		if (top->stype == st_default) {
		    message(ERR_DUPDEFAULT);
		    return;
		}
	    } else {
		if (top->stype != st_default && cur->v2.i == top->v2.i) {
		    message(ERR_DUPCASE, cur->v2.i);
		    return;
		}
	    }
	}
    }
}

static STMT   *
switchstmt P0(void)
{
    STMT   * snp = mk_stmt(st_switch);
    STMT   * local_last_case = last_case;
    TYP	   * local_last_switch_type = last_switch_type;
    break_lvl++;
    case_lvl++;

    getsym();
    last_case = snp;
    snp->s1 = NIL_STMT;
    needpunc(tk_openpa);
    snp->exp = integral_expression();
    sequence_point();
    last_switch_type = snp->exp->etp;
    check_unconditional(snp->exp,"switch");
    needpunc(tk_closepa);
    need_label = TRUE;
    snp->v1.s = statement();
    need_label = FALSE;	/* currently too difficult to work out if true */
    check_cases(snp->s1);
#ifdef FACIST
    {
	STMT *cur;
	for (cur = snp->s1; cur != NIL_STMT; cur = cur->s1) {
	    if (cur->stype == st_default)
		break;
	}
	if (cur == NIL_STMT)
	    message(WARN_DEFAULT);
    }
#endif /* FACIST */

    last_case = local_last_case;
    last_switch_type = local_last_switch_type;
    break_lvl--;
    case_lvl--;
    return snp;
}

static STMT   *
retstmt P0(void)
{
    STMT   *snp;

    snp = mk_stmt(st_return);
    getsym();
    snp->exp = expression();
    sequence_point();
    if (snp->exp != NIL_EXPR) {
	if (is_void(ret_type))
	    message(ERR_VOIDFUNC);
	else if (is_void(snp->exp->etp))
	    message(ERR_VOIDRETURN);
	else {
	    if (snp->exp->nodetype == en_autocon)
		message(WARN_LOCAL);
	    check_qualifiers(ret_type, snp->exp->etp);
	    snp->exp = implicit_castop(snp->exp, ret_type);
	}
    } else if (ret_type->type != bt_void)
	message(WARN_VALRETURN);
    return_found = TRUE;
    needpunc(tk_semicolon);
    need_label = TRUE;
    return snp;
}

static STMT   *
breakstmt P0(void)
{
    STMT   *snp;
    if (break_lvl == 0)
        message(ERR_BREAK);
    snp = mk_stmt(st_break);
    getsym();
    needpunc(tk_semicolon);
    need_label = TRUE;
    return snp;
}

static STMT   *
contstmt P0(void)
{
    STMT   *snp;
    if (cont_lvl == 0)
	message(ERR_CONT);
    snp = mk_stmt(st_continue);
    getsym();
    needpunc(tk_semicolon);
    need_label = TRUE;
    return snp;
}

/*
 * exprstmt is called whenever a statement does not begin with a keyword. the
 * statement should be an expression.
 */
static STMT   *
exprstmt P0(void)
{
    STMT   *snp;
    if (lastst == tk_semicolon)
	snp = NIL_STMT;
    else {
	snp = mk_stmt(st_expr);
	snp->exp = expression();
	if (snp->exp == NIL_EXPR) {
	    /* brute force fix to detect loop */
	    if (errorloop) {
		message(ERR_EXPREXPECT);
	    } else {
		errorloop = TRUE;
		getsym();
	    }
	} else
	    check_discard(snp->exp);
    }
    sequence_point();
    needpunc(tk_semicolon);
    return snp;
}


/*
 * compound processes a block of statements and forms a linked list of the
 * statements within the block.
 */
static STMT   *
compound P0(void)
{
    STMT   *stmthead, *stmttail, *snp;
    needpunc(tk_begin);
    lc_auto = declaration_list(sc_auto,lc_auto);
    snp = mk_stmt(st_compound);
    if (init_node == NIL_EXPR) {
	stmthead = NIL_STMT;
    } else {
	stmthead = stmttail = mk_stmt(st_expr);
	stmthead->exp = init_node;
	stmthead->next = NIL_STMT;
    }
    init_node = NIL_EXPR;
    while (lastst != tk_end) {
	if (stmthead == NIL_STMT)
	    stmthead = stmttail = statement();
	else {
	    stmttail->next = statement();
	    if (stmttail->next != NIL_STMT)
		stmttail = stmttail->next;
	}
    }
    needpunc(tk_end);
    snp->s1 = stmthead;
    return snp;
}

/*
 * labelstmt processes a label that appears before a statement as a seperate
 * statement.
 */
static STMT   *
labelstmt P0(void)
{
    STMT   *snp;
    snp = mk_stmt(st_label);
    snp->v2.l = lab_define(lastsym);
    getsym();
    needpunc(tk_colon);
    snp->v1.s = statement();
    return snp;
}

/*
 * gotostmt processes the goto statement
 */
static STMT   *
gotostmt P0(void)
{
    STMT   *snp;
    snp = mk_stmt(st_goto);
    getsym();
    if (lastst == tk_id) {
	snp->v2.l = lab_search(lastsym);
	getsym();			/* get past label name */
    } else
	message(ERR_IDEXPECT);
    needpunc(tk_semicolon);
    need_label = TRUE;
    return snp;
}

#ifdef ASM
static STMT *
asmstmt P0(void)
{
    STMT   *snp;
    snp = mk_stmt(st_asm);
    getsym();
    needpunc(tk_openpa);
    snp->exp = asm_expression();
    needpunc(tk_closepa);
    return snp;
}
#endif /* ASM */

#ifdef TRACE
/*
 * tracestmt adds calls to a run-time tracing/debugging routine.
 */
static STMT   *
tracestmt P0(void)
{
    STMT   *snp;
    snp = mk_stmt(st_compound);
    snp->s1 = mk_stmt(st_expr);
    snp->s1->exp = traceexpr();
    return snp;
}
#endif /* TRACE */

/*
 * statement figures out which of the statement processors should be called
 * and transfers control to the proper routine.
 */
static STMT   *
statement P0(void)
{
    STMT   *snp;
#ifdef TRACE
    STMT   *tsnp;
    if (trace_option)
	tsnp = tracestmt();
#endif
    switch (lastst) {
      default:
	if (need_label)
	    message (WARN_NOTREACHED);
	/*FALLTHRU*/
      case kw_case:
      case kw_default:
	need_label = FALSE;
	/*FALLTHRU*/
      case tk_begin:
      case tk_id:
	break;
    }
    switch (lastst) {
#ifdef ASM
      case kw_asm:
	snp = asmstmt();
	break;
#endif /* ASM */
      case tk_begin:
	beginblock();
	snp = compound();
	endblock();
	break;
      case kw_if:
	snp = ifstmt();
	break;
      case kw_while:
	snp = whilestmt();
	break;
      case kw_for:
	snp = forstmt();
	break;
      case kw_return:
	snp = retstmt();
	break;
      case kw_break:
	snp = breakstmt();
	break;
      case kw_goto:
	snp = gotostmt();
	break;
      case kw_continue:
	snp = contstmt();
	break;
      case kw_do:
	snp = dostmt();
	break;
      case kw_switch:
	snp = switchstmt();
	break;
      case kw_case:
      case kw_default:
	snp = casestmt();
	break;
      case tk_id:
	if (is_label(lastst)) {
	    need_label = FALSE;
	    snp = labelstmt();
	    break;
	}
	/* else fall through to process expression */
      default:
	if (need_label)
	    message (WARN_NOTREACHED);
	need_label = FALSE;
	snp = exprstmt();
	break;
    }
#ifdef TRACE
    if (trace_option) {
	tsnp->s1->next = snp;
	return tsnp;
    } else
#endif /* TRACE */
    return snp;
}

/*
 * funcbody starts with the current symbol being the begin for the local
 * block or the first symbol of the parameter declaration
 */
void
funcbody P2(SYM *, sp, BLOCK *, block)
{
    int      old_global;
#ifdef CPU_DEFINED
    STMT   *stmt;
    SIZE     poffset, return_block;
    LINE     line = act_line;
#ifdef DEBUGOPT
    CHAR *   linetxt = mk_string(act_linetxt);
#else
    CHAR *   linetxt = NULL;
#endif /* DEBUGOPT */
    SYM     *sp1;
#endif /* CPU_DEFINED */
#ifdef VERBOSE
    clock_t          ltime = clock();

    if (verbose_option)
	eprintf("%s%s", sp->name, newline);
#endif /* VERBOSE */

    uses_structassign = FALSE;
    lc_auto_max = lc_auto = 0L;
    need_label = FALSE;
    return_found = FALSE;
    is_leaf_function = TRUE;
    init_node = NIL_EXPR;
    old_global = global_flag;
    global_flag = 0;
    ret_type = returned_type(sp->tp);

#ifdef CPU_DEFINED
    return_block  = tp_pointer->size;	/* return address */
    return_block += tp_pointer->size;	/* saved frame pointer */
    if (is_structure(ret_type)) {
	return_block += tp_pointer->size;
    }
    poffset = return_block;

    for (sp1 = block->symbols.head; sp1; sp1 = sp1->next) {
	poffset = calculate_offset (sp1, poffset, sc_parms, !is_ansi(sp->tp));
	addoptinfo(sp1, sc_parms);
    }

#endif /* CPU_DEFINED */
    beginfuncblock(block);
#ifdef CPU_DEFINED
    stmt = compound();
#else
    VOIDCAST compound();
#endif /* CPU_DEFINED */
    check_labels();
    if (ret_type->type != bt_void && !return_found)
	message(WARN_IMPLICITRET);
#ifdef VERBOSE
    parse_time += clock() - ltime;
    ltime = clock();
#endif /* VERBOSE */
#ifdef ICODE
    if (icode_option) {
	iprintf("%s:\n", sp->name);
	genicode(stmt,0);
    }
#endif /* ICODE */
#ifdef CPU_DEFINED
    if (code_option) {
	CSE *cse = globalopt(stmt);
#ifdef VERBOSE
	opt_time += clock() - ltime;
	ltime = clock();
#endif /* VERBOSE */
	genfunc(stmt, cse, line, linetxt);
#ifdef VERBOSE
	gen_time += clock() - ltime;
	ltime = clock();
#endif /* VERBOSE */
	g_flush(sp);
#ifdef VERBOSE
	flush_time += clock() - ltime;
	ltime = clock();
#endif /* VERBOSE */
    }
#endif /* CPU_DEFINED */
    endfuncblock();

    rel_local();		/* release local symbols */

    global_flag = old_global;
}
