/*
// Full copyright information is available in the file ../doc/CREDITS
//
// File: decode.c
// ---
// Routines to decompile a method.
*/

#define _DECODE_C_
#include "defs.h"

#include "cdc_pcode.h"
#include "util.h"

#define TOKEN_SIZE (sizeof(binary_tokens) / sizeof(*binary_tokens))
#define PREC_SIZE (sizeof(precedences) / sizeof(*precedences))
#define A_VERY_LARGE_PRECEDENCE_LEVEL	1000

#define COMPLEX_FLAG			0x1
#define SPANNING_IF_FLAG		0x2
#define COMPLEX_IF_FLAG			0x4

/* this hack is for the sole purpose of assignments as expressions decompiling
   correctly */
#define PAREN_ASSIGN 1
#define NOPAREN_ASSIGN 0

typedef struct context Context;

struct context {
    Short type;
    Int end;
    Context *enclosure;
};

INTERNAL Int count_lines(Int start, Int end, unsigned *flags);
INTERNAL Stmt_list *decompile_stmt_list(Int start, Int end);
INTERNAL Stmt *decompile_stmt(Int * pos_ptr);
INTERNAL Stmt *decompile_body(Int start, Int end);
INTERNAL Stmt *decompile_until(Int * start, Int marker);
INTERNAL Stmt *body_from_stmt_list(Stmt_list * stmts);
INTERNAL Case_list *decompile_cases(Int start, Int end);
INTERNAL Case_entry *decompile_case(Int * pos_ptr, Int switch_end);
INTERNAL Expr_list *decompile_case_values(Int * pos_ptr, Int * end_ret);
INTERNAL Id_list *make_error_id_list(Int which);
INTERNAL Expr_list *decompile_expressions(Int * pos_ptr);
INTERNAL Expr_list *decompile_expressions_bounded(Int * pos_ptr, Int end);
INTERNAL cList *unparse_stmt_list(cList * output, Stmt_list * stmts, Int indent);
INTERNAL cList *unparse_stmt(cList * output, Stmt * stmt, Int indent, Stmt * last);
INTERNAL Int is_complex_if_else_stmt(Stmt * stmt);
INTERNAL Int is_complex_type(Int type);
INTERNAL cList *unparse_body(cList * output, Stmt * body, cStr * str, Int indent);
INTERNAL cList *unparse_cases(cList * output, Case_list * cases, Int indent);
INTERNAL cList *unparse_case(cList * output, Case_entry * case_entry, Int indent);
INTERNAL cStr *unparse_expr(cStr * str, Expr * expr, Int paren);
INTERNAL Int is_this(Expr * expr);
INTERNAL cStr *unparse_args(cStr * str, Expr_list * args);
INTERNAL cStr *unparse_dict(cStr * str, Expr_list * args);
INTERNAL cStr *unparse_expr_prec(cStr * str, Expr * expr, Int caller_type, Int assoc);
INTERNAL cStr *unparse_complex_expr(cStr * str, Expr * expr, Int caller_type, Int assoc);
INTERNAL Int prec_level(Int opcode);
INTERNAL char *binary_token(Int opcode);
INTERNAL cList *add_and_discard_string(cList * output, cStr * str);
INTERNAL char *varname(Int ind);

/* These globals get set at the start and are never modified. */
INTERNAL Obj *the_object;
INTERNAL Method *the_method;
INTERNAL Long *the_opcodes;
INTERNAL Int the_increment;

static struct {
    Int opcode;
    char *token;
} binary_tokens[] = {
    {
    '&', "&"}, {
    '|', "|"}, {
    SHLEFT, "<<"}, {
    SHRIGHT, ">>"}, {
    OP_IN, "in"}, {
    EQ, "=="}, {
    NE, "!="}, {
    '>', ">"}, {
    GE, ">="}, {
    '<', "<"}, {
    LE, "<="}, {
    '+', "+"}, {
    '-', "-"}, {
    '*', "*"}, {
    '/', "/"}, {
'%', "%"},};

static struct {
    Int opcode;
    Int level;
} precedences[] = {
    {
    OP_ASSIGN, 1}, {
    PLUS_EQ, 1}, {
    MINUS_EQ, 1}, {
    MULT_EQ, 1}, {
    DIV_EQ, 1}, {
    OPTIONAL_ASSIGN, 1}, {
    CONDITIONAL, 2}, {
    OR, 3}, {
    AND, 4}, {
    OP_IN, 5}, {
    EQ, 6}, {
    NE, 6}, {
    '>', 6}, {
    GE, 6}, {
    '<', 6}, {
    LE, 6}, {
    '+', 7}, {
    '-', 7}, {
    '*', 8}, {
    '/', 8}, {
    '%', 8}, {
    '!', 9}, {
    NEG, 9}, {
    P_INCREMENT, 10}, {
    P_DECREMENT, 10}, {
    INCREMENT, 10}, {
    DECREMENT, 10}, {
    CALL_METHOD, 11}, {
    INDEX, 12}
};

Int line_number(Method * method, Int pc)
{
    Int count = 1;
    unsigned flags;

#if DISABLED
    /* Count declaration lines. */
    if (method->m_flags & MF_NOOVER)
        count++;
#endif

    if (method->num_args || method->rest != -1)
        count++;
    if (method->num_vars)
        count++;
    if (count > 1)
        count++;

    the_opcodes = method->opcodes;
    return count + count_lines(0, pc, &flags);
}

