/* "p2c", a Pascal to C translator.
   Copyright (C) 1989 David Gillespie.
   Author's address: daveg@csvax.caltech.edu; 256-80 Caltech/Pasadena CA 91125.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation (any version).

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; see the file COPYING.  If not, write to
the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */

#define PROTO_PEXPR4_C
#include "trans.h"

#define EXTRASPACE() if (spaceexprs == 1) output(" ")
#define NICESPACE()  if (spaceexprs != 0) output(" ")

#define outop(name) do { \
    NICESPACE(); outop3(breakflag, name); NICESPACE(); \
} while (0)

#define outop2(name) do { \
    EXTRASPACE(); outop3(breakflag, name); EXTRASPACE(); \
} while (0)

#define checkbreak(code) do { \
    breakflag=(code); \
    if ((prec != -99) && (breakflag & BRK_ALLNONE)) output("\007"); \
} while (0)

#define setprec(p) \
    if ((subprec=(p)) <= prec) { \
        parens = 1; output("("); \
    }

#define setprec2(p) \
    if ((subprec=(p)) <= prec) { \
        parens = 1; output("("); \
    } else if (prec != -99) { \
        parens = 2; output((breakparens == 1) ? "\010" : "\003"); \
    }

#define setprec3(p) \
    if ((subprec=(p)) <= prec) { \
         parens = 1; output("("); \
    } else if (prec != -99) { \
         parens = 2; output((prec > 2 && breakparens != 0) ? "\010" \
			                                   : "\003"); \
    }

Static int scanfield(variants, unions, lev, mp, field)
Meaning **variants, *mp, *field;
short *unions;
int lev;
{
    int i, num, breakflag;
    Value v;

    unions[lev] = (mp && mp->kind == MK_VARIANT);
    while (mp && mp->kind == MK_FIELD) {
        if (mp == field) {
            for (i = 0; i < lev; i++) {
		v = variants[i]->val;    /* sidestep a Sun 386i compiler bug */
                num = ord_value(v);
		breakflag = breakbeforedot;
                if (!unions[i]) {
                    output(format_s(name_UNION, ""));
		    outop2(".");
                }
                if (variants[i]->ctx->cnext ||
                    variants[i]->ctx->kind != MK_FIELD) {
                    output(format_s(name_VARIANT, variantfieldname(num)));
		    outop2(".");
                }
            }
            output(mp->name);
            return 1;
        }
        mp = mp->cnext;
    }
    while (mp && mp->kind == MK_VARIANT) {
        variants[lev] = mp;
        if (scanfield(variants, unions, lev+1, mp->ctx, field))
            return 1;
        mp = mp->cnext;
    }
    return 0;
}

void out_field(mp)
Meaning *mp;
{
    Meaning *variants[50];
    short unions[51];

    if (!scanfield(variants, unions, 0, mp->rectype->fbase, mp))
        intwarning("out_field", "field name not in tree [309]");
    else if (mp->warnifused) {
        if (mp->rectype->meaning)
            note(format_ss("Reference to field %s of record %s [282]", 
                           mp->name, mp->rectype->meaning->name));
        else
            note(format_s("Reference to field %s [282]", mp->name));
    }
}

