#define ASSEMBLE__C


#include <stdlib.h>
#include <string.h>

#include "assemble.h"                  /* my own definitions */

#include "assemble_globals.h"
#include "assemble_aux.h"

#include "util.h"

#include "mlb.h"
#include "object.h"
#include "listing.h"
#include "parse.h"
#include "symbols.h"
#include "extree.h"
#include "macros.h"
#include "rept_irpc.h"

#include "rad50.h"




/* assemble - read a line from the input stack, assemble it. */

/* This function is way way too large, because I just coded most of
   the operation code and pseudo-op handling right in line.  */

static int assemble(
    STACK *stack,
    TEXT_RLD *tr)
{
    char           *cp;         /* Parse character pointer */
    char           *opcp;       /* Points to operation mnemonic text */
    char           *ncp;        /* "next" cp */
    char           *label;      /* A label */
    char           *line;       /* The whole line */
    SYMBOL         *op;         /* The operation SYMBOL */
    int             local;      /* Whether a label is a local label or
                                   not */

    line = stack_gets(stack);
    if (line == NULL)
        return -1;                     /* Return code for EOF. */

    if (!enabl_lc) {                   /* If lower case disabled, */
        upcase(line);                  /* turn it into upper case. */
    }

    cp = line;

    /* Frankly, I don't need to keep "line."  But I found it quite
       handy during debugging, to see what the whole operation was,
       when I'm down to parsing the second operand and things aren't
       going right. */

    stmtno++;                          /* Increment statement number */

    list_source(stack->top, line);     /* List source */

    if (suppressed) {
        /* Assembly is suppressed by unsatisfied conditional.  Look
           for ending and enabling statements. */

        op = get_op(cp, &cp);          /* Look at operation code */

        /* FIXME: this code will blindly look into .REM commentary and
           find operation codes.  Incidentally, so will read_body. */

        if (op == NULL)
            return 1;                  /* Not found.  Don't care. */
        if (op->section->type != SECTION_PSEUDO)
            return 1;                  /* Not a pseudo-op. */
        switch (op->value) {
        case P_IF:
        case P_IFDF:
            suppressed++;              /* Nested.  Suppressed. */
            break;
        case P_IFTF:
            if (suppressed == 1)       /* Reduce suppression from 1 to 0. */
                suppressed = 0;
            break;
        case P_IFF:
            if (suppressed == 1) {     /* Can reduce suppression from 1 to 0. */
                if (!conds[last_cond].ok)
                    suppressed = 0;
            }
            break;
        case P_IFT:
            if (suppressed == 1) {     /* Can reduce suppression from 1 to 0. */
                if (conds[last_cond].ok)
                    suppressed = 0;
            }
            break;
        case P_ENDC:
            suppressed--;              /* Un-nested. */
            if (suppressed == 0)
                pop_cond(last_cond - 1);        /* Re-enabled. */
            break;
        }
        return 1;
    }

    /* The line may begin with "label<ws>:[:]" */

    opcp = cp;
    if ((label = get_symbol(cp, &ncp, &local)) != NULL) {
        int             flag = SYMBOLFLAG_PERMANENT | SYMBOLFLAG_DEFINITION | local;
        SYMBOL         *sym;

        ncp = skipwhite(ncp);
        if (*ncp == ':') {             /* Colon, for symbol definition? */
            ncp++;
            /* maybe it's a global definition */
            if (*ncp == ':') {
                flag |= SYMBOLFLAG_GLOBAL;      /* Yes, include global flag */
                ncp++;
            }

            sym = add_sym(label, DOT, flag, current_pc->section, &symbol_st);
            cp = ncp;

            if (sym == NULL)
                report(stack->top, "Illegal symbol definition %s\n", label);

            free(label);

            /* See if local symbol block should be incremented */
            if (!enabl_lsb && !local) {
                lsb = get_next_lsb();
            }

            cp = skipwhite(ncp);
            opcp = cp;
            label = get_symbol(cp, &ncp, NULL); /* Now, get what follows */
        }
    }

    /* PSEUDO P_IIF jumps here.  */
  reassemble:
    cp = skipwhite(cp);

    if (EOL(*cp))
        return 1;                      /* It's commentary.  All done. */

    if (label) {                       /* Something looks like a label. */
        /* detect assignment */

        ncp = skipwhite(ncp);          /* The pointer to the text that
                                          follows the symbol */

        if (*ncp == '=') {
            unsigned        flags;
            EX_TREE        *value;
            SYMBOL         *sym;

            cp = ncp;

            /* Symbol assignment. */

            flags = SYMBOLFLAG_DEFINITION | local;
            cp++;
            if (*cp == '=') {
                flags |= SYMBOLFLAG_GLOBAL;     /* Global definition */
                cp++;
            }
            if (*cp == ':') {
                flags |= SYMBOLFLAG_PERMANENT;
                cp++;
            }

            cp = skipwhite(cp);

            value = parse_expr(cp, 0);

            /* Special code: if the symbol is the program counter,
               this is harder. */

            if (strcmp(label, ".") == 0) {
                if (current_pc->section->flags & PSECT_REL) {
                    SYMBOL         *symb;
                    unsigned        offset;

                    /* Express the given expression as a symbol and an
                       offset. The symbol must not be global, the
                       section must = current. */

                    if (!express_sym_offset(value, &symb, &offset)) {
                        report(stack->top, "Illegal ORG\n");
                    } else if ((symb->flags & (SYMBOLFLAG_GLOBAL | SYMBOLFLAG_DEFINITION)) == SYMBOLFLAG_GLOBAL) {
                        report(stack->top, "Can't ORG to external location\n");
                    } else if (symb->flags & SYMBOLFLAG_UNDEFINED) {
                        report(stack->top, "Can't ORG to undefined sym\n");
                    } else if (symb->section != current_pc->section) {
                        report(stack->top, "Can't ORG to alternate section " "(use PSECT)\n");
                    } else {
                        DOT = symb->value + offset;
                        list_value(stack->top, DOT);
                        change_dot(tr, 0);
                    }
                } else {
                    /* If the current section is absolute, the value
                       must be a literal */
                    if (value->type != EX_LIT) {
                        report(stack->top, "Can't ORG to non-absolute location\n");
                        free_tree(value);
                        free(label);
                        return 0;
                    }
                    DOT = value->data.lit;
                    list_value(stack->top, DOT);
                    change_dot(tr, 0);
                }
                free_tree(value);
                free(label);
                return 1;
            }

            /* regular symbols */
            if (value->type == EX_LIT) {
                sym = add_sym(label, value->data.lit, flags, &absolute_section, &symbol_st);
            } else if (value->type == EX_SYM || value->type == EX_TEMP_SYM) {
                sym = add_sym(label, value->data.symbol->value, flags, value->data.symbol->section, &symbol_st);
            } else {
                report(stack->top, "Complex expression cannot be assigned " "to a symbol\n");

                if (!pass) {
                    /* This may work better in pass 2 - something in
                       RT-11 monitor needs the symbol to apear to be
                       defined even if I can't resolve its value. */
                    sym = add_sym(label, 0, SYMBOLFLAG_UNDEFINED, &absolute_section, &symbol_st);
                } else
                    sym = NULL;
            }

            if (sym != NULL)
                list_value(stack->top, sym->value);

            free_tree(value);
            free(label);

            return sym != NULL;
        }

        /* Try to resolve macro */

        op = lookup_sym(label, &macro_st);
        if (op /*&& op->stmtno < stmtno*/) {
            STREAM         *macstr;

            free(label);

            list_location(stack->top, DOT);

            macstr = expandmacro(stack->top, (MACRO *) op, ncp);

            stack_push(stack, macstr); /* Push macro expansion
                                          onto input stream */

            return 1;
        }

        /* Try to resolve instruction or pseudo */
        op = lookup_sym(label, &system_st);
        if (op) {
            cp = ncp;

            free(label);               /* Don't need this hanging around anymore */

            switch (op->section->type) {
            case SECTION_PSEUDO:
                switch (op->value) {
                case P_ENDR:
                case P_ENDM:
                case P_SBTTL:
                case P_LIST:
                case P_NLIST:
                case P_PRINT:
                    return 1;          /* Accepted, ignored.  (An obvious
                                          need: get assembly listing
                                          controls working. ) */

                case P_IDENT:
                    {
                        char            endc[6];
                        int             len;

                        cp = skipwhite(cp);
                        endc[0] = *cp++;
                        endc[1] = '\n';
                        endc[2] = 0;
                        len = (int) strcspn(cp, endc);
                        if (len > 6)
                            len = 6;

                        if (ident)     /* An existing ident? */
                            free(ident);        /* Discard it. */

                        ident = memcheck(malloc(len + 1));
                        memcpy(ident, cp, len);
                        ident[len] = 0;
                        upcase(ident);

                        return 1;
                    }

                case P_RADIX:
                    {
                        int             old_radix = radix;

                        radix = strtoul(cp, &cp, 10);
                        if (radix != 8 && radix != 10 && radix != 16 && radix != 2) {
                            radix = old_radix;
                            report(stack->top, "Illegal radix\n");
                            return 0;
                        }
                        return 1;
                    }

                case P_FLT4:
                case P_FLT2:
                    {
                        int             ok = 1;

                        while (!EOL(*cp)) {
                            unsigned        flt[4];

                            if (parse_float(cp, &cp, (op->value == P_FLT4 ? 4 : 2), flt)) {
                                /* Store the word values */
                                store_word(stack->top, tr, 2, flt[0]);
                                store_word(stack->top, tr, 2, flt[1]);
                                if (op->value == P_FLT4) {
                                    store_word(stack->top, tr, 2, flt[2]);
                                    store_word(stack->top, tr, 2, flt[3]);
                                }
                            } else {
                                report(stack->top, "Bad floating point format\n");
                                ok = 0;
                            }
                            cp = skipdelim(cp);
                        }
                        return ok;
                    }

                case P_ERROR:
                    report(stack->top, "%.*s\n", strcspn(cp, "\n"), cp);
                    return 0;

                case P_SAVE:
                    if (sect_sp >= SECT_STACK_SIZE - 1) {
                        report(stack->top, "Too many saved sections for .SAVE\n");
                        return 0;
                    }
                    sect_sp++;
                    sect_stack[sect_sp] = current_pc->section;
                    dot_stack[sect_sp] = DOT;
                    return 1;

                case P_RESTORE:
                    if (sect_sp < 0) {
                        report(stack->top, "No saved section for .RESTORE\n");
                        return 0;
                    } else {
                        go_section(tr, sect_stack[sect_sp]);
                        DOT = dot_stack[sect_sp];
                        list_location(stack->top, DOT);
                        if (!enabl_lsb) {
                            lsb = get_next_lsb();
                        }
                        sect_sp--;
                    }
                    return 1;

                case P_NARG:
                    {
                        STREAM         *str;
                        MACRO_STREAM   *mstr;
                        int             islocal;

                        label = get_symbol(cp, &cp, &islocal);

                        if (label == NULL) {
                            report(stack->top, "Bad .NARG syntax\n");
                            return 0;
                        }

                        /* Walk up the stream stack to find the
                           topmost macro stream */
                        for (str = stack->top; str != NULL && str->vtbl != &macro_stream_vtbl;
                             str = str->next) ;

                        if (!str) {
                            report(str, ".NARG not within macro expansion\n");
                            free(label);
                            return 0;
                        }

                        mstr = (MACRO_STREAM *) str;

                        add_sym(label, mstr->nargs, SYMBOLFLAG_DEFINITION | islocal, &absolute_section,
                                &symbol_st);
                        free(label);
                        list_value(stack->top, mstr->nargs);
                        return 1;
                    }

                case P_NCHR:
                    {
                        char           *string;
                        int             islocal;

                        label = get_symbol(cp, &cp, &islocal);

                        if (label == NULL) {
                            report(stack->top, "Bad .NCHR syntax\n");
                            return 0;
                        }

                        cp = skipdelim(cp);

                        string = getstring(cp, &cp);

                        add_sym(label, strlen(string), SYMBOLFLAG_DEFINITION | islocal, &absolute_section,
                                &symbol_st);
                        free(label);
                        free(string);
                        return 1;
                    }

                case P_NTYPE:
                    {
                        ADDR_MODE       mode;
                        int             islocal;

                        label = get_symbol(cp, &cp, &islocal);
                        if (label == NULL) {
                            report(stack->top, "Bad .NTYPE syntax\n");
                            return 0;
                        }

                        cp = skipdelim(cp);

                        if (!get_mode(cp, &cp, &mode)) {
                            report(stack->top, "Bad .NTYPE addressing mode\n");
                            free(label);
                            return 0;
                        }

                        add_sym(label, mode.type, SYMBOLFLAG_DEFINITION | islocal, &absolute_section, &symbol_st);
                        free_addr_mode(&mode);
                        free(label);

                        return 1;
                    }

                case P_INCLUDE:
                    {
                        char           *name = getstring_fn(cp, &cp);
                        STREAM         *incl;
                        char            hitfile[FILENAME_MAX];

                        if (name == NULL) {
                            report(stack->top, "Bad .INCLUDE file name\n");
                            return 0;
                        }

                        my_searchenv(name, "INCLUDE", hitfile, sizeof(hitfile));
                        free(name);

                        if (hitfile[0] == '\0') {
                            report(stack->top, "Unable to find .INCLUDE file \"%s\"\n", name);
                            return 0;
                        }

                        incl = new_file_stream(hitfile);
                        if (incl == NULL) {
                            report(stack->top, "Unable to open .INCLUDE file \"%s\"\n", hitfile);
                            return 0;
                        }

                        stack_push(stack, incl);

                        return 1;
                    }

                case P_REM:
                    {
                        char            quote[4];

                        /* Read and discard lines until one with a
                           closing quote */

                        cp = skipwhite(cp);
                        quote[0] = *cp++;
                        quote[1] = '\n';
                        quote[2] = 0;

                        for (;;) {
                            cp += strcspn(cp, quote);
                            if (*cp == quote[0])
                                break; /* Found closing quote */
                            cp = stack_gets(stack);     /* Read next input line */
                            if (cp == NULL)
                                break; /* EOF */
                        }
                    }
                    return 1;

                case P_IRP:
                    {
                        STREAM         *str = expand_irp(stack, cp);

                        if (str)
                            stack_push(stack, str);
                        return str != NULL;
                    }

                case P_IRPC:
                    {
                        STREAM         *str = expand_irpc(stack, cp);

                        if (str)
                            stack_push(stack, str);
                        return str != NULL;
                    }

                case P_LIBRARY:
                    if (pass == 0) {
                        char            hitfile[FILENAME_MAX];
                        char           *name = getstring_fn(cp, &cp);

                        my_searchenv(name, "MCALL", hitfile, sizeof(hitfile));

                        if (hitfile[0]) {
                            mlbs[nr_mlbs] = mlb_open(hitfile, 0);
                            if (mlbs[nr_mlbs] == NULL) {
                                report(stack->top, "Unable to register macro library \"%s\"\n", hitfile);
                            } else {
                                nr_mlbs++;
                            }
                        } else {
                            report(stack->top, "Unable to locate macro library \"%s\"\n", name);
                        }
                        free(name);
                    }
                    return 1;

                case P_MCALL:
                    {
                        STREAM         *macstr;
                        BUFFER         *macbuf;
                        char           *maccp;
                        int             saveline;
                        MACRO          *mac;
                        int             i;
                        char            macfile[FILENAME_MAX];
                        char            hitfile[FILENAME_MAX];

                        for (;;) {
                            cp = skipdelim(cp);

                            if (EOL(*cp))
                                return 1;

                            label = get_symbol(cp, &cp, NULL);
                            if (!label) {
                                report(stack->top, "Illegal .MCALL format\n");
                                return 0;
                            }

                            /* See if that macro's already defined */
                            if (lookup_sym(label, &macro_st)) {
                                free(label);    /* Macro already
                                                   registered.  No
                                                   prob. */
                                cp = skipdelim(cp);
                                continue;
                            }

                            /* Find the macro in the list of included
                               macro libraries */
                            macbuf = NULL;
                            for (i = 0; i < nr_mlbs; i++)
                                if ((macbuf = mlb_entry(mlbs[i], label)) != NULL)
                                    break;
                            if (macbuf != NULL) {
                                macstr = new_buffer_stream(macbuf, label);
                                buffer_free(macbuf);
                            } else {
                                strncpy(macfile, label, sizeof(macfile));
                                strncat(macfile, ".MAC", sizeof(macfile) - strlen(macfile) - 1);
                                my_searchenv(macfile, "MCALL", hitfile, sizeof(hitfile));
                                if (hitfile[0])
                                    macstr = new_file_stream(hitfile);
                                else
                                    macstr = NULL;
                            }

                            if (macstr != NULL) {
                                for (;;) {
                                    char           *mlabel;

                                    maccp = macstr->vtbl->gets(macstr);
                                    if (maccp == NULL)
                                        break;
                                    mlabel = get_symbol(maccp, &maccp, NULL);
                                    if (mlabel == NULL)
                                        continue;
                                    op = lookup_sym(mlabel, &system_st);
                                    free(mlabel);
                                    if (op == NULL)
                                        continue;
                                    if (op->value == P_MACRO)
                                        break;
                                }

                                if (maccp != NULL) {
                                    STACK           macstack = {
                                        macstr
                                    };
                                    int             savelist = list_level;

                                    saveline = stmtno;
                                    list_level = -1;
                                    mac = defmacro(maccp, &macstack, TRUE);
                                    if (mac == NULL) {
                                        report(stack->top, "Failed to define macro " "called %s\n", label);
                                    }

                                    stmtno = saveline;
                                    list_level = savelist;
                                }

                                macstr->vtbl->delete(macstr);
                            } else
                                report(stack->top, "MACRO %s not found\n", label);

                            free(label);
                        }
                    }
                    return 1;

                case P_MACRO:
                    {
                        MACRO          *mac = defmacro(cp, stack, FALSE);

                        return mac != NULL;
                    }

                case P_MEXIT:
                    {
                        STREAM         *macstr;

                        /* Pop a stream from the input. */
                        /* It must be the first stream, and it must be */
                        /* a macro, rept, irp, or irpc. */
                        macstr = stack->top;
                        if (macstr->vtbl != &macro_stream_vtbl && macstr->vtbl != &rept_stream_vtbl
                            && macstr->vtbl != &irp_stream_vtbl && macstr->vtbl != &irpc_stream_vtbl) {
                            report(stack->top, ".MEXIT not within a macro\n");
                            return 0;
                        }

                        /* and finally, pop the macro */
                        stack_pop(stack);

                        return 1;
                    }

                case P_REPT:
                    {
                        STREAM         *reptstr = expand_rept(stack, cp);

                        if (reptstr)
                            stack_push(stack, reptstr);
                        return reptstr != NULL;
                    }

                case P_ENABL:
                    /* FIXME - add all the rest of the options. */
                    while (!EOL(*cp)) {
                        label = get_symbol(cp, &cp, NULL);
                        if (strcmp(label, "AMA") == 0)
                            enabl_ama = 1;
                        else if (strcmp(label, "LSB") == 0) {
                            enabl_lsb = 1;
                            lsb = get_next_lsb();
                        } else if (strcmp(label, "GBL") == 0) {
                            enabl_gbl = 1;
                        } else if (strcmp(label, "LC") == 0) {
                            enabl_lc = 1;
                        }
                        free(label);
                        cp = skipdelim(cp);
                    }
                    return 1;

                case P_DSABL:
                    /* FIXME Ditto as for .ENABL */
                    while (!EOL(*cp)) {
                        label = get_symbol(cp, &cp, NULL);
                        if (strcmp(label, "AMA") == 0)
                            enabl_ama = 0;
                        else if (strcmp(label, "LSB") == 0) {
                            lsb = get_next_lsb();
                            enabl_lsb = 0;
                        } else if (strcmp(label, "GBL") == 0) {
                            enabl_gbl = 0;
                        } else if (strcmp(label, "LC") == 0) {
                            enabl_lc = 0;
                        }
                        free(label);
                        cp = skipdelim(cp);
                    }
                    return 1;

                case P_LIMIT:
                    store_limits(stack->top, tr);
                    return 1;

                case P_TITLE:
                    /* accquire module name */
                    if (module_name != NULL) {
                        free(module_name);
                    }
                    module_name = get_symbol(cp, &cp, NULL);
                    return 1;

                case P_END:
                    /* Accquire transfer address */
                    cp = skipwhite(cp);
                    if (!EOL(*cp)) {
                        if (xfer_address)
                            free_tree(xfer_address);
                        xfer_address = parse_expr(cp, 0);
                    }
                    return 1;

                case P_IFDF:
                    opcp = skipwhite(opcp);
                    cp = opcp + 3;     /* Point cp at the "DF" or
                                          "NDF" part */
                    /* Falls into... */
                case P_IIF:
                case P_IF:
                    {
                        EX_TREE        *value;
                        int             ok;

                        label = get_symbol(cp, &cp, NULL);      /* Get condition */
                        cp = skipdelim(cp);

                        if (strcmp(label, "DF") == 0) {
                            value = parse_expr(cp, 1);
                            cp = value->cp;
                            ok = eval_defined(value);
                            free_tree(value);
                        } else if (strcmp(label, "NDF") == 0) {
                            value = parse_expr(cp, 1);
                            cp = value->cp;
                            ok = eval_undefined(value);
                            free_tree(value);
                        } else if (strcmp(label, "B") == 0 ||
                                   strcmp(label, "NB") == 0) {
                            cp = skipwhite(cp);
                            if (EOL(*cp)) {
                                ok = 1;
                            } else {
                                char           *thing, *end;

                                thing = getstring(cp, &cp);
                                end = skipwhite(thing);
                                ok = (*end == 0);
                                free(thing);
                            }
                            if (label[0] == 'N') {
                                ok = !ok;
                            }
                        } else if (strcmp(label, "IDN") == 0 ||
                                   strcmp(label, "DIF") == 0) {
                            char           *thing1,
                                           *thing2;

                            thing1 = getstring(cp, &cp);
                            cp = skipdelim(cp);
                            if (!EOL(*cp))
                                thing2 = getstring(cp, &cp);
                            else
                                thing2 = memcheck(strdup(""));
                            ok = (strcmp(thing1, thing2) == 0);
                            if (label[0] == 'D') {
                                ok = !ok;
                            }
                            free(thing1);
                            free(thing2);
                        } else if (strcmp(label, "P1") == 0) {
                            ok = (pass == 0);
                        } else if (strcmp(label, "P2") == 0) {
                            ok = (pass == 1);
                        } else {
                            int             sword;
                            unsigned        uword;
                            EX_TREE        *tvalue = parse_expr(cp, 0);

                            cp = tvalue->cp;

                            if (tvalue->type != EX_LIT) {
                                report(stack->top, "Bad .IF expression\n");
                                list_value(stack->top, 0);
                                free_tree(tvalue);
                                ok = FALSE;     /* Pick something. */
                            } else {
                                unsigned        word;

                                /* Convert to signed and unsigned words */
                                sword = tvalue->data.lit & 0x7fff;

                                /* FIXME I don't know if the following
                                   is portable enough.  */
                                if (tvalue->data.lit & 0x8000)
                                    sword |= ~0xFFFF;   /* Render negative */

                                /* Reduce unsigned value to 16 bits */
                                uword = tvalue->data.lit & 0xffff;

                                if (strcmp(label, "EQ") == 0 || strcmp(label, "Z") == 0)
                                    ok = (uword == 0), word = uword;
                                else if (strcmp(label, "NE") == 0 || strcmp(label, "NZ") == 0)
                                    ok = (uword != 0), word = uword;
                                else if (strcmp(label, "GT") == 0 || strcmp(label, "G") == 0)
                                    ok = (sword > 0), word = sword;
                                else if (strcmp(label, "GE") == 0)
                                    ok = (sword >= 0), word = sword;
                                else if (strcmp(label, "LT") == 0 || strcmp(label, "L") == 0)
                                    ok = (sword < 0), word = sword;
                                else if (strcmp(label, "LE") == 0)
                                    ok = (sword <= 0), word = sword;
                                else
                                    ok = 0, word = 0;

                                list_value(stack->top, word);

                                free_tree(tvalue);
                            }
                        }

                        free(label);

                        if (op->value == P_IIF) {
                            stmtno++;  /* the second half is a
                                          separate statement */
                            if (ok) {
                                /* The "immediate if" */
                                /* Only slightly tricky. */
                                cp = skipdelim(cp);
                                label = get_symbol(cp, &ncp, &local);
                                goto reassemble;
                            }
                            return 1;
                        }

                        push_cond(ok, stack->top);

                        if (!ok)
                            suppressed++;       /* Assembly
                                                   suppressed
                                                   until .ENDC */
                    }
                    return 1;

                case P_IFF:
                    if (last_cond < 0) {
                        report(stack->top, "No conditional block active\n");
                        return 0;
                    }
                    if (conds[last_cond].ok)    /* Suppress if last cond
                                                   is true */
                        suppressed++;
                    return 1;

                case P_IFT:
                    if (last_cond < 0) {
                        report(stack->top, "No conditional block active\n");
                        return 0;
                    }
                    if (!conds[last_cond].ok)   /* Suppress if last cond
                                                   is false */
                        suppressed++;
                    return 1;

                case P_IFTF:
                    if (last_cond < 0) {
                        report(stack->top, "No conditional block active\n");
                        return 0;
                    }
                    return 1;          /* Don't suppress. */

                case P_ENDC:
                    if (last_cond < 0) {
                        report(stack->top, "No conditional block active\n");
                        return 0;
                    }

                    pop_cond(last_cond - 1);
                    return 1;

                case P_EVEN:
                    if (DOT & 1) {
                        list_word(stack->top, DOT, 0, 1, "");
                        DOT++;
                        change_dot(tr, 0);
                    }
                    return 1;

                case P_ODD:
                    if (!(DOT & 1)) {
                        list_word(stack->top, DOT, 0, 1, "");
                        DOT++;
                        change_dot(tr, 0);
                    }
                    return 1;

                case P_ASECT:
                    if (!enabl_lsb) {
                        lsb = get_next_lsb();
                    }
                    go_section(tr, &absolute_section);
                    list_location(stack->top, DOT);
                    return 1;

                case P_CSECT:
                case P_PSECT:
                    {
                        SYMBOL         *sectsym;
                        SECTION        *sect;
                        unsigned int    old_flags = ~0u;
                        int             unnamed_csect = 0;

                        label = get_symbol(cp, &cp, NULL);
                        if (label == NULL) {
                            if (op->value == P_CSECT) {
                                label = memcheck(strdup(". BLK."));
                                unnamed_csect = 1;
                            } else {
                                label = memcheck(strdup(""));       /* Allow blank */
                            }
                        }

                        sectsym = lookup_sym(label, &section_st);
                        if (sectsym) {
                            sect = sectsym->section;
                            free(label);
                            old_flags = sect->flags;
                        } else {
                            sect = new_section();
                            sect->label = label;
                            sect->flags = 0;
                            sect->pc = 0;
                            sect->size = 0;
                            sect->type = SECTION_USER;
                            sections[sector++] = sect;
                            sectsym = add_sym(label, 0, 0, sect, &section_st);

                            /* page 6-41 table 6-5 */
                            if (op->value == P_PSECT) {
                                sect->flags |= PSECT_REL;
                            } else if (op->value == P_CSECT) {
                                if (unnamed_csect) {
                                    sect->flags |= PSECT_REL;
                                } else {
                                    sect->flags |= PSECT_REL | PSECT_COM | PSECT_GBL;
                                }
                            }
                        }

                        cp = skipdelim(cp);
                        if (!EOL(*cp)) {
                            while (cp = skipdelim(cp), !EOL(*cp)) {
                                /* Parse section options */
                                label = get_symbol(cp, &cp, NULL);
                                if (strcmp(label, "ABS") == 0) {
                                    sect->flags &= ~PSECT_REL;      /* Not relative */
                                    sect->flags |= PSECT_COM;       /* implies common */
                                } else if (strcmp(label, "REL") == 0) {
                                    sect->flags |= PSECT_REL;       /* Is relative */
                                } else if (strcmp(label, "SAV") == 0) {
                                    sect->flags |= PSECT_SAV;       /* Is root */
                                } else if (strcmp(label, "NOSAV") == 0) {
                                    sect->flags &= ~PSECT_SAV;      /* Is not root */
                                } else if (strcmp(label, "OVR") == 0) {
                                    sect->flags |= PSECT_COM;       /* Is common */
                                } else if (strcmp(label, "CON") == 0) {
                                    sect->flags &= ~PSECT_COM;      /* Concatenated */
                                } else if (strcmp(label, "RW") == 0) {
                                    sect->flags &= ~PSECT_RO;       /* Not read-only */
                                } else if (strcmp(label, "RO") == 0) {
                                    sect->flags |= PSECT_RO;        /* Is read-only */
                                } else if (strcmp(label, "I") == 0) {
                                    sect->flags &= ~PSECT_DATA;     /* Not data */
                                } else if (strcmp(label, "D") == 0) {
                                    sect->flags |= PSECT_DATA;      /* data */
                                } else if (strcmp(label, "GBL") == 0) {
                                    sect->flags |= PSECT_GBL;       /* Global */
                                } else if (strcmp(label, "LCL") == 0) {
                                    sect->flags &= ~PSECT_GBL;      /* Local */
                                } else {
                                    report(stack->top, "Unknown flag %s given to " ".PSECT directive\n", label);
                                    free(label);
                                    return 0;
                                }

                                free(label);
                            }
                            /* If a section is declared a second time, and flags
                             * are given, then they must be identical to the
                             * first time.
                             * See page 6-38 of AA-KX10A-TC_PDP-11_MACRO-11_Reference_Manual_May88.pdf .
                             */
                            if (old_flags != ~0u && sect->flags != old_flags) {
                                /* The manual also says that any different
                                 * flags are ignored, and an error issued.
                                 * Apparently, that isn't true.
                                 * Kermit seems to do this in k11cmd.mac:
                                 *      .psect  $pdata          ; line 16
                                 *      .psect  $pdata  ,ro,d,lcl,rel,con
                                 *               ; k11mac.mac, first pass only
                                 *      .psect  $PDATA  ,D      ; line 1083
                                 * and ends up with
                                 * $PDATA  001074    003   (RO,D,LCL,REL,CON)
                                 */

                                /*
                                sect->flags = old_flags;
                                report(stack->top, "Program section flags not identical\n");
                                */
                            }
                        }

                        if (!enabl_lsb) {
                            lsb = get_next_lsb();
                        }
                        go_section(tr, sect);
                        list_location(stack->top, DOT);

                        return 1;
                    }                  /* end PSECT code */
                    break;

                case P_WEAK:
                case P_GLOBL:
                    {
                        SYMBOL         *sym;

                        while (!EOL(*cp)) {
                            /* Loop and make definitions for
                               comma-separated symbols */
                            label = get_symbol(cp, &ncp, NULL);
                            if (label == NULL) {
                                report(stack->top, "Illegal .GLOBL/.WEAK " "syntax\n");
                                return 0;
                            }

                            sym = lookup_sym(label, &symbol_st);
                            if (sym) {
                                sym->flags |= SYMBOLFLAG_GLOBAL | (op->value == P_WEAK ? SYMBOLFLAG_WEAK : 0);
                            } else
                                sym = add_sym(label, 0,
                                              SYMBOLFLAG_GLOBAL | (op->value == P_WEAK ? SYMBOLFLAG_WEAK : 0),
                                              &absolute_section, &symbol_st);

                            free(label);
                            cp = skipdelim(ncp);
                        }
                    }
                    return 1;

                case P_WORD:
                    {
                        /* .WORD might be followed by nothing, which
                           is an implicit .WORD 0 */
                        if (EOL(*cp)) {
                            if (DOT & 1) {
                                report(stack->top, ".WORD on odd " "boundary\n");
                                DOT++; /* Fix it, too */
                            }
                            store_word(stack->top, tr, 2, 0);
                            return 1;
                        } else
                            return do_word(stack, tr, cp, 2);
                    }

                case P_BYTE:
                    if (EOL(*cp)) {
                        /* Blank .BYTE.  Same as .BYTE 0 */
                        store_word(stack->top, tr, 1, 0);
                        return 1;
                    } else
                        return do_word(stack, tr, cp, 1);

                case P_BLKW:
                case P_BLKB:
                    {
                        EX_TREE        *value = parse_expr(cp, 0);
                        int             ok = 1;

                        if (value->type != EX_LIT) {
                            report(stack->top, "Argument to .BLKB/.BLKW " "must be constant\n");
                            ok = 0;
                        } else {
                            list_value(stack->top, DOT);
                            DOT += value->data.lit * (op->value == P_BLKW ? 2 : 1);
                            change_dot(tr, 0);
                        }
                        free_tree(value);
                        return ok;
                    }

                case P_ASCIZ:
                case P_ASCII:
                    {
                        do {
                            cp = skipwhite(cp);
                            if (*cp == '<') {
                                EX_TREE        *value;
                                /* A byte value */
                                value = parse_unary_expr(cp, 0);
                                cp = value->cp;
                                store_value(stack, tr, 1, value);
                                free_tree(value);
                            } else {
                                char            quote = *cp++;

                                while (*cp && *cp != '\n' && *cp != quote)
                                    store_word(stack->top, tr, 1, *cp++);
                                cp++;  /* Skip closing quote */
                            }

                            cp = skipwhite(cp);
                        } while (!EOL(*cp));

                        if (op->value == P_ASCIZ) {
                            store_word(stack->top, tr, 1, 0);
                        }

                        return 1;
                    }

                case P_RAD50:
                    if (DOT & 1) {
                        report(stack->top, ".RAD50 on odd " "boundary\n");
                        DOT++;         /* Fix it */
                    }
                    {
                        char           *radstr;
                        int             i, len;

                        /*
                         * Allocate storage sufficient for the rest of
                         * the line.
                         */
                        radstr = memcheck(malloc(strlen(cp)));
                        len = 0;

                        do {
                            cp = skipwhite(cp);
                            if (*cp == '<') {
                                EX_TREE        *value;
                                /* A byte value */
                                value = parse_unary_expr(cp, 0);
                                cp = value->cp;
                                if (value->type != EX_LIT) {
                                    report(stack->top, "expression must be constant\n");
                                    radstr[len++] = 0;
                                } else if (value->data.lit >= 050) {
                                    report(stack->top, "invalid character value %o\n", value->data.lit);
                                    radstr[len++] = 0;
                                } else {
                                    radstr[len++] = value->data.lit;
                                }
                                free_tree(value);
                            } else {
                                char            quote = *cp++;

                                while (*cp && *cp != '\n' && *cp != quote) {
                                    int         ch = ascii2rad50(*cp++);

                                    if (ch == -1) {
                                        report(stack->top, "invalid character '%c'\n", cp[-1]);
                                        radstr[len++] = 0;
                                    } else {
                                        radstr[len++] = ch;
                                    }

                                }
                                cp++;  /* Skip closing quote */
                            }

                            cp = skipwhite(cp);
                        } while (!EOL(*cp));

                        for (i = 0; i < len; i += 3) {
                            int word = packrad50word(radstr + i, len - i);
                            store_word(stack->top, tr, 2, word);
                        }

                        free(radstr);
                    }
                    return 1;

                default:
                    report(stack->top, "Unimplemented directive %s\n", op->label);
                    return 0;
                }                      /* end switch (PSEUDO operation) */

            case SECTION_INSTRUCTION:
                {
                    /* The PC must always be even. */
                    if (DOT & 1) {
                        report(stack->top, "Instruction on odd address\n");
                        DOT++;         /* ...and fix it... */
                    }

                    switch (op->flags & OC_MASK) {
                    case OC_NONE:
                        /* No operands. */
                        store_word(stack->top, tr, 2, op->value);
                        return 1;

                    case OC_MARK:
                        /* MARK, EMT, TRAP */  {
                            EX_TREE        *value;
                            unsigned        word;

                            cp = skipwhite(cp);
                            if (*cp == '#')
                                cp++;  /* Allow the hash, but
                                          don't require it */
                            value = parse_expr(cp, 0);
                            if (value->type != EX_LIT) {
                                report(stack->top, "Instruction requires " "simple literal operand\n");
                                word = op->value;
                            } else {
                                word = op->value | value->data.lit;
                            }

                            store_word(stack->top, tr, 2, word);
                            free_tree(value);
                        }
                        return 1;

                    case OC_1GEN:
                        /* One general addressing mode */  {
                            ADDR_MODE       mode;
                            unsigned        word;

                            if (!get_mode(cp, &cp, &mode)) {
                                report(stack->top, "Illegal addressing mode\n");
                                return 0;
                            }

                            if (op->value == 0100 && (mode.type & 070) == 0) {
                                report(stack->top, "JMP Rn is illegal\n");
                                /* But encode it anyway... */
                            }

                            /* Build instruction word */
                            word = op->value | mode.type;
                            store_word(stack->top, tr, 2, word);
                            mode_extension(tr, &mode, stack->top);
                        }
                        return 1;

                    case OC_2GEN:
                        /* Two general addressing modes */  {
                            ADDR_MODE       left,
                                            right;
                            unsigned        word;

                            if (!get_mode(cp, &cp, &left)) {
                                report(stack->top, "Illegal addressing mode\n");
                                return 0;
                            }

                            cp = skipwhite(cp);
                            if (*cp++ != ',') {
                                report(stack->top, "Illegal syntax\n");
                                free_addr_mode(&left);
                                return 0;
                            }

                            if (!get_mode(cp, &cp, &right)) {
                                report(stack->top, "Illegal addressing mode\n");
                                free_addr_mode(&left);
                                return 0;
                            }

                            /* Build instruction word */
                            word = op->value | left.type << 6 | right.type;
                            store_word(stack->top, tr, 2, word);
                            mode_extension(tr, &left, stack->top);
                            mode_extension(tr, &right, stack->top);
                        }
                        return 1;

                    case OC_BR:
                        /* branches */  {
                            EX_TREE        *value;
                            unsigned        offset;

                            value = parse_expr(cp, 0);
                            cp = value->cp;

                            /* Relative PSECT or absolute? */
                            if (current_pc->section->flags & PSECT_REL) {
                                SYMBOL         *sym = NULL;

                                /* Can't branch unless I can
                                   calculate the offset. */

                                /* You know, I *could* branch
                                   between sections if I feed the
                                   linker a complex relocation
                                   expression to calculate the
                                   offset.  But I won't. */

                                if (!express_sym_offset(value, &sym, &offset)
                                    || sym->section != current_pc->section) {
                                    report(stack->top, "Bad branch target (%s)\n",
                                            sym ? "not same section"
                                                : "can't express offset");
                                    store_word(stack->top, tr, 2, op->value);
                                    free_tree(value);
                                    return 0;
                                }

                                /* Compute the branch offset and
                                   check for addressability */
                                offset += sym->value;
                                offset -= DOT + 2;
                            } else {
                                if (value->type != EX_LIT) {
                                    report(stack->top, "Bad branch target (not literal; ABS section)\n");
                                    store_word(stack->top, tr, 2, op->value);
                                    free_tree(value);
                                    return 0;
                                }

                                offset = value->data.lit - (DOT + 2);
                            }

                            if (!check_branch(stack, offset, -256, 255))
                                offset = 0;

                            /* Emit the branch code */
                            offset &= 0777;     /* Reduce to 9 bits */
                            offset >>= 1;       /* Shift to become
                                                   word offset */

                            store_word(stack->top, tr, 2, op->value | offset);

                            free_tree(value);
                        }
                        return 1;

                    case OC_SOB:
                        {
                            EX_TREE        *value;
                            unsigned        reg;
                            unsigned        offset;

                            value = parse_expr(cp, 0);
                            cp = value->cp;

                            reg = get_register(value);
                            free_tree(value);
                            if (reg == NO_REG) {
                                report(stack->top, "Illegal addressing mode\n");
                                return 0;
                            }

                            cp = skipwhite(cp);
                            if (*cp++ != ',') {
                                report(stack->top, "Illegal syntax\n");
                                return 0;
                            }

                            value = parse_expr(cp, 0);
                            cp = value->cp;

                            /* Relative PSECT or absolute? */
                            if (current_pc->section->flags & PSECT_REL) {
                                SYMBOL         *sym;

                                if (!express_sym_offset(value, &sym, &offset)) {
                                    report(stack->top, "Bad branch target (can't express offset)\n");
                                    free_tree(value);
                                    return 0;
                                }
                                /* Must be same section */
                                if (sym->section != current_pc->section) {
                                    report(stack->top, "Bad branch target (different section)\n");
                                    free_tree(value);
                                    offset = 0;
                                } else {
                                    /* Calculate byte offset */
                                    offset += DOT + 2;
                                    offset -= sym->value;
                                }
                            } else {
                                if (value->type != EX_LIT) {
                                    report(stack->top, "Bad branch target (not a literal)\n");
                                    offset = 0;
                                } else {
                                    offset = DOT + 2 - value->data.lit;
                                }
                            }

                            if (!check_branch(stack, offset, 0, 126))
                                offset = 0;

                            offset &= 0177;     /* Reduce to 7 bits */
                            offset >>= 1;       /* Shift to become word offset */
                            store_word(stack->top, tr, 2, op->value | offset | (reg << 6));

                            free_tree(value);
                        }
                        return 1;

                    case OC_ASH:
                        /* First op is gen, second is register. */  {
                            ADDR_MODE       mode;
                            EX_TREE        *value;
                            unsigned        reg;
                            unsigned        word;

                            if (!get_mode(cp, &cp, &mode)) {
                                report(stack->top, "Illegal addressing mode\n");
                                return 0;
                            }

                            cp = skipwhite(cp);
                            if (*cp++ != ',') {
                                report(stack->top, "Illegal addressing mode\n");
                                free_addr_mode(&mode);
                                return 0;
                            }
                            value = parse_expr(cp, 0);
                            cp = value->cp;

                            reg = get_register(value);
                            if (reg == NO_REG) {
                                report(stack->top, "Illegal addressing mode\n");
                                free_tree(value);
                                free_addr_mode(&mode);
                                return 0;
                            }

                            /* Instruction word */
                            word = op->value | mode.type | (reg << 6);
                            store_word(stack->top, tr, 2, word);
                            mode_extension(tr, &mode, stack->top);
                            free_tree(value);
                        }
                        return 1;

                    case OC_JSR:
                        /* First op is register, second is gen. */  {
                            ADDR_MODE       mode;
                            EX_TREE        *value;
                            unsigned        reg;
                            unsigned        word;

                            value = parse_expr(cp, 0);
                            cp = value->cp;

                            reg = get_register(value);
                            if (reg == NO_REG) {
                                report(stack->top, "Illegal addressing mode\n");
                                free_tree(value);
                                return 0;
                            }

                            cp = skipwhite(cp);
                            if (*cp++ != ',') {
                                report(stack->top, "Illegal addressing mode\n");
                                return 0;
                            }

                            if (!get_mode(cp, &cp, &mode)) {
                                report(stack->top, "Illegal addressing mode\n");
                                free_tree(value);
                                return 0;
                            }

                            if ((mode.type & 070) == 0) {
                                report(stack->top, "JSR Rn,Rm is illegal\n");
                                /* But encode it anyway... */
                            }

                            word = op->value | mode.type | (reg << 6);
                            store_word(stack->top, tr, 2, word);
                            mode_extension(tr, &mode, stack->top);
                            free_tree(value);
                        }
                        return 1;

                    case OC_1REG:
                        /* One register (RTS) */  {
                            EX_TREE        *value;
                            unsigned        reg;

                            value = parse_expr(cp, 0);
                            cp = value->cp;
                            reg = get_register(value);
                            if (reg == NO_REG) {
                                report(stack->top, "Illegal addressing mode\n");
                                free_tree(value);
                                reg = 0;
                            }

                            store_word(stack->top, tr, 2, op->value | reg);
                            free_tree(value);
                        }
                        return 1;

                    case OC_1FIS:
                        /* One one gen and one reg 0-3 */  {
                            ADDR_MODE       mode;
                            EX_TREE        *value;
                            unsigned        reg;
                            unsigned        word;

                            if (!get_mode(cp, &cp, &mode)) {
                                report(stack->top, "Illegal addressing mode\n");
                                return 0;
                            }

                            cp = skipwhite(cp);
                            if (*cp++ != ',') {
                                report(stack->top, "Illegal addressing mode\n");
                                free_addr_mode(&mode);
                                return 0;
                            }

                            value = parse_expr(cp, 0);
                            cp = value->cp;

                            reg = get_register(value);
                            if (reg == NO_REG || reg > 4) {
                                report(stack->top, "Invalid destination register\n");
                                reg = 0;
                            }

                            word = op->value | mode.type | (reg << 6);
                            store_word(stack->top, tr, 2, word);
                            mode_extension(tr, &mode, stack->top);
                            free_tree(value);
                        }
                        return 1;

                    case OC_2FIS:
                        /* One reg 0-3 and one gen */  {
                            ADDR_MODE       mode;
                            EX_TREE        *value;
                            unsigned        reg;
                            unsigned        word;

                            value = parse_expr(cp, 0);
                            cp = value->cp;

                            reg = get_register(value);
                            if (reg == NO_REG || reg > 4) {
                                report(stack->top, "Illegal source register\n");
                                reg = 0;
                            }

                            cp = skipwhite(cp);
                            if (*cp++ != ',') {
                                report(stack->top, "Illegal addressing mode\n");
                                free_tree(value);
                                return 0;
                            }

                            if (!get_mode(cp, &cp, &mode)) {
                                report(stack->top, "Illegal addressing mode\n");
                                free_tree(value);
                                return 0;
                            }

                            word = op->value | mode.type | (reg << 6);
                            store_word(stack->top, tr, 2, word);
                            mode_extension(tr, &mode, stack->top);
                            free_tree(value);
                        }
                        return 1;

                    default:
                        report(stack->top, "Unimplemented instruction format\n");
                        return 0;
                    }                  /* end(handle an instruction) */
                }
                break;
            }                          /* end switch(section type) */
        }                              /* end if (op is a symbol) */
    }

    /* Only thing left is an implied .WORD directive */
    /*JH: fall through in case of illegal opcode, illegal label! */
    free(label);

    return do_word(stack, tr, cp, 2);
}

int get_next_lsb(
    void)
{
    if (lsb_used) {
        lsb_used = 0;
        if (enabl_debug && lstfile) {
            fprintf(lstfile, "get_next_lsb: lsb: %d becomes %d (= next_lsb)\n", lsb, next_lsb);
        }
        return next_lsb++;
    } else {
        if (enabl_debug && lstfile) {
            fprintf(lstfile, "get_next_lsb: lsb: stays %d\n", lsb);
        }
        return lsb;
    }
}

/* assemble_stack assembles the input stack.  It returns the error
   count. */

int assemble_stack(
    STACK *stack,
    TEXT_RLD *tr)
{
    int             res;
    int             errcount = 0;

    while ((res = assemble(stack, tr)) >= 0) {
        list_flush();
        if (res == 0)
            errcount++;                   /* Count an error */
    }

    return errcount;
}