INTERNAL Int count_lines(Int start, Int end, unsigned *flags)
{
    Int count = 0, last = -1, next, complex_catch = 0, with_start = 0;

    *flags = 0x0;

    while (start < end) {

        /* Find next opcode. */
        next = start + 1;
        if (op_table[the_opcodes[start]].arg1)
            next++;
        if (op_table[the_opcodes[start]].arg2)
            next++;

        /* Check for statement opcodes. */
        switch (the_opcodes[start]) {

        case POP:
        case BREAK:
        case CONTINUE:
        case RETURN:
        case RETURN_EXPR:
            /* Count one line. */
            count++;
            if (last != -1)
                *flags |= COMPLEX_FLAG;
            *flags &= ~SPANNING_IF_FLAG;
            last = the_opcodes[start];
            break;

        case COMMENT:
            /* If last is a comment or this is the beginning of a block,
             * increment once; otherwise, increment twice. */
            count += ((last == -1 || last == COMMENT) ? 1 : 2);
            if (last != -1)
                *flags |= COMPLEX_FLAG;
            *flags &= ~SPANNING_IF_FLAG;
            last = COMMENT;
            break;

        case IF_ELSE:
            {
                Int body_end;
                unsigned if_flags = 0x0, else_flags = 0x0;

                /* Count "if (expr)" line and consider if body. */
                count++;
                body_end = the_opcodes[start + 1] - 2;
                start = next;

                if (end < body_end) {
                    /* End is inside if body. */
                    return count + count_lines(start, end, &if_flags);
                } else {
                    /* Count if body. */
                    count += count_lines(start, body_end, &if_flags);
                }

                /* Count "else" line and consider else body. */
                count++;
                start = body_end + 2;
                body_end = the_opcodes[start - 1];

                if (end < body_end) {
                    /* End is in else body.  Count the entire else body to see if
                     * it contains a spanning if; if so, adjust count backwards
                     * by one to account for the "if (expr)" being on the same
                     * line as our "else".  Then count the body up to the end. */
                    count_lines(start, body_end, &else_flags);
                    if (else_flags & SPANNING_IF_FLAG)
                        count--;
                    return count + count_lines(start, end, &else_flags);
                }

                /* Count else body. */
                count += count_lines(start, body_end, &else_flags);

                if (else_flags & SPANNING_IF_FLAG) {
                    /* Adjust the count back one, since the "if (expr)" line went
                     * on the same line as our "else".  Also, if the else body
                     * added a line for a closing brace, adjust back one more,
                     * since the responsibility for the closing brace falls on
                     * the outermost if-else. */
                    count--;
                    if (else_flags & COMPLEX_IF_FLAG)
                        count--;
                }

                /* Add a line for the closing brace if the if body was complex,
                 * if the else body was complex and not a spanning if, or if the
                 * else body was a spanning if and had complex subclauses.  Set
                 * COMPLEX_IF_FLAG if we do this. */
                if ((if_flags & COMPLEX_FLAG) ||
                    (else_flags & COMPLEX_FLAG &&
                     !(else_flags & SPANNING_IF_FLAG)) ||
                    (else_flags & SPANNING_IF_FLAG && else_flags & COMPLEX_IF_FLAG)) {
                    /* Boy, that was a messy condition. */
                    count++;
                    *flags |= COMPLEX_IF_FLAG;
                }

                /* Set other flags. */
                *flags |= COMPLEX_FLAG;
                if (last == -1)
                    *flags |= SPANNING_IF_FLAG;

                last = IF_ELSE;
                next = body_end;
                break;
            }

        case IF:
        case FOR_RANGE:
        case FOR_LIST:
        case WHILE:
        case LAST_CASE_VALUE:
        case LAST_CASE_RANGE:
            {
                Int opcode = the_opcodes[start], body_end;
                unsigned body_flags;

                /* Count header line and consider body. */
                count++;
                body_end = the_opcodes[start + 1];
                if (opcode == LAST_CASE_VALUE || opcode == LAST_CASE_RANGE)
                    body_end -= 2;
                start = next;

                if (end < body_end) {
                    /* End is in body. */
                    return count + count_lines(start, end, &body_flags);
                }

                /* Count body.  If it's complex, count line for closing brace. */
                count += count_lines(start, body_end, &body_flags);
                if (opcode != LAST_CASE_VALUE && opcode != LAST_CASE_RANGE) {
                    if (body_flags & COMPLEX_FLAG)
                        count++;
                }

                /* For if statements, set COMPLEX_IF_FLAG if body is complex, and
                 * set SPANNING_IF_FLAG if this is the first statement (it may be
                 * unset by a later statement). */
                if (opcode == IF) {
                    if (body_flags & COMPLEX_FLAG)
                        *flags |= COMPLEX_IF_FLAG;
                    if (last == -1)
                        *flags |= SPANNING_IF_FLAG;
                } else {
                    *flags &= ~SPANNING_IF_FLAG;
                }

                *flags |= COMPLEX_FLAG;
                last = opcode;
                next = body_end;
                break;
            }

        case SWITCH:
            {
                Int body_end;
                unsigned body_flags;

                /* Count switch header and consider body. */
                count++;
                body_end = the_opcodes[start + 1];
                start += 2;

                if (end < body_end) {
                    /* End is in switch body. */
                    return count + count_lines(start, end, &body_flags);
                }

                /* Count switch body recursively (so that default case knows
                 * where to stop).  If the default case was complex, count the
                 * closing brace for the default case. */
                count += count_lines(start, body_end, &body_flags);

                /* Count closing brace and set flags. */
                count++;
                *flags |= COMPLEX_FLAG;
                *flags &= ~SPANNING_IF_FLAG;
                last = SWITCH;
                next = body_end;
                break;
            }

        case DEFAULT:
            {
                unsigned body_flags;

                /* Switch bodies are counted recursively, so the end of the
                 * default case is end.  Count default header and body. */
                count += 1 + count_lines(next, end, &body_flags);
                next = end;
                break;
            }

        case CATCH:
            {
                Int body_end, body_lines;
                unsigned body_flags;

                /* Count catch line and consider body. */
                count++;
                body_end = the_opcodes[start + 1];
                start = next;

                if (end < body_end) {
                    /* end is inside catch body. */
                    return count + count_lines(start, end, &body_flags);
                }

                /* Count body and closing brace, if applicable */
                body_lines = count_lines(start, body_end, &body_flags);
                count += body_lines;
                if (body_lines > 1) {
                    count += 1;
                    complex_catch++;
                }

                /*
                   // BUGNOTE: this odd handling of with_start etc does not
                   // take into account nested catch statements, which will
                   // cause the line count to be off.  It does work for the
                   // non-nested catch, which is fine until I rewrite this.
                   // -B
                 */
                if (the_opcodes[body_end] == HANDLER_END) {
                    /* Skip dummy HANDLER_END instruction. */
                    last = CATCH;
                    next = body_end + 1;
                    with_start = 0;
                } else {
                    /* We don't know where the end of the handler is.  Rather
                     * than counting it recursively, fake it, and count the
                     * closing brace when we hit the HANDLER_END. */
                    last = -1;
                    next = body_end;

                    /* add in the line for } with { */
                    count++;

                    /* remember where we are */
                    with_start = count;
                }

                *flags &= ~SPANNING_IF_FLAG;
                *flags |= COMPLEX_FLAG;
                break;
            }

        case HANDLER_END:
            /* Count closing brace, and set last to CATCH to reflect the
             * enclosing catch statement. */
            if ((count - with_start) > 1 || complex_catch) {
                count++;
                complex_catch--;
                with_start = 0;
            }
            last = CATCH;
            break;
        }

        start = next;
    }

    return count;
}

cList *decompile(Method * method, Obj * object, Int increment, int fflags)
{
    Stmt_list *body;
    cList *output;
    cStr *str;
    Int i;
    char *s;

    /* Set globals so we don't have to pass method and object around. */
    the_object = object;
    the_method = method;
    the_opcodes = method->opcodes;
    the_increment = increment;
    format_flags = fflags;

    /* Prepare output list. */
    output = list_new(0);

    /* Add 'args' line if there are arguments. */
    if (method->num_args || method->rest != -1) {
        str = string_from_chars("arg ", 4);
        for (i = method->num_args - 1; i >= 0; i--) {
            s = ident_name(object_get_ident(object, method->argnames[i]));
            str = string_add_chars(str, s, strlen(s));
            if (i > 0 || method->rest != -1)
                str = string_add_chars(str, ", ", 2);
        }
        if (method->rest != -1) {
            str = string_addc(str, '@');
            s = ident_name(object_get_ident(object, method->rest));
            str = string_add_chars(str, s, strlen(s));
        }
        str = string_addc(str, ';');
        output = add_and_discard_string(output, str);
    }

    /* Add 'vars' line if there are variables. */
    if (method->num_vars) {
        str = string_from_chars("var ", 4);
        for (i = method->num_vars - 1; i >= 0; i--) {
            s = ident_name(object_get_ident(object, method->varnames[i]));
            str = string_add_chars(str, s, strlen(s));
            if (i > 0)
                str = string_add_chars(str, ", ", 2);
        }
        str = string_addc(str, ';');
        output = add_and_discard_string(output, str);
    }

    /* Add blank line if there were declarations and there is code. */
    if (output->len && method->num_opcodes)
        output = add_and_discard_string(output, string_new(0));

    /* Decompile opcodes Into parse tree. */
    body = decompile_stmt_list(0, method->num_opcodes);

    /* The first statement in the body (the last statement in the method) will
     * be a 'return 0' statement.  Ignore this. */
    body = body->next;

    /* Now unparse the body, add it to the list, and return the output. */
    output = unparse_stmt_list(output, body, 0);

    /* Free up all that memory we've been allocating and losing track of. */
    pfree(compiler_pile);

    return output;
}