Static void wrexpr(ex, prec)
Expr *ex;
int prec;
{
    short parens = 0;
    int subprec, i, j, minusflag, breakflag = 0;
    int saveindent;
    Expr *ex2, *ex3;
    char *cp;
    Meaning *mp;
    Symbol *sp;

    if (debug>2) { fprintf(outf,"wrexpr{"); dumpexpr(ex); fprintf(outf,", %d}\n", prec); }
    switch (ex->kind) {

        case EK_VAR:
            mp = (Meaning *)ex->val.i;
            if (mp->warnifused)
                note(format_s("Reference to %s [283]", mp->name));
            out_var(mp, prec);
            break;

        case EK_NAME:
            output(ex->val.s);
            break;

        case EK_MACARG:
            output("<meef>");
            intwarning("wrexpr", "Stray EK_MACARG encountered [310]");
            break;

        case EK_CTX:
            out_ctx((Meaning *)ex->val.i, 1);
            break;

        case EK_CONST:
            if (ex->nargs > 0)
                cp = value_name(ex->val, ex->args[0]->val.s, 0);
            else
                cp = value_name(ex->val, NULL, 0);
            if (*cp == '-')
                setprec(14);
            output(cp);
            break;

        case EK_LONGCONST:
            if (ex->nargs > 0)
                cp = value_name(ex->val, ex->args[0]->val.s, 1);
            else
                cp = value_name(ex->val, NULL, 1);
            if (*cp == '-')
                setprec(14);
            output(cp);
            break;

        case EK_STRUCTCONST:
            ex3 = NULL;
            for (i = 0; i < ex->nargs; i++) {
                ex2 = ex->args[i];
                if (ex2->kind == EK_STRUCTOF) {
                    j = ex2->val.i;
                    ex2 = ex2->args[0];
                } else
                    j = 1;
                if (ex2->kind == EK_VAR) {
                    mp = (Meaning *)ex2->val.i;
                    if (mp->kind == MK_CONST &&
                        (mp->val.type->kind == TK_RECORD ||
                         mp->val.type->kind == TK_ARRAY)) {
                        if (foldconsts != 1)
                            note(format_s("Expanding constant %s into another constant [284]",
                                          mp->name));
                        ex2 = (Expr *)mp->val.i;
                    }
                }
                while (--j >= 0) {
                    if (ex3) {
                        if (ex3->kind == EK_STRUCTCONST ||
                            ex2->kind == EK_STRUCTCONST)
                            output(",\n");
                        else
                            output(",\001 ");
                    }
                    if (ex2->kind == EK_STRUCTCONST) {
                        output("{ \005");
			saveindent = outindent;
			moreindent(extrainitindent);
                        out_expr(ex2);
                        outindent = saveindent;
                        output(" }");
                    } else
                        out_expr(ex2);
                    ex3 = ex2;
                }
            }
            break;

        case EK_FUNCTION:
            mp = (Meaning *)ex->val.i;
	    sp = findsymbol_opt(mp->name);
	    if ((sp && (sp->flags & WARNLIBR)) || mp->warnifused)
                note(format_s("Called procedure %s [285]", mp->name));
            output(mp->name);
            output("(\002");
	    j = sp ? (sp->flags & FUNCBREAK) : 0;
	    if (j == FALLBREAK)
		output("\007");
            for (i = 0; i < ex->nargs; i++) {
		if ((j == FSPCARG1 && i == 1) ||
		    (j == FSPCARG2 && i == 2) ||
		    (j == FSPCARG3 && i == 3))
		    output(",\011 ");
                else if (i > 0)
                    output(",\002 ");
                out_expr(ex->args[i]);
            }
            if (mp->ctx->kind == MK_FUNCTION && mp->ctx->varstructflag) {
                if (i > 0)
                    output(",\002 ");
                out_ctx(mp->ctx, 1);
            }
            output(")");
            break;

        case EK_BICALL:
            cp = ex->val.s;
            while (*cp == '*')
                cp++;
	    sp = findsymbol_opt(cp);
	    if (sp && (sp->flags & WARNLIBR))
                note(format_s("Called library procedure %s [286]", cp));
            output(cp);
            output("(\002");
	    j = sp ? (sp->flags & FUNCBREAK) : 0;
	    if (j == FALLBREAK)
		output("\007");
            for (i = 0; i < ex->nargs; i++) {
		if ((j == FSPCARG1 && i == 1) ||
		    (j == FSPCARG2 && i == 2) ||
		    (j == FSPCARG3 && i == 3))
		    output(",\011 ");
                else if (i > 0)
                    output(",\002 ");
                out_expr(ex->args[i]);
            }
            output(")");
            break;

        case EK_SPCALL:
            setprec(16);
            if (starfunctions) {
                output("(\002*");
                wrexpr(ex->args[0], 13);
                output(")");
            } else
                wrexpr(ex->args[0], subprec-1);
            output("(\002");
            for (i = 1; i < ex->nargs; i++) {
                if (i > 1)
                    output(",\002 ");
                out_expr(ex->args[i]);
            }
            output(")");
            break;

        case EK_INDEX:
            setprec(16);
            wrexpr(ex->args[0], subprec-1);
	    if (lookback(1) == ']')
		output("\001");
            output("[");
            out_expr(ex->args[1]);
            output("]");
            break;

        case EK_DOT:
            setprec2(16);
	    checkbreak(breakbeforedot);
            if (ex->args[0]->kind == EK_HAT) {
                wrexpr(ex->args[0]->args[0], subprec-1);
                outop2("->");
            } else if (ex->args[0]->kind == EK_CTX) {
                out_ctx((Meaning *)ex->args[0]->val.i, 0);
            } else {
                wrexpr(ex->args[0], subprec-1);
                outop2(".");
            }
            if (ex->val.i)
                out_field((Meaning *)ex->val.i);
            else
                output(ex->val.s);
            break;

        case EK_POSTINC:
	    if (prec == 0 && !postincrement) {
		setprec(14);
		output("++");
		EXTRASPACE();
		wrexpr(ex->args[0], subprec);
	    } else {
		setprec(15);
		wrexpr(ex->args[0], subprec);
		EXTRASPACE();
		output("++");
	    }
            break;

        case EK_POSTDEC:
	    if (prec == 0 && !postincrement) {
		setprec(14);
		output("--");
		EXTRASPACE();
		wrexpr(ex->args[0], subprec);
	    } else {
		setprec(15);
		wrexpr(ex->args[0], subprec);
		EXTRASPACE();
		output("--");
	    }
            break;

        case EK_HAT:
            setprec(14);
	    if (lookback_prn(1) == '/')
		output(" ");
            output("*");
            EXTRASPACE();
            wrexpr(ex->args[0], subprec-1);
            break;

        case EK_ADDR:
            setprec(14);
	    if (lookback_prn(1) == '&')
		output(" ");
            output("&");
            EXTRASPACE();
            wrexpr(ex->args[0], subprec-1);
            break;

        case EK_NEG:
            setprec(14);
            output("-");
            EXTRASPACE();
            if (ex->args[0]->kind == EK_TIMES)
                wrexpr(ex->args[0], 12);
            else
                wrexpr(ex->args[0], subprec-1);
            break;

        case EK_NOT:
            setprec(14);
            output("!");
            EXTRASPACE();
            wrexpr(ex->args[0], subprec-1);
            break;

        case EK_BNOT:
            setprec(14);
            output("~");
            EXTRASPACE();
            wrexpr(ex->args[0], subprec-1);
            break;

        case EK_CAST:
        case EK_ACTCAST:
            if (similartypes(ex->val.type, ex->args[0]->val.type)) {
                wrexpr(ex->args[0], prec);
            } else if (ord_type(ex->args[0]->val.type)->kind == TK_ENUM &&
                       ex->val.type == tp_int && !useenum) {
                wrexpr(ex->args[0], prec);
            } else {
                setprec2(14);
                output("(");
                out_type(ex->val.type, 0);
                output(")\002");
                EXTRASPACE();
                if (extraparens != 0)
                    wrexpr(ex->args[0], 15);
                else
                    wrexpr(ex->args[0], subprec-1);
            }
            break;

        case EK_LITCAST:
            setprec2(14);
            output("(");
            out_expr(ex->args[0]);
            output(")\002");
            EXTRASPACE();
            if (extraparens != 0)
                wrexpr(ex->args[1], 15);
            else
                wrexpr(ex->args[1], subprec-1);
            break;

        case EK_SIZEOF:
            setprec(14);
            output("sizeof(");
	    out_expr(ex->args[0]);
            output(")");
            break;

	case EK_TYPENAME:
	    out_type(ex->val.type, 1);
	    break;

        case EK_TIMES:
	    setprec2(13);
	    checkbreak(breakbeforearith);
            ex2 = copyexpr(ex);
            if (expr_looks_neg(ex2->args[ex2->nargs-1])) {
                ex2->args[0] = makeexpr_neg(ex2->args[0]);
                ex2->args[ex2->nargs-1] = makeexpr_neg(ex2->args[ex2->nargs-1]);
            }
            wrexpr(ex2->args[0], incompat(ex2, 0, subprec-1));
            for (i = 1; i < ex2->nargs; i++) {
                outop("*");
                wrexpr(ex2->args[i], incompat(ex2, i, subprec));
            }
            freeexpr(ex2);
            break;

        case EK_DIV:
        case EK_DIVIDE:
            setprec2(13);
	    checkbreak(breakbeforearith);
	    wrexpr(ex->args[0], incompat(ex, 0, subprec-1));
            outop("/");
            wrexpr(ex->args[1], incompat(ex, 1, subprec));
            break;

        case EK_MOD:
            setprec2(13);
	    checkbreak(breakbeforearith);
            wrexpr(ex->args[0], incompat(ex, 0, subprec-1));
            outop("%");
            wrexpr(ex->args[1], incompat(ex, 1, subprec));
            break;

        case EK_PLUS:
            setprec2(12);
	    checkbreak(breakbeforearith);
            ex2 = copyexpr(ex);
            minusflag = 0;
            if (expr_looks_neg(ex2->args[0])) {
                j = 1;
                while (j < ex2->nargs && expr_looks_neg(ex2->args[j])) j++;
                if (j < ex2->nargs)
                    swapexprs(ex2->args[0], ex2->args[j]);
            } else if (ex2->val.i && ex2->nargs == 2) {   /* this was originally "a-b" */
                if (isliteralconst(ex2->args[1], NULL) != 2) {
                    if (expr_neg_cost(ex2->args[1]) <= 0) {
                        minusflag = 1;
                    } else if (expr_neg_cost(ex2->args[0]) <= 0) {
                        swapexprs(ex2->args[0], ex2->args[1]);
                        if (isliteralconst(ex2->args[0], NULL) != 2)
                            minusflag = 1;
                    }
                }
            }
            wrexpr(ex2->args[0], incompat(ex, 0, subprec));
            for (i = 1; i < ex2->nargs; i++) {
                if (expr_looks_neg(ex2->args[i]) || minusflag) {
                    outop("-");
                    ex2->args[i] = makeexpr_neg(ex2->args[i]);
                } else
                    outop("+");
                wrexpr(ex2->args[i], incompat(ex, i, subprec));
            }
            freeexpr(ex2);
            break;

        case EK_LSH:
            setprec3(11);
	    checkbreak(breakbeforearith);
            wrexpr(ex->args[0], incompat(ex, 0, subprec));
            outop("<<");
            wrexpr(ex->args[1], incompat(ex, 1, subprec));
            break;

        case EK_RSH:
            setprec3(11);
	    checkbreak(breakbeforearith);
	    wrexpr(ex->args[0], incompat(ex, 0, subprec));
            outop(">>");
            wrexpr(ex->args[1], incompat(ex, 1, subprec));
            break;

        case EK_LT:
            setprec2(10);
	    checkbreak(breakbeforerel);
            wrexpr(ex->args[0], incompat(ex, 0, subprec));
            outop("<");
            wrexpr(ex->args[1], incompat(ex, 0, subprec));
            break;

        case EK_GT:
            setprec2(10);
	    checkbreak(breakbeforerel);
            wrexpr(ex->args[0], incompat(ex, 0, subprec));
            outop(">");
            wrexpr(ex->args[1], incompat(ex, 0, subprec));
            break;

        case EK_LE:
            setprec2(10);
	    checkbreak(breakbeforerel);
            wrexpr(ex->args[0], incompat(ex, 0, subprec));
            outop("<=");
            wrexpr(ex->args[1], incompat(ex, 0, subprec));
            break;

        case EK_GE:
            setprec2(10);
	    checkbreak(breakbeforerel);
            wrexpr(ex->args[0], incompat(ex, 0, subprec));
            outop(">=");
            wrexpr(ex->args[1], incompat(ex, 0, subprec));
            break;

        case EK_EQ:
            setprec2(9);
	    checkbreak(breakbeforerel);
            wrexpr(ex->args[0], incompat(ex, 0, subprec));
            outop("==");
            wrexpr(ex->args[1], incompat(ex, 0, subprec));
            break;

        case EK_NE:
            setprec2(9);
	    checkbreak(breakbeforerel);
            wrexpr(ex->args[0], incompat(ex, 0, subprec));
            outop("!=");
            wrexpr(ex->args[1], incompat(ex, 0, subprec));
            break;

        case EK_BAND:
            setprec3(8);
	    if (ex->val.type == tp_boolean)
		checkbreak(breakbeforelog);
	    else
		checkbreak(breakbeforearith);
            wrexpr(ex->args[0], incompat(ex, 0, subprec-1));
	    outop("&");
            wrexpr(ex->args[1], incompat(ex, 1, subprec-1));
            break;

        case EK_BXOR:
            setprec3(7);
	    checkbreak(breakbeforearith);
            wrexpr(ex->args[0], incompat(ex, 0, subprec-1));
            outop("^");
            wrexpr(ex->args[1], incompat(ex, 1, subprec-1));
            break;

        case EK_BOR:
            setprec3(6);
	    if (ex->val.type == tp_boolean)
		checkbreak(breakbeforelog);
	    else
		checkbreak(breakbeforearith);
            wrexpr(ex->args[0], incompat(ex, 0, subprec-1));
	    outop("|");
            wrexpr(ex->args[1], incompat(ex, 1, subprec-1));
            break;

        case EK_AND:
            setprec3(5);
	    checkbreak(breakbeforelog);
	    wrexpr(ex->args[0], incompat(ex, 0, subprec-1));
            outop("&&");
	    wrexpr(ex->args[1], incompat(ex, 1, subprec-1));
            break;

        case EK_OR:
            setprec3(4);
	    checkbreak(breakbeforelog);
	    wrexpr(ex->args[0], incompat(ex, 0, subprec-1));
            outop("||");
	    wrexpr(ex->args[1], incompat(ex, 1, subprec-1));
            break;

        case EK_COND:
            setprec3(3);
	    i = 0;
	    for (;;) {
		i++;
		if (extraparens != 0)
		    wrexpr(ex->args[0], 15);
		else
		    wrexpr(ex->args[0], subprec);
		NICESPACE();
		output("\002?");
		NICESPACE();
		out_expr(ex->args[1]);
		if (ex->args[2]->kind == EK_COND) {
		    NICESPACE();
		    output("\002:");
		    NICESPACE();
		    ex = ex->args[2];
		} else {
		    NICESPACE();
		    output((i == 1) ? "\017:" : "\002:");
		    NICESPACE();
		    wrexpr(ex->args[2], subprec-1);
		    break;
		}
	    }
            break;

        case EK_ASSIGN:
            if (ex->args[1]->kind == EK_PLUS &&
                exprsame(ex->args[1]->args[0], ex->args[0], 2) &&
                ex->args[1]->args[1]->kind == EK_CONST &&
                ex->args[1]->args[1]->val.type->kind == TK_INTEGER &&
                abs(ex->args[1]->args[1]->val.i) == 1) {
		if (prec == 0 && postincrement) {
		    setprec(15);
		    wrexpr(ex->args[0], subprec);
		    EXTRASPACE();
		    if (ex->args[1]->args[1]->val.i == 1)
			output("++");
		    else
			output("--");
		} else {
		    setprec(14);
		    if (ex->args[1]->args[1]->val.i == 1)
			output("++");
		    else
			output("--");
		    EXTRASPACE();
		    wrexpr(ex->args[0], subprec-1);
		}
            } else {
                setprec2(2);
		checkbreak(breakbeforeassign);
                wrexpr(ex->args[0], subprec);
                ex2 = copyexpr(ex->args[1]);
                j = -1;
                switch (ex2->kind) {

                    case EK_PLUS:
                    case EK_TIMES:
                    case EK_BAND:
                    case EK_BOR:
                    case EK_BXOR:
                        for (i = 0; i < ex2->nargs; i++) {
                            if (exprsame(ex->args[0], ex2->args[i], 2)) {
                                j = i;
                                break;
                            }
                            if (ex2->val.type->kind == TK_REAL)
                                break;   /* non-commutative */
                        }
                        break;

                    case EK_DIVIDE:
                    case EK_DIV:
                    case EK_MOD:
                    case EK_LSH:
                    case EK_RSH:
                        if (exprsame(ex->args[0], ex2->args[0], 2))
                            j = 0;
                        break;

		    default:
			break;
                }
                if (j >= 0) {
                    if (ex2->nargs == 2)
                        ex2 = grabarg(ex2, 1-j);
                    else
                        delfreearg(&ex2, j);
                    switch (ex->args[1]->kind) {

                        case EK_PLUS:
                            if (expr_looks_neg(ex2)) {
                                outop("-=");
                                ex2 = makeexpr_neg(ex2);
                            } else
                                outop("+=");
                            break;

                        case EK_TIMES:
                            outop("*=");
                            break;

                        case EK_DIVIDE:
                        case EK_DIV:
                            outop("/=");
                            break;

                        case EK_MOD:
                            outop("%=");
                            break;

                        case EK_LSH:
                            outop("<<=");
                            break;

                        case EK_RSH:
                            outop(">>=");
                            break;

                        case EK_BAND:
                            outop("&=");
                            break;

                        case EK_BOR:
                            outop("|=");
                            break;

                        case EK_BXOR:
                            outop("^=");
                            break;

			default:
			    break;
                    }
                } else {
		    output(" ");
		    outop3(breakbeforeassign, "=");
		    output(" ");
                }
                if (extraparens != 0 &&
                    (ex2->kind == EK_EQ || ex2->kind == EK_NE ||
                     ex2->kind == EK_GT || ex2->kind == EK_LT ||
                     ex2->kind == EK_GE || ex2->kind == EK_LE ||
                     ex2->kind == EK_AND || ex2->kind == EK_OR))
                    wrexpr(ex2, 16);
                else
                    wrexpr(ex2, subprec-1);
                freeexpr(ex2);
            }
            break;

        case EK_COMMA:
            setprec3(1);
            for (i = 0; i < ex->nargs-1; i++) {
                wrexpr(ex->args[i], subprec);
                output(",\002");
                NICESPACE();
            }
            wrexpr(ex->args[ex->nargs-1], subprec);
            break;

        default:
            intwarning("wrexpr", "bad ex->kind [311]");
    }
    switch (parens) {
      case 1:
        output(")");
	break;
      case 2:
	output("\004");
	break;
    }
}

/* will parenthesize assignments and "," operators */

void out_expr(ex)
Expr *ex;
{
    wrexpr(ex, 2);
}

/* will not parenthesize anything at top level */

void out_expr_top(ex)
Expr *ex;
{
    wrexpr(ex, 0);
}

/* will parenthesize unless only writing a factor */

void out_expr_factor(ex)
Expr *ex;
{
    wrexpr(ex, 15);
}

/* will parenthesize always */

void out_expr_parens(ex)
Expr *ex;
{
    output("(");
    wrexpr(ex, 1);
    output(")");
}

/* evaluate expression for side effects only */
/* no top-level parentheses */

void out_expr_stmt(ex)
Expr *ex;
{
    wrexpr(ex, 0);
}

/* evaluate expression for boolean (zero/non-zero) result only */
/* parenthesizes like out_expr() */

void out_expr_bool(ex)
Expr *ex;
{
    wrexpr(ex, 2);
}

/* End. */