static Stmt *decompile_stmt(Int * pos_ptr)
{
    Int pos = *pos_ptr, end;
    Expr_list *exprs;
    Stmt *stmt, *body;
    char *var, *comment;

    /* Most statement opcodes follow at least one expression.  Therefore, the
     * first thing we do is decompile expressions until we hit a statement
     * opcode. */
    exprs = decompile_expressions(&pos);

    /* Now look at the statement opcode. */
    switch (the_opcodes[pos]) {

    case COMMENT:
        /* COMMENT opcode follows no expressions. */
        comment = string_chars(the_object->strings[the_opcodes[pos + 1]].str);
        (*pos_ptr) = pos + 2;
        return comment_stmt(comment);

    case POP:
        /* POP opcode follows one expression. */
        (*pos_ptr) = pos + 1;
        return expr_stmt(exprs->expr);

    case IF:
        /* IF opcode follows one expression. */
        end = the_opcodes[pos + 1];
        body = decompile_body(pos + 2, end);
        stmt = if_stmt(exprs->expr, body);
        (*pos_ptr) = end;
        return stmt;

    case IF_ELSE:
        /* IF_ELSE opcode follows one expression.  First get the if part. */
        end = the_opcodes[pos + 1];
        body = decompile_body(pos + 2, end - 2);
        stmt = if_stmt(exprs->expr, body);

        /* Now get the else part. */
        pos = end;
        end = the_opcodes[pos - 1];
        body = decompile_body(pos, end);
        stmt = if_else_stmt(stmt, body);

        (*pos_ptr) = end;
        return stmt;

    case FOR_RANGE:
        /* FOR_RANGE opcode follows two expressions. */
        /* End stored in second opcode argument is after the END opcode, so the
           body ends at end - 1. */
        end = the_opcodes[pos + 1];
        var = varname(the_opcodes[pos + 2]);
        body = decompile_body(pos + 3, end - 2);
        (*pos_ptr) = end;
        return for_range_stmt(var, exprs->next->expr, exprs->expr, body);

    case FOR_LIST:
        /* FOR_LIST opcode follows two expressions; one is just a zero. */
        /* End stored in second opcode argument is after the END opcode, so the
         * body ends at end - 1. */
        end = the_opcodes[pos + 1];
        var = varname(the_opcodes[pos + 2]);
        body = decompile_body(pos + 3, end - 2);
        (*pos_ptr) = end;
        return for_list_stmt(var, exprs->next->expr, body);

    case WHILE:
        /* WHILE statement follows one expression. */
        /* End stored in second opcode argument is after the END opcode, so the
         * body ends at end - 1. */
        end = the_opcodes[pos + 1];
        body = decompile_body(pos + 3, end - 2);
        (*pos_ptr) = end;
        return while_stmt(exprs->expr, body);

    case SWITCH:
        {
            Case_list *cases;

            /* SWITCH statement follows one opcode. */
            end = the_opcodes[pos + 1];
            cases = decompile_cases(pos + 2, end);
            (*pos_ptr) = end;
            return switch_stmt(exprs->expr, cases);
        }

    case BREAK:
        /* BREAK opcode follows no expressions. */
        (*pos_ptr) = pos + 3;
        return break_stmt();

    case CONTINUE:
        /* CONTINUE opcode follows no expressions. */
        (*pos_ptr) = pos + 3;
        return continue_stmt();

    case RETURN:
        /* RETURN opcode follows no expressions. */
        (*pos_ptr) = pos + 1;
        return return_stmt();

    case RETURN_EXPR:
        /* RETURN_EXPR opcode follows one expression. */
        (*pos_ptr) = pos + 1;
        return return_expr_stmt(exprs->expr);

    case CATCH:
        {
            Id_list *errors;

            /* CATCH opcode follows no expressions. */
            errors = make_error_id_list(the_opcodes[pos + 2]);
            end = the_opcodes[pos + 1];
            body = decompile_body(pos + 3, end - 2);
            if (the_opcodes[end] == HANDLER_END)
                stmt = NULL;
            else
                stmt = decompile_until(&end, HANDLER_END);
            (*pos_ptr) = end + 1;
            return catch_stmt(errors, body, stmt);
        }

    default:
        return NULL;
    }
}

static Stmt_list *decompile_stmt_list(Int start, Int end)
{
    Stmt_list *stmts = NULL;

    while (start < end)
        stmts = stmt_list(decompile_stmt(&start), stmts);
    return stmts;
}

static Stmt *decompile_until(Int * start, Int marker)
{
    Stmt_list *stmts = NULL;

    while (the_opcodes[*start] != marker)
        stmts = stmt_list(decompile_stmt(start), stmts);
    return body_from_stmt_list(stmts);
}

static Stmt *decompile_body(Int start, Int end)
{
    return body_from_stmt_list(decompile_stmt_list(start, end));
}

static Stmt *body_from_stmt_list(Stmt_list * stmts)
{
    if (!stmts)
        return noop_stmt();
    else if (stmts->next)
        return compound_stmt(stmts);
    else
        return stmts->stmt;
}

static Case_list *decompile_cases(Int start, Int end)
{
    Case_list *cases = NULL;

    while (start < end)
        cases = case_list(decompile_case(&start, end), cases);

    /* If the default case has no action, forget about it. */
    if (!cases->case_entry->stmts)
        cases = cases->next;

    return cases;
}

static Case_entry *decompile_case(Int * pos_ptr, Int switch_end)
{
    Expr_list *values;
    Stmt_list *stmts;
    Int end;

    values = decompile_case_values(pos_ptr, &end);
    if (!values) {
        /* It's a default statement; decompile up to the switch end. */
        stmts = decompile_stmt_list(*pos_ptr, switch_end);
        *pos_ptr = switch_end;
    } else {
        /* Decompile up to the END_CASE instruction at the end of the body. */
        stmts = decompile_stmt_list(*pos_ptr, end - 2);
        *pos_ptr = end;
    }
    return case_entry(values, stmts);
}

static Expr_list *decompile_case_values(Int * pos_ptr, Int * end_ret)
{
    Expr_list *exprs, *values = NULL;
    Expr *range;
    Int pos = *pos_ptr;

    if (the_opcodes[pos] == DEFAULT) {
        (*pos_ptr) = pos + 1;
        return NULL;
    }

    /* Loop until we hit the last case opcode for this case. */
    forever {
        /* Get any expressions preceding the case opcode. */
        exprs = decompile_expressions(&pos);

        switch (the_opcodes[pos]) {

        case CASE_VALUE:
            pos += 2;
            values = expr_list(exprs->expr, values);
            break;

        case CASE_RANGE:
            pos += 2;
            range = range_expr(exprs->next->expr, exprs->expr);
            values = expr_list(range, values);
            break;

        case LAST_CASE_VALUE:
            *end_ret = the_opcodes[pos + 1];
            *pos_ptr = pos + 2;
            return expr_list(exprs->expr, values);

        case LAST_CASE_RANGE:
            *end_ret = the_opcodes[pos + 1];
            *pos_ptr = pos + 2;
            range = range_expr(exprs->next->expr, exprs->expr);
            return expr_list(range, values);
        }
    }
}

static Id_list *make_error_id_list(Int which)
{
    Error_list *elist;
    Id_list *idl = NULL;
    Int i;

    /* A -1 indicates a 'catch all' expression; return a null ID list. */
    if (which == -1)
        return NULL;

    /* Retrieve the error list from the method by its index. */
    elist = &the_method->error_lists[which];

    /* Traverse the error list, building an id list from the id numbers.
     * Since the list gets flipped internally, this list should be read
     * forwards later on. */

    for (i = 0; i < elist->num_errors; i++)
        idl = id_list(ident_name(elist->error_ids[i]), idl);

    return idl;
}

/* Decompile an unbounded list of expressions. */
static Expr_list *decompile_expressions(Int * pos_ptr)
{
    return decompile_expressions_bounded(pos_ptr, -1);
}

static Expr *decompile_scatter(Int * pos_ptr)
{
    Int pos = *pos_ptr, end;
    Expr_list *args = NULL;
    char *s;
    Int is_splice = FALSE;

    while (1) {
        switch (the_opcodes[pos]) {
        case SET_LOCAL:
            s = varname(the_opcodes[pos + 1]);
            args = expr_list(var_expr(s), args);
            pos += 2;
            break;

        case SET_OBJ_VAR:
            s = ident_name(object_get_ident(the_object, the_opcodes[pos + 1]));
            args = expr_list(var_expr(s), args);
            pos += 2;
            break;

        case OPTIONAL_ASSIGN:
            {
                Expr_list *rhs;

                end = the_opcodes[pos + 1];
                pos += 2;
                rhs = decompile_expressions_bounded(&pos, end);
                pos = end + 1;  /* skip OPTIONAL_END */
                args = expr_list(opt_expr(rhs->expr->u.assign.lval->u.name, rhs->expr->u.assign.value), args);
                break;
            }
        case SCATTER_START:
            pos++;
            args = expr_list(decompile_scatter(&pos), args);
            break;

        case SPLICE:
            pos++;
            is_splice = TRUE;
            break;

        case SCATTER_END:
            if (is_splice)
                args->expr = splice_expr(args->expr);
            *pos_ptr = pos + 1;
            return list_expr(args);
        }
    }
}


/* This function constructs the list of expressions that would result from
 * interpreting the opcodes starting at (*pos_ptr).  We stop at end, at a
 * statement token, or at a token which pops an argument list off the stack. */
static Expr_list *decompile_expressions_bounded(Int * pos_ptr, Int expr_end)
{
    Int pos = *pos_ptr, end;
    Expr_list *stack = NULL;
    char *s;

    while (expr_end == -1 || pos < expr_end) {
        switch (the_opcodes[pos]) {

        case ZERO:
            stack = expr_list(integer_expr(0), stack);
            pos++;
            break;

        case ONE:
            stack = expr_list(integer_expr(1), stack);
            pos++;
            break;

        case INTEGER:
            stack = expr_list(integer_expr(the_opcodes[pos + 1]), stack);
            pos += 2;
            break;

        case FLOAT:
            stack = expr_list(float_expr(*((Float *) (&the_opcodes[pos + 1]))), stack);
            pos += 2;
            break;

        case STRING:
            s = string_chars(the_object->strings[the_opcodes[pos + 1]].str);
            stack = expr_list(string_expr(s), stack);
            pos += 2;
            break;

        case OBJNUM:
            stack = expr_list(objnum_expr(the_opcodes[pos + 1]), stack);
            pos += 2;
            break;

        case SYMBOL:
            s = ident_name(object_get_ident(the_object, the_opcodes[pos + 1]));
            stack = expr_list(symbol_expr(s), stack);
            pos += 2;
            break;

        case T_ERROR:
            s = ident_name(object_get_ident(the_object, the_opcodes[pos + 1]));
            stack = expr_list(error_expr(s), stack);
            pos += 2;
            break;

        case OBJNAME:
            s = ident_name(object_get_ident(the_object, the_opcodes[pos + 1]));
            stack = expr_list(objname_expr(s), stack);
            pos += 2;
            break;

        case GET_LOCAL:
            s = varname(the_opcodes[pos + 1]);
            stack = expr_list(var_expr(s), stack);
            pos += 2;
            break;

        case GET_OBJ_VAR:
            s = ident_name(object_get_ident(the_object, the_opcodes[pos + 1]));
            stack = expr_list(var_expr(s), stack);
            pos += 2;
            break;

        case P_INCREMENT:
        case P_DECREMENT:
        case INCREMENT:
        case DECREMENT:
            if (the_opcodes[pos + 1] == SET_OBJ_VAR)
                s = ident_name(object_get_ident(the_object, the_opcodes[pos + 2]));
            else
                s = varname(the_opcodes[pos + 2]);
            stack->expr = indecr_expr(the_opcodes[pos], s);
            pos += 3;
            break;

        case MULT_EQ:
        case DIV_EQ:
        case PLUS_EQ:
        case MINUS_EQ:
            /* ignore the current expr, it is just a GET_VAR */
            stack = stack->next;
            if (the_opcodes[pos + 1] == SET_OBJ_VAR)
                s = ident_name(object_get_ident(the_object, the_opcodes[pos + 2]));
            else
                s = varname(the_opcodes[pos + 2]);
            stack->expr = doeq_expr(the_opcodes[pos], s, stack->expr);
            pos += 3;
            break;

        case SET_LOCAL:
            /* SET_LOCAL opcode follows one expression. */
            s = varname(the_opcodes[pos + 1]);
            stack->expr = assign_expr(var_expr(s), stack->expr);
            pos += 2;
            break;

        case SET_OBJ_VAR:
            /* SET_OBJ_VAR opcode follows one expression. */
            s = ident_name(object_get_ident(the_object, the_opcodes[pos + 1]));
            stack->expr = assign_expr(var_expr(s), stack->expr);
            pos += 2;
            break;

        case OPTIONAL_ASSIGN:
            {
                /* This is optional assignment. It can't be in the scatter,
                   since it's handled from there. */

                Expr_list *rhs;

                end = the_opcodes[pos + 1];
                pos += 2;
                rhs = decompile_expressions_bounded(&pos, end);
                pos = end;
                stack =
                    expr_list(opt_expr
                              (rhs->expr->u.assign.lval->u.name,
                               rhs->expr->u.assign.value), stack->next /*skip the get_var */ );
                break;
            }

        case SCATTER_START:
            pos++;
            stack->expr = assign_expr(decompile_scatter(&pos), stack->expr);
            break;

        case START_ARGS:
            {
                Expr_list *args;

                pos++;
                args = decompile_expressions(&pos);
                switch (the_opcodes[pos]) {

                case PASS:
                    stack = expr_list(pass_expr(args), stack);
                    pos++;
                    break;

                case CALL_METHOD:
                    s = ident_name(object_get_ident(the_object, the_opcodes[pos + 1]));
                    stack->expr = message_expr(stack->expr, s, args);
                    pos += 2;
                    break;

                case EXPR_CALL_METHOD:
                    stack->next->expr = expr_message_expr(stack->next->expr, stack->expr, args);
                    stack = stack->next;
                    pos++;
                    break;

                case LIST:
                    stack = expr_list(list_expr(args), stack);
                    pos++;
                    break;

                case DICT:
                    stack = expr_list(dict_expr(args), stack);
                    pos++;
                    break;

                case BUFFER:
                    stack = expr_list(buffer_expr(args), stack);
                    pos++;
                    break;

                default:
                    s = op_table[the_opcodes[pos]].name;
                    if (!s)
                        panic("Invalid expression opcode.");
                    stack = expr_list(function_call_expr(s, args), stack);
                    pos++;
                    break;
                }
                break;
            }

        case FROB:
            stack->next->expr = frob_expr(stack->next->expr, stack->expr, NULL);
            stack = stack->next;
            pos++;
            break;

        case OP_HANDLED_FROB:
            stack->next->next->expr = frob_expr(stack->next->next->expr, stack->next->expr, stack->expr);
            stack = stack->next->next;
            pos++;
            break;

        case INDEX:
            stack->next->expr = index_expr(stack->next->expr, stack->expr);
            stack = stack->next;
            pos++;
            break;

        case AND:
            {
                Expr_list *rhs;

                end = the_opcodes[pos + 1];
                pos += 2;
                rhs = decompile_expressions_bounded(&pos, end);
                stack->expr = and_expr(stack->expr, rhs->expr);
                pos = end;
                break;
            }

        case OR:
            {
                Expr_list *rhs;

                end = the_opcodes[pos + 1];
                pos += 2;
                rhs = decompile_expressions_bounded(&pos, end);
                stack->expr = or_expr(stack->expr, rhs->expr);
                pos = end;
                break;
            }

        case OP_MAPHASH:
        case OP_FILTER:
        case OP_FIND:
        case OP_MAP:
            {
                Expr_list *job;
                Int etype = the_opcodes[pos];

                end = the_opcodes[pos + 1];
                s = varname(the_opcodes[pos + 2]);
                pos += 3;
                job = decompile_expressions_bounded(&pos, end - 2);
                pos = end;
                /* delete the three ZEROs */
                stack = stack->next->next->next;
                stack->expr = map_expr(stack->expr, s, job->expr, etype);
                break;
            }

        case OP_MAPHASH_RANGE:
        case OP_FILTER_RANGE:
        case OP_FIND_RANGE:
        case OP_MAP_RANGE:
            {
                Expr_list *job;
                Expr *start_expr, *end_expr;
                Int etype = the_opcodes[pos];

                end = the_opcodes[pos + 1];
                s = varname(the_opcodes[pos + 2]);
                pos += 3;
                job = decompile_expressions_bounded(&pos, end - 2);
                pos = end;
                /* delete the ZEROs */
                stack = stack->next->next;
                start_expr = stack->next->expr;
                end_expr = stack->expr;
                stack = stack->next;
                stack->expr = map_range_expr(start_expr, end_expr, s, job->expr, etype);
                break;
            }

        case CONDITIONAL:
            {
                Expr_list *true, *false;

                /* The end stored in opcodes[pos + 1] is after the ELSE
                 * instruction. */
                /* Get true expression. */
                end = the_opcodes[pos + 1];
                pos += 2;
                true = decompile_expressions_bounded(&pos, end - 2);

                /* Get false expression. */
                pos = end;
                end = the_opcodes[pos - 1];
                false = decompile_expressions_bounded(&pos, end);

                stack->expr = cond_expr(stack->expr, true->expr, false->expr);
                pos = end;
                break;
            }

        case CRITICAL:
            /* Ignore this, and look for CRITICAL_END instead. */
            pos += 2;
            break;

        case CRITICAL_END:
            stack->expr = critical_expr(stack->expr);
            pos++;
            break;

        case PROPAGATE:
            /* Ignore this, and look for PROPAGATE_END instead. */
            pos += 2;
            break;

        case PROPAGATE_END:
            stack->expr = propagate_expr(stack->expr);
            pos++;
            break;

        case '!':
        case NEG:
            stack->expr = unary_expr(the_opcodes[pos], stack->expr);
            pos++;
            break;

        case '*':
        case '/':
        case '%':
        case '+':
        case '-':
        case EQ:
        case NE:
        case '>':
        case GE:
        case '<':
        case LE:
        case OP_IN:
            stack->next->expr = binary_expr(the_opcodes[pos], stack->next->expr, stack->expr);
            stack = stack->next;
            pos++;
            break;

        case SPLICE_ADD:
            {
                Expr_list **elistp = &stack->expr->u.args;

                /* Find the end of the list expression's argument list. */
                while (*elistp)
                    elistp = &(*elistp)->next;

                /* Append the spliced expression to the end. */
                *elistp = expr_list(splice_expr(stack->next->expr), NULL);

                /* Shift the new list expression back one on the stack. */
                stack->next->expr = stack->expr;
                stack = stack->next;
                pos++;
                break;
            }

        case SPLICE:
            stack->expr = splice_expr(stack->expr);
            pos++;
            break;

        default:
            /* We've hit a statement token, or a token that takes an argument
             * list.  Stop. */
            *pos_ptr = pos;
            return stack;
        }
    }

    /* We hit the end.  Stop. */
    *pos_ptr = pos;
    return stack;
}

/* Write a statement list onto the output list, backwards. */
static cList *unparse_stmt_list(cList * output, Stmt_list * stmts, Int indent)
{
    Stmt *last;

    if (stmts) {
        last = stmts->next ? stmts->next->stmt : NULL;
        output = unparse_stmt_list(output, stmts->next, indent);
        return unparse_stmt(output, stmts->stmt, indent, last);
    } else {
        return output;
    }
}

static cList *unparse_stmt(cList * output, Stmt * stmt, Int indent, Stmt * last)
{
    cStr *str;

    switch (stmt->type) {

        /* This switch doesn't include no-op statements or RETURN statements,
         * since we never see them here. */

    case NOOP:
        str = string_of_char(' ', indent);
        str = string_addc(str, ';');
        return add_and_discard_string(output, str);

    case COMMENT:
        /* Add a blank line if there is a previous line and it is not a
         * comment. */
        if (last && last->type != COMMENT)
            output = add_and_discard_string(output, string_new(0));
        str = string_of_char(' ', indent);
        str = string_add_chars(str, "//", 2);
        str = string_add_chars(str, stmt->u.comment, strlen(stmt->u.comment));
        return add_and_discard_string(output, str);

    case EXPR:
        str = string_of_char(' ', indent);
        str = unparse_expr(str, stmt->u.expr, NOPAREN_ASSIGN);
        str = string_addc(str, ';');
        return add_and_discard_string(output, str);

    case COMPOUND:
        /* The calling routine handles the positioning of the braces. */
        return unparse_stmt_list(output, stmt->u.stmt_list, indent);

    case IF:
        str = string_of_char(' ', indent);
        str = string_add_chars(str, "if (", 4);
        str = unparse_expr(str, stmt->u.if_.cond, PAREN_ASSIGN);
        str = string_addc(str, ')');
        return unparse_body(output, stmt->u.if_.true, str, indent);

    case IF_ELSE:
        {
            Int complex;

            /* This is the most complicated unparsing job, because we need to
             * decide to format both the if-true and if-false statements, and we
             * also want to correctly format chains of "if ... else if ... ". */

            /* Determine if either of the statements is complex (will take more
             * than one line).  Do this just once at the beginning. */
            complex = is_complex_if_else_stmt(stmt);

            /* Start with a string of spaces one less than the indent. */
            str = string_of_char(' ', indent);

            /* Execute this for stmt as well as any else-ifs. */
            while (stmt->type == IF_ELSE) {
                str = string_add_chars(str, "if (", 4);
                str = unparse_expr(str, stmt->u.if_.cond, PAREN_ASSIGN);
                str = string_addc(str, ')');

                if (stmt->u.if_.cond->type == NOOP) {
                    str = string_addc(str, ';');
                    output = add_and_discard_string(output, str);
                    str = string_of_char(' ', indent);
                } else if (complex) {
                    str = string_add_chars(str, " {", 2);
                    output = add_and_discard_string(output, str);
                    output = unparse_stmt(output, stmt->u.if_.true, indent + the_increment, NULL);
                    str = string_of_char(' ', indent);
                    str = string_add_chars(str, "} ", 2);
                } else {
                    output = add_and_discard_string(output, str);
                    output = unparse_stmt(output, stmt->u.if_.true, indent + the_increment, NULL);
                    str = string_of_char(' ', indent);
                }

                str = string_add_chars(str, "else ", 5);

                /* Set stmt to stmt->u.if_.false, so that we will continue the
                 * loop if the false statement is an if statement. */
                stmt = stmt->u.if_.false;
            }

            if (stmt->type == IF) {
                str = string_add_chars(str, "if (", 4);
                str = unparse_expr(str, stmt->u.if_.cond, PAREN_ASSIGN);
                str = string_add_chars(str, ") ", 2);
                stmt = stmt->u.if_.true;
            }

            /* Now unparse the final statement, which is in stmt. */
            if (stmt->type == NOOP) {
                /* Replace the trailing space with a semicolon. */
                str = string_truncate(str, string_length(str) - 1);
                str = string_addc(str, ';');
                return add_and_discard_string(output, str);
            } else if (complex) {
                str = string_addc(str, '{');
                output = add_and_discard_string(output, str);
                output = unparse_stmt(output, stmt, indent + the_increment, NULL);
                str = string_of_char(' ', indent);
                str = string_addc(str, '}');
                return add_and_discard_string(output, str);
            } else {
                /* Just eliminate the trailing space. */
                str = string_truncate(str, string_length(str) - 1);
                output = add_and_discard_string(output, str);
                return unparse_stmt(output, stmt, indent + the_increment, NULL);
            }
        }

    case FOR_RANGE:
        {
            char *s;

            s = stmt->u.for_range.var;
            str = string_of_char(' ', indent);
            str = string_add_chars(str, "for ", 4);
            str = string_add_chars(str, s, strlen(s));
            str = string_add_chars(str, " in [", 5);
            str = unparse_expr(str, stmt->u.for_range.lower, PAREN_ASSIGN);
            str = string_add_chars(str, " .. ", 4);
            str = unparse_expr(str, stmt->u.for_range.upper, PAREN_ASSIGN);
            str = string_addc(str, ']');
            return unparse_body(output, stmt->u.for_range.body, str, indent);
        }

    case FOR_LIST:
        {
            char *s;

            s = stmt->u.for_list.var;
            str = string_of_char(' ', indent);
            str = string_add_chars(str, "for ", 4);
            str = string_add_chars(str, s, strlen(s));
            str = string_add_chars(str, " in (", 5);
            str = unparse_expr(str, stmt->u.for_list.list, PAREN_ASSIGN);
            str = string_addc(str, ')');
            return unparse_body(output, stmt->u.for_list.body, str, indent);
        }

    case WHILE:
        str = string_of_char(' ', indent);
        str = string_add_chars(str, "while (", 7);
        str = unparse_expr(str, stmt->u.while_.cond, PAREN_ASSIGN);
        str = string_addc(str, ')');
        return unparse_body(output, stmt->u.while_.body, str, indent);

    case SWITCH:
        str = string_of_char(' ', indent);
        str = string_add_chars(str, "switch (", 8);
        str = unparse_expr(str, stmt->u.switch_.expr, PAREN_ASSIGN);
        str = string_add_chars(str, ") {", 3);
        output = add_and_discard_string(output, str);
        output = unparse_cases(output, stmt->u.switch_.cases, indent + the_increment);
        str = string_of_char(' ', indent);
        str = string_addc(str, '}');
        return add_and_discard_string(output, str);

    case BREAK:
        str = string_of_char(' ', indent);
        str = string_add_chars(str, "break;", 6);
        return add_and_discard_string(output, str);

    case CONTINUE:
        str = string_of_char(' ', indent);
        str = string_add_chars(str, "continue;", 9);
        return add_and_discard_string(output, str);

    case RETURN:
        str = string_of_char(' ', indent);
        str = string_add_chars(str, "return;", 7);
        return add_and_discard_string(output, str);

    case RETURN_EXPR:
        str = string_of_char(' ', indent);
        str = string_add_chars(str, "return ", 7);
        str = unparse_expr(str, stmt->u.expr, PAREN_ASSIGN);
        str = string_addc(str, ';');
        return add_and_discard_string(output, str);

    case CATCH:
        {
            Id_list *errors;
            Int complex = 0;

            if ((stmt->u.ccatch.body != NULL &&
                 is_complex_type(stmt->u.ccatch.body->type)) ||
                (stmt->u.ccatch.handler != NULL && is_complex_type(stmt->u.ccatch.handler->type)))
                complex = 1;

            str = string_of_char(' ', indent);
            str = string_add_chars(str, "catch ", 6);
            errors = stmt->u.ccatch.errors;
            if (errors) {
                for (; errors; errors = errors->next) {
                    str = string_addc(str, '~');
                    str = string_add_chars(str, errors->ident, strlen(errors->ident));
                    if (errors->next)
                        str = string_add_chars(str, ", ", 2);
                }
            } else {
                str = string_add_chars(str, "any", 3);
            }
            if (complex)
                str = string_add_chars(str, " {", 2);
            output = add_and_discard_string(output, str);
            output = unparse_stmt(output, stmt->u.ccatch.body, indent + the_increment, NULL);
            if (stmt->u.ccatch.handler) {
                str = string_of_char(' ', indent);
                if (complex)
                    str = string_add_chars(str, "} with {", 8);
                else
                    str = string_add_chars(str, "with", 4);
                output = add_and_discard_string(output, str);
                output = unparse_stmt(output, stmt->u.ccatch.handler, indent + the_increment, NULL);
            }
            if (complex) {
                str = string_of_char(' ', indent);
                str = string_addc(str, '}');
                output = add_and_discard_string(output, str);
            }
            return output;
        }

    default:
        return output;
    }
}

/* Returns nonzero if the if-else statment stmt is complex--that is, if we
 * should display its true and false clauses in braces.  If the false clause
 * is an IF_ELSE or an IF statement, then we will be displaying the false
 * clause's if statment on the same line as our own else statement, so we must
 * check to see if the false clause itself is complex.  Apart from that, it's
 * just a matter of testing if either the true or false clause is complex. */
static Int is_complex_if_else_stmt(Stmt * stmt)
{
    if (is_complex_type(stmt->u.if_.true->type))
        return 1;
    else if (stmt->u.if_.false->type == IF_ELSE)
        return is_complex_if_else_stmt(stmt->u.if_.false);
    else if (stmt->u.if_.false->type == IF)
        return is_complex_type(stmt->u.if_.false->u.if_.true->type);
    else
        return is_complex_type(stmt->u.if_.false->type);
}

static Int is_complex_type(Int type)
{
    return FULL_BRACES() ||
        (type != NOOP && type != EXPR && type != ASSIGN && type != BREAK &&
         type != CONTINUE && type != RETURN && type != RETURN_EXPR);
}

static cList *unparse_body(cList * output, Stmt * body, cStr * str, Int indent)
{
    if (is_complex_type(body->type)) {
        str = string_add_chars(str, " {", 2);
        output = add_and_discard_string(output, str);
        output = unparse_stmt(output, body, indent + the_increment, NULL);
        str = string_of_char(' ', indent);
        str = string_addc(str, '}');
        return add_and_discard_string(output, str);
    } else {
        output = add_and_discard_string(output, str);
        return unparse_stmt(output, body, indent + the_increment, NULL);
    }
}

static cList *unparse_cases(cList * output, Case_list * cases, Int indent)
{
    if (cases) {
        output = unparse_cases(output, cases->next, indent);
        output = unparse_case(output, cases->case_entry, indent);
    }
    return output;
}

static cList *unparse_case(cList * output, Case_entry * case_entry, Int indent)
{
    cStr *str;

    str = string_of_char(' ', indent);
    if (!case_entry->values) {
        str = string_add_chars(str, "default:", 8);
    } else {
        str = string_add_chars(str, "case ", 5);
        str = unparse_args(str, case_entry->values);
        str = string_addc(str, ':');
    }
    output = add_and_discard_string(output, str);
    return unparse_stmt_list(output, case_entry->stmts, indent + the_increment);
}

static cStr *unparse_expr(cStr * str, Expr * expr, Int paren)
{
    char *s;

    switch (expr->type) {

    case INTEGER:
        {
            Number_buf nbuf;

            s = long_to_ascii(expr->u.num, nbuf);
            return string_add_chars(str, s, strlen(s));
        }

    case FLOAT:
        {
            Number_buf nbuf;

            s = float_to_ascii(expr->u.fnum, nbuf);
            return string_add_chars(str, s, strlen(s));
        }

    case STRING:
        return string_add_unparsed(str, expr->u.str, strlen(expr->u.str));

    case OBJNUM:
        {
            Number_buf nbuf;

            str = string_addc(str, '#');
            s = long_to_ascii(expr->u.objnum, nbuf);
            return string_add_chars(str, s, strlen(s));
        }

    case SYMBOL:
        s = expr->u.symbol;
        str = string_addc(str, '\'');
        if (is_valid_ident(expr->u.symbol))
            return string_add_chars(str, s, strlen(s));
        else
            return string_add_unparsed(str, s, strlen(s));

    case T_ERROR:
        s = expr->u.error;
        str = string_addc(str, '~');
        if (is_valid_ident(expr->u.error))
            return string_add_chars(str, s, strlen(s));
        else
            return string_add_unparsed(str, s, strlen(s));

    case OBJNAME:
        s = expr->u.name;
        str = string_addc(str, '$');
        if (is_valid_ident(expr->u.name))
            return string_add_chars(str, s, strlen(s));
        else
            return string_add_unparsed(str, s, strlen(s));

    case VAR:
        return string_add_chars(str, expr->u.name, strlen(expr->u.name));

    case ASSIGN:
        if (paren)
            str = string_addc(str, '(');
        str = unparse_expr(str, expr->u.assign.lval, PAREN_ASSIGN);
        str = string_add_chars(str, " = ", 3);
        str = unparse_expr(str, expr->u.assign.value, PAREN_ASSIGN);
        if (paren)
            str = string_addc(str, ')');
        return str;

    case OPTIONAL_ASSIGN:
        s = expr->u.optassign.var;
        if (paren)
            str = string_addc(str, '(');
        str = string_add_chars(str, s, strlen(s));
        str = string_add_chars(str, " ?= ", 4);
        str = unparse_expr(str, expr->u.optassign.value, PAREN_ASSIGN);
        if (paren)
            str = string_addc(str, ')');
        return str;

    case INDECR:
        s = expr->u.doeq.var;

        /* redundant code */
        switch (expr->u.doeq.opcode) {
        case P_INCREMENT:
            str = string_add_chars(str, "++", 2);
            str = string_add_chars(str, s, strlen(s));
            break;
        case P_DECREMENT:
            str = string_add_chars(str, "--", 2);
            str = string_add_chars(str, s, strlen(s));
            break;
        case INCREMENT:
            str = string_add_chars(str, s, strlen(s));
            str = string_add_chars(str, "++", 2);
            break;
        case DECREMENT:
            str = string_add_chars(str, s, strlen(s));
            str = string_add_chars(str, "--", 2);
            break;
        }
        return str;

    case DOEQ:
        s = expr->u.doeq.var;
        if (paren)
            str = string_addc(str, '(');
        str = string_add_chars(str, s, strlen(s));
        switch (expr->u.doeq.opcode) {
        case MULT_EQ:
            str = string_add_chars(str, " *= ", 4);
            break;
        case DIV_EQ:
            str = string_add_chars(str, " /= ", 4);
            break;
        case PLUS_EQ:
            str = string_add_chars(str, " += ", 4);
            break;
        case MINUS_EQ:
            str = string_add_chars(str, " -= ", 4);
            break;
        }
        str = unparse_expr(str, expr->u.doeq.value, PAREN_ASSIGN);
        if (paren)
            str = string_addc(str, ')');
        return str;

    case FUNCTION_CALL:
        s = expr->u.function.name;
        str = string_add_chars(str, s, strlen(s));
        str = string_addc(str, '(');
        str = unparse_args(str, expr->u.function.args);
        return string_addc(str, ')');

    case PASS:
        str = string_add_chars(str, "pass(", 5);
        str = unparse_args(str, expr->u.args);
        return string_addc(str, ')');

    case CALL_METHOD:
        s = expr->u.message.name;

        /* Only include target if it's not this(). */
        if (expr->u.message.to->type == INTEGER)
            str = string_addc(str, '(');
        if (!is_this(expr->u.message.to))
            str = unparse_expr_prec(str, expr->u.message.to, CALL_METHOD, 0);
        if (expr->u.message.to->type == INTEGER)
            str = string_addc(str, ')');

        str = string_addc(str, '.');
        str = string_add_chars(str, s, strlen(s));
        str = string_addc(str, '(');
        str = unparse_args(str, expr->u.message.args);
        str = string_addc(str, ')');
        return str;

    case EXPR_CALL_METHOD:
        /* Only include target if it's not this(). */

        if (expr->u.message.to->type == INTEGER)
            str = string_addc(str, '(');
        if (!is_this(expr->u.expr_message.to))
            str = unparse_expr_prec(str, expr->u.message.to, CALL_METHOD, 0);
        if (expr->u.message.to->type == INTEGER)
            str = string_addc(str, ')');

        str = string_add_chars(str, ".(", 2);
        str = unparse_expr(str, expr->u.expr_message.message, PAREN_ASSIGN);
        str = string_addc(str, ')');
        str = string_addc(str, '(');
        str = unparse_args(str, expr->u.expr_message.args);
        return string_addc(str, ')');

    case LIST:
        str = string_addc(str, '[');
        str = unparse_args(str, expr->u.args);
        return string_addc(str, ']');

    case DICT:
        str = string_add_chars(str, "#[", 2);
        str = unparse_args(str, expr->u.args);
        return string_addc(str, ']');

    case BUFFER:
        str = string_add_chars(str, "`[", 2);
        str = unparse_args(str, expr->u.args);
        return string_addc(str, ']');

    case FROB:
        /* $#$NOTE: frobs shouldn't have to have ()'s around them */
        str = string_add_chars(str, "(<", 2);
        str = unparse_complex_expr(str, expr->u.frob.cclass, FROB, 0);
        str = string_add_chars(str, ", ", 2);
        str = unparse_complex_expr(str, expr->u.frob.rep, FROB, 0);
        if (expr->u.frob.handler) {
            str = string_add_chars(str, ", ", 2);
            str = unparse_complex_expr(str, expr->u.frob.handler, FROB, 0);
        }
        return string_add_chars(str, ">)", 2);

    case INDEX:
        str = unparse_expr_prec(str, expr->u.index.list, INDEX, 0);
        str = string_addc(str, '[');
        str = unparse_expr(str, expr->u.index.offset, PAREN_ASSIGN);
        return string_addc(str, ']');

    case UNARY:
        {
            Int opcode = expr->u.unary.opcode;

            str = string_addc(str, (opcode == NEG) ? '-' : opcode);
            return unparse_expr_prec(str, expr->u.unary.expr, opcode, 0);
        }

    case BINARY:
        {
            Int opcode = expr->u.binary.opcode;

            s = binary_token(opcode);
            str = unparse_expr_prec(str, expr->u.binary.left, opcode, 0);
            str = string_addc(str, ' ');
            str = string_add_chars(str, s, strlen(s));
            str = string_addc(str, ' ');
            return unparse_expr_prec(str, expr->u.binary.right, opcode, 1);
        }

    case AND:
        str = unparse_expr_prec(str, expr->u.and.left, AND, 1);
        str = string_add_chars(str, " && ", 4);
        return unparse_expr_prec(str, expr->u.and.right, AND, 0);

    case OR:
        str = unparse_expr_prec(str, expr->u.or.left, OR, 1);
        str = string_add_chars(str, " || ", 4);
        return unparse_expr_prec(str, expr->u.or.right, OR, 0);

    case OP_MAPHASH:
    case OP_FILTER:
    case OP_FIND:
    case OP_MAP:
        {
            char *s1, *s2;

            switch (expr->type) {
            case OP_MAP:
                s1 = "map ";
                s2 = " to ";
                break;
            case OP_FIND:
                s1 = "find ";
                s2 = " where ";
                break;
            case OP_FILTER:
                s1 = "filter ";
                s2 = " where ";
                break;
            default:
                s1 = "hash ";
                s2 = " to ";
            }
            s = expr->u.map.var;
            str = string_add_chars(str, s1, strlen(s1));
            str = string_add_chars(str, s, strlen(s));
            str = string_add_chars(str, " in ", 4);
            str = string_addc(str, '(');
            str = unparse_expr(str, expr->u.map.src, PAREN_ASSIGN);
            str = string_addc(str, ')');
            str = string_add_chars(str, s2, strlen(s2));
            str = string_addc(str, '(');
            str = unparse_expr(str, expr->u.map.job, PAREN_ASSIGN);
            str = string_addc(str, ')');
            return str;
        }

    case OP_MAPHASH_RANGE:
    case OP_FILTER_RANGE:
    case OP_FIND_RANGE:
    case OP_MAP_RANGE:
        {
            char *s1, *s2;

            switch (expr->type) {
            case OP_MAP_RANGE:
                s1 = "map ";
                s2 = " to ";
                break;
            case OP_FIND_RANGE:
                s1 = "find ";
                s2 = " where ";
                break;
            case OP_FILTER_RANGE:
                s1 = "filter ";
                s2 = " where ";
                break;
            default:
                s1 = "hash ";
                s2 = " to ";
            }
            s = expr->u.maprange.var;
            str = string_add_chars(str, s1, strlen(s1));
            str = string_add_chars(str, s, strlen(s));
            str = string_add_chars(str, " in ", 4);
            str = string_addc(str, '[');
            str = unparse_expr(str, expr->u.maprange.start, PAREN_ASSIGN);
            str = string_add_chars(str, " .. ", 4);
            str = unparse_expr(str, expr->u.maprange.end, PAREN_ASSIGN);
            str = string_addc(str, ']');
            str = string_add_chars(str, s2, strlen(s2));
            str = string_addc(str, '(');
            str = unparse_expr(str, expr->u.maprange.job, PAREN_ASSIGN);
            str = string_addc(str, ')');
            return str;
        }

    case CONDITIONAL:
        str = unparse_expr_prec(str, expr->u.cond.cond, CONDITIONAL, 1);
        str = string_add_chars(str, " ? ", 3);
        str = unparse_expr_prec(str, expr->u.cond.true, CONDITIONAL, 1);
        str = string_add_chars(str, " : ", 3);
        str = unparse_expr_prec(str, expr->u.cond.false, CONDITIONAL, 1);
        return str;

    case CRITICAL:
        str = string_add_chars(str, "(| ", 3);
        str = unparse_expr(str, expr->u.expr, PAREN_ASSIGN);
        return string_add_chars(str, " |)", 3);

    case PROPAGATE:
        str = string_add_chars(str, "(> ", 3);
        str = unparse_expr(str, expr->u.expr, PAREN_ASSIGN);
        return string_add_chars(str, " <)", 3);

    case SPLICE:
        str = string_addc(str, '@');
        if (expr->u.expr->type == BINARY) {
            /* Add parens around binary expressions for readability. */
            str = string_addc(str, '(');
            str = unparse_expr(str, expr->u.expr, PAREN_ASSIGN);
            return string_addc(str, ')');
        } else {
            return unparse_expr(str, expr->u.expr, PAREN_ASSIGN);
        }

    case RANGE:
        str = unparse_expr(str, expr->u.range.lower, PAREN_ASSIGN);
        str = string_add_chars(str, " .. ", 4);
        return unparse_expr(str, expr->u.range.upper, PAREN_ASSIGN);

    default:
        return str;
    }
}

static Int is_this(Expr * expr)
{
    return (expr->type == FUNCTION_CALL && !expr->u.function.args && strcmp(expr->u.function.name, "this") == 0);
}

/* Unparse an argument list, backwards.  The double check on args and
 * args->next is not redundant, since an argument list may be empty. */
static cStr *unparse_args(cStr * str, Expr_list * args)
{
    if (args) {
        if (args->next) {
            str = unparse_args(str, args->next);
            str = string_add_chars(str, ", ", 2);
        }
        str = unparse_expr(str, args->expr, PAREN_ASSIGN);
    }
    return str;
}

/* recursive....? whatever... too tired... */
Expr_list *rev(Expr_list * head, Expr_list * reversed_tail)
{
    if (head) {
        Expr_list *next = head->next;
        head->next = reversed_tail;
        return rev(next, head);
    }
    return reversed_tail;
}

/* man... i suck.. is there an easier way to do this?
 * dicts all use list expressions internally :S */
static cStr *unparse_dict(cStr * str, Expr_list * args)
{
    args = rev(args, NULL);

    Expr_list *iter;
    for (iter = args; iter; iter = iter->next) {
        str = unparse_expr(str, iter->expr->u.args->next->expr, PAREN_ASSIGN);
        str = string_add_chars(str, " => ", 4);
        str = unparse_expr(str, iter->expr->u.args->expr, PAREN_ASSIGN);
        if (iter->next)
            str = string_add_chars(str, ", ", 2);
    }
    return str;
}

/* Unparse an expression, inserting parentheses around it if the caller
 * precedence is greater than the expression's precedence level.  If we
 * should put parentheses around the expression if it is at the same
 * precedence level because of assocation (e.g. a / (b * c)) then assoc
 * should be 1. */
static cStr *unparse_expr_prec(cStr * str, Expr * expr, Int caller_type, Int assoc)
{
    Int caller_prec = prec_level(caller_type), type, prec;

    type = (expr->type == BINARY) ? expr->u.binary.opcode : (expr->type == UNARY) ? expr->u.unary.opcode : expr->type;
    prec = prec_level(type);

    if (prec >= 0 && (FULL_PARENS() || caller_prec + assoc > prec)) {
        str = string_addc(str, '(');
        str = unparse_expr(str, expr, PAREN_ASSIGN);
        return string_addc(str, ')');
    } else {
        return unparse_expr(str, expr, PAREN_ASSIGN);
    }
}

static cStr *unparse_complex_expr(cStr * str, Expr * expr, Int caller_type, Int assoc)
{
    if (expr->type < DATA_END || expr->type == FUNCTION_CALL || expr->type == VAR) {
        return unparse_expr(str, expr, PAREN_ASSIGN);
    } else {
        str = string_addc(str, '(');
        str = unparse_expr(str, expr, PAREN_ASSIGN);
        return string_addc(str, ')');
    }
}

static Int prec_level(Int opcode)
{
    Int i;

    for (i = 0; i < PREC_SIZE; i++) {
        if (precedences[i].opcode == opcode)
            return precedences[i].level;
    }

    /* Atomic expressions won't be in the table. */
    return -1;
}

static char *binary_token(Int opcode)
{
    Int i;

    for (i = 0; i < TOKEN_SIZE; i++) {
        if (binary_tokens[i].opcode == opcode)
            return binary_tokens[i].token;
    }
    return "??";
}

static cList *add_and_discard_string(cList * output, cStr * str)
{
    cData d;

    d.type = STRING;
    d.u.str = str;
    output = list_add(output, &d);
    string_discard(str);
    return output;
}

/* Get the variable name for an index. */
static char *varname(Int ind)
{
    Long id;

    if (ind < the_method->num_args) {
        ind = the_method->num_args - ind - 1;
        id = object_get_ident(the_object, the_method->argnames[ind]);
        return ident_name(id);
    }
    ind -= the_method->num_args;

    if (the_method->rest != -1) {
        if (ind == 0) {
            id = object_get_ident(the_object, the_method->rest);
            return ident_name(id);
        }
        ind--;
    }

    id = object_get_ident(the_object, the_method->varnames[ind]);
    return ident_name(id);
}
