/*
HEADER:     ;
TITLE:      Frankenstein Cross Assemblers;
VERSION:    2.0;
DESCRIPTION: "  Reconfigurable Cross-assembler producing Intel (TM)
        Hex format object records.  ";
SYSTEM:     UNIX, MS-Dos ;
FILENAME:   fraosub.c;
WARNINGS:   "This software is in the public domain.  
        Any prior copyright claims are relinquished.  

        This software is distributed with no warranty whatever.  
        The author takes no responsibility for the consequences 
        of its use."  ;
SEE-ALSO:   frasmain.c;
AUTHORS:    Mark Zenier;
*/

/*
    description output pass utility routines
    history     September 27, 1987
            March 15, 1988   release 1.1 WIDTH
            September 14, 1990  Dosify, 6 char unique names
*/

#include "config.h"
#include <stdio.h>
#include "frasmdat.h"
#include "fragcon.h"
#include "icart/icartrom.h"
#include "icart/icartbin.h"

#define OUTRESULTLEN 256
#define NUMHEXPERL 16
#define SOURCEOFFSET 24
#define NUMHEXSOURCE 6

#define INTELLEN 32

int linenumber = 0;
char lineLbuff[INBUFFSZ];
int lineLflag = FALSE;
static int show_noncode_lines = TRUE;
static int show_coded_lines = TRUE;

#define LISTMODESTK (256)
static int listmodestk[LISTMODESTK];

static unsigned char    outresult[OUTRESULTLEN];
static unsigned short   outrs16  [OUTRESULTLEN / 2];
static int  nextresult;
static long     genlocctr, resultloc;

static unsigned mode_set = 0, mode_clr = 0, type_flag = 0;
static icartrom_t icart_rom;
static uint_16    rombuf[INTELLEN / 2];

static char *oeptr;

#define MAXIMPWID   24

static long widthmask[MAXIMPWID+1] =
{
/*  0 */    1L,
/*  1 */    1L,
/*  2 */    (1L <<  2 ) - 1,
/*  3 */    (1L <<  3 ) - 1,
/*  4 */    (1L <<  4 ) - 1,
/*  5 */    (1L <<  5 ) - 1,
/*  6 */    (1L <<  6 ) - 1,
/*  7 */    (1L <<  7 ) - 1,
/*  8 */    (1L <<  8 ) - 1,
/*  9 */    (1L <<  9 ) - 1,
/* 10 */    (1L << 10 ) - 1,
/* 11 */    (1L << 11 ) - 1,
/* 12 */    (1L << 12 ) - 1,
/* 13 */    (1L << 13 ) - 1,
/* 14 */    (1L << 14 ) - 1,
/* 15 */    (1L << 15 ) - 1,
/* 16 */    (1L << 16 ) - 1,
/* 17 */    (1L << 17 ) - 1,
/* 18 */    (1L << 18 ) - 1,
/* 19 */    (1L << 19 ) - 1,
/* 20 */    (1L << 20 ) - 1,
/* 21 */    (1L << 21 ) - 1,
/* 22 */    (1L << 22 ) - 1,
/* 23 */    (1L << 23 ) - 1,
/* 24 */    (1L << 24 ) - 1
};
    

static long dgethex()
/*
    description convert the character string pointed to by
            the output expression pointer to a long integer
    globals     oeptr, the output expression pointer
    return      the value
*/
{
    long rv = 0;

    while( *oeptr != '\0')
    {
        switch(*oeptr)
        {
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
            rv = (rv << 4) + ((*oeptr) - '0');
            break;

        case 'a':
        case 'b':
        case 'c':
        case 'd':
        case 'e':
        case 'f':
            rv = (rv << 4) + ((*oeptr) - 'a' + 10);
            break;
        
        case 'A':
        case 'B':
        case 'C':
        case 'D':
        case 'E':
        case 'F':
            rv = (rv << 4) + ((*oeptr) - 'A' + 10);
            break;

        default:
            return rv;
        }

        oeptr++;
    }

    return rv;
}

static void parsemode()
{
    int action = 0;
    unsigned set = 0, clr = 0, bit;

    while (*oeptr)
    {
        while (*oeptr && *oeptr == ',') oeptr++;
        bit = action = 0;

        do {
            while (*oeptr && strchr(" \t\n\r", *oeptr))
                oeptr++;

            if (!*oeptr) break;

            if (!action) { action = *oeptr++; continue; }
            switch (*oeptr++)
            {
                case 'R': case 'r': bit |= ICARTROM_READ;    break;
                case 'W': case 'w': bit |= ICARTROM_WRITE;   break;
                case 'N': case 'n': bit |= ICARTROM_NARROW;  break;
                case 'B': case 'b': bit |= ICARTROM_BANKSW;  break;
                case '-': case '+': case '=':
                    frp2error("Mode syntax: Action char where mode "
                             "char expected"); break;
                default:
                    frp2error("Mode syntax: Unknown mode character"); break;
            }
        } while (*oeptr && *oeptr != ',');

        if (!action) break;

        switch (action)
        {
            case '+': { set |=  bit; clr &= ~bit; break; } 
            case '-': { set &= ~bit; clr |=  bit; break; } 
            case '=': { set  =  bit; clr  = ~bit; break; } 
            case 'R' : case 'W' : case 'B' : case 'N':
            case 'r' : case 'w' : case 'b' : case 'n':
                frp2error("Mode syntax: Missing action character"); break;
            default:
                frp2error("Mode syntax: Unknown action character"); break;
        }
    }

    mode_set = set & 0xF;
    mode_clr = clr & 0xF;
}

static void markrange(int startaddr, int endaddr)
{
    icartrom_addseg(&icart_rom, NULL, startaddr, endaddr - startaddr, 
                    mode_set, mode_clr);
}
        
outphase()
/*
    description process all the lines in the intermediate file
    globals     the input line
            the output expression pointer
            line number
            file name
            the binary output array and counts
*/
{
    int firstchar;

    for(;;)
    {
        if((firstchar = fgetc(intermedf)) == EOF)
            break;

        if(firstchar == 'L')
        {
            if(listflag)
                flushlisthex();

            if( fgets(&lineLbuff[1], INBUFFSZ-1, intermedf) == (char *)NULL)
            {
                frp2error( "error or premature end of intermediate file");
                break;
            }

            lineLflag = show_coded_lines;
        }
        else
        {
            finbuff[0] = firstchar;
            if(fgets( &finbuff[1], INBUFFSZ-1, intermedf) == (char *)NULL)
            {
                frp2error("error or premature end of intermediate file");
                break;
            }
        }
    
        switch(firstchar)
        {
        case 'N':
            {
                int i, lm;
                lm = atoi(&finbuff[2]);

                if (lm < 3)
                {
                    for (i = 1; i < LISTMODESTK - 1; i++)
                        listmodestk[i] = listmodestk[i + 1];
                    listmodestk[0]             = 0;
                    listmodestk[LISTMODESTK-1] = lm;
                } else
                {
                    for (i = LISTMODESTK - 1; i > 0; i--)
                        listmodestk[i] = listmodestk[i - 1];
                    lm = listmodestk[LISTMODESTK-1];
                }

                switch (lm)
                {
                case 0: lineLflag          = show_noncode_lines;
                        show_coded_lines   = TRUE;
                        show_noncode_lines = TRUE;  break;
                case 1: lineLflag          = FALSE;
                        show_coded_lines   = TRUE;
                        show_noncode_lines = FALSE; break;
                case 2: lineLflag          = FALSE;
                        show_coded_lines   = FALSE;
                        show_noncode_lines = FALSE; break;
                }
/*fprintf(loutf, "listing mode: %d %d %d '%s'\n", lm, show_coded_lines, show_noncode_lines, lineLbuff + 1);*/
            }
            break;

        case 'E': /* error */
            if(listflag)
                flushsourceline();

            fputs(&finbuff[2], loutf);

            break;

        case 'L': /* listing */
            linenumber++;
            break;

        case 'C': /* comment / uncounted listing */
            if(listflag && show_noncode_lines)
            {
                char *stuff = strchr(finbuff, '\n');

                if(stuff != NULL)
                    *stuff = '\0';

                fprintf(loutf,"%-*.*s", 
                 SOURCEOFFSET, SOURCEOFFSET, &finbuff[2]);
                if(lineLflag)
                {
                    fputs(&lineLbuff[2], loutf);
                    lineLflag = FALSE;
                }
                else
                {
                    fputc('\n', loutf);
                }
            } else
                lineLflag = FALSE;
            break;

        case 'P': /* location set */
            oeptr = &finbuff[2];
            currseg = dgethex();
            oeptr++;
            genlocctr = locctr = dgethex();
            oeptr++;
            type_flag = dgethex();
            oeptr++;
            parsemode();
            break;

        case 'R': /* reserve range */
            oeptr = &finbuff[2];
            markrange(genlocctr, dgethex());
            break;

        case 'M':
        {
            long lo, hi;
            unsigned old_set = mode_set, old_clr = mode_clr;
            oeptr = &finbuff[2];
            lo = dgethex(); oeptr++;
            hi = dgethex(); oeptr++;
            parsemode();
            markrange(lo, hi);
            mode_set = old_set;
            mode_clr = old_clr;
            break;
        }
        
        case 'D': /* data */
            oeptr = &finbuff[2];
            nextresult = 0;
            resultloc = genlocctr;
            outeval();
            if(hexflag)
                outhexblock();
            if(listflag)
                listhex();
            break;
        
        case 'F': /* file start */
            {
                char *tp;
                if( (tp = strchr(finbuff,'\n')) != (char *)NULL)
                    *tp = '\0';
                strncpy(currentfnm, &finbuff[2], 100);
                currentfnm[99] = '\0';
            }
            lnumstk[currfstk++] = linenumber;
            linenumber = 0;
            break;
        
        case 'X': /* file resume */
            {
                char *tp;
                if( (tp = strchr(finbuff,'\n')) != (char *)NULL)
                    *tp = '\0';
                strncpy(currentfnm, &finbuff[2], 100);
                currentfnm[99] = '\0';
            }
            linenumber = lnumstk[--currfstk];
            break;

        default:
            frp2error("unknown intermediate file command");
            break;
        }
    }

    if(hexflag)
        flushhex();

    if(listflag)
        flushlisthex();
}

outeval()
/*
    description convert the polish form character string in the 
            intermediate file 'D' line to binary values in the
            output result array.
    globals     the output expression pointer
            the output result array
*/
{
    register long etop = 0;
    register struct evstkel *estkm1p = &estk[0];
    int offset = 0;

    while( *oeptr != '\0')
    {
        switch(*oeptr)
        {
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
            etop = (etop << 4) + ((*oeptr) - '0');
            break;

        case 'a':
        case 'b':
        case 'c':
        case 'd':
        case 'e':
        case 'f':
            etop = (etop << 4) + ((*oeptr) - 'a' + 10);
            break;
        
        case 'A':
        case 'B':
        case 'C':
        case 'D':
        case 'E':
        case 'F':
            etop = (etop << 4) + ((*oeptr) - 'A' + 10);
            break;

#include "fraeuni.h"
#include "fraebin.h"
        case IFC_SYMB:
            {
                struct symel *tsy;

                tsy = symbindex[etop];
                if(tsy->seg <= 0)
                {
                    frp2undef(tsy);
                    etop = 0;
                }
                else
                {
                    if(tsy->seg == SSG_EQU ||
                       tsy->seg == SSG_SET)
                    {
                        frp2warn( "forward reference to SET/EQU symbol");
                    }
                    etop = tsy->value;
                }
            }
            break;

        case IFC_CURRLOC: 
            etop = genlocctr + (offset / 2);
            break;

        case IFC_PROGCTR:
            etop = genlocctr;
            break;

        case IFC_DUP:
            if(estkm1p >= &estk[PESTKDEPTH-1])
            {
                frp2error("expression stack overflow");
            }
            else
            {
                (++estkm1p)->v = etop;
            }
            break;

        case IFC_LOAD:
            if(estkm1p >= &estk[PESTKDEPTH-1])
            {
                frp2error("expression stack overflow");
            }
            else
            {
                (++estkm1p)->v = etop;
            }
            etop = 0;
            break;

        case IFC_CLR:
            etop = 0;
            break;

        case IFC_CLRALL:
            etop = 0;
            estkm1p = &estk[0];
            break;

        case IFC_POP:
            etop = (estkm1p--)->v;
            break;

        case IFC_TESTERR:
            if(etop)
            {
                frp2error("expression fails validity test");
            }
            break;

        case IFC_SWIDTH:
            if( etop > 0 && etop <= MAXIMPWID)
            {
                if( estkm1p->v < -(widthmask[etop-1]+1) ||
                    estkm1p->v > widthmask[etop-1] )
                {
                    frp2error("expression exceeds available field width");
                }
                etop = ((estkm1p--)->v)  & widthmask[etop];
            }
            else
            {
                frp2error("unimplemented width");
            }
            break;

        case IFC_WIDTH:
            if( etop > 0 && etop <= MAXIMPWID)
            {
                if( estkm1p->v < -(widthmask[etop-1]+1) ||
                    estkm1p->v > widthmask[etop] )
                {
                    frp2error("expression exceeds available field width");
                }
                etop = ((estkm1p--)->v)  & widthmask[etop];
            }
            else
            {
                frp2error("unimplemented width");
            }
            break;

        case IFC_IWIDTH:
            if( etop > 0 && etop <= MAXIMPWID)
            {
                if( estkm1p->v < 0 ||
                    estkm1p->v > widthmask[etop] )
                {
                    frp2error("expression exceeds available field width");
                }
                etop = ((estkm1p--)->v)  & widthmask[etop];
            }
            else
            {
                frp2error("unimplemented width");
            }
            break;

        case IFC_EMU8:
            if( etop >= -128 && etop <= 255)
            {
                outresult[nextresult++] = etop & 0xff;
            }
            else
            {
                outresult[nextresult++] = 0;
                frp2error("expression exceeds available field width");
            }
            offset++;
            etop = 0;
            break;

        case IFC_EMS7:
            if(etop >= -128 && etop <= 127)
            {
                outresult[nextresult++] = etop & 0xff;
            }
            else
            {
                outresult[nextresult++] = 0;
                frp2error("expression exceeds available field width");
            }
            offset++;
            etop = 0;
            break;

        case IFC_EM16:
            if(etop >= -32768L && etop <= 65535L)
            {
                outresult[nextresult++] = (etop >> 8) & 0xff;
                outresult[nextresult++] = etop & 0xff;
            }
            else
            {
                outresult[nextresult++] = 0;
                outresult[nextresult++] = 0;
                frp2error("expression exceeds available field width");
            }
            offset += 2;
            etop = 0;
            break;

        case IFC_EMBR16:
            if(etop >= -32768L && etop <= 65535L)
            {
                outresult[nextresult++] = etop & 0xff;
                outresult[nextresult++] = (etop >> 8) & 0xff;
            }
            else
            {
                outresult[nextresult++] = 0;
                outresult[nextresult++] = 0;
                frp2error("expression exceeds available field width");
            }
            offset += 2;
            etop = 0;
            break;

        default:
            break;
        }
        oeptr++;
    }

    genlocctr += offset / 2;
}

static long lhaddr, lhnextaddr;
static int lhnew, lhnext = 0;
static unsigned char listbuffhex[NUMHEXPERL];

flushlisthex()
/*
    description output the residue of the hexidecimal values for
            the previous assembler statement.
    globals     the new hex list flag
*/
{
    listouthex();
    lhnew = TRUE;
}

listhex()
/*
    description buffer the output result to block the hexidecimal 
            listing on the output file to NUMHEXPERL bytes per
            listing line.
    globals     The output result array and count
            the hex line buffer and counts
*/
{
    register int cht;
    register long inhaddr = resultloc;

    if(lhnew)
    {
        lhaddr = lhnextaddr = resultloc;
        lhnew = FALSE;
    }

    for(cht = 0; cht < nextresult; cht += 2)
    {
        if(lhnextaddr != inhaddr 
         || lhnext >= (lineLflag ? NUMHEXSOURCE : NUMHEXPERL ) )
        {
            listouthex();
            lhaddr = lhnextaddr = inhaddr;
        }
        listbuffhex[lhnext++] = outresult[cht];
        listbuffhex[lhnext++] = outresult[cht+1];
        lhnextaddr++;
        inhaddr++;
    }
}

listouthex()
/*
    description print a line of hexidecimal on the listing
    globals     the hex listing buffer
*/
{
    register int cn;
    register int tc;

    if(lhnext > 0)
    {
        fputc(hexch((int)lhaddr>>12), loutf);
        fputc(hexch((int)lhaddr>>8), loutf);
        fputc(hexch((int)lhaddr>>4), loutf);
        fputc(hexch((int)lhaddr), loutf);
        fputc(' ', loutf);

        for(cn = 0; cn < lhnext; cn += 2)
        {
            fputc(hexch((int)(tc = listbuffhex[cn])>>4), loutf);
            fputc(hexch(tc), loutf);
            fputc(hexch((int)(tc = listbuffhex[cn+1])>>4), loutf);
            fputc(hexch(tc), loutf);
            fputc(' ', loutf);
        }

        if( ! lineLflag)
            fputc('\n', loutf);
    } else
    {
        if (!show_noncode_lines)
            lineLflag = FALSE;
    }

    if(lineLflag)
    {
        if(lineLbuff[2] != '\n')
        {
            switch(lhnext)
            {
            case 0:
            case 1:
                fputs("\t\t\t",loutf);
                break;
            case 2:
            case 3:
            case 4:
                fputs("\t\t",loutf);
                break;
            case 5:
            case 6:
                fputs("\t",loutf);
                break;
            default:
                break;
            }

            fputs(&lineLbuff[2], loutf);
            lineLflag = FALSE;
        }
        else
        {
            fputc('\n', loutf);
        }
    }
        
    lhnext = 0;
}

static long nextoutaddr, blockaddr;
static int hnextsub;
static char hlinebuff[INTELLEN];
extern unsigned long memory_bitmap[65536 >> 5];


outhexblock()
/*
    description buffer the output result to group adjacent output
                data into longer lines.
    globals     the output result array
                the intel hex line buffer
*/
{
    int i;

    blockaddr = resultloc;
    nextoutaddr = blockaddr + currseg;

    for (i = 0; i < nextresult; i += 2)
        outrs16[i>>1] = (outresult[i] << 8) | (0xFF & outresult[i + 1]);
        
    icartrom_addseg(&icart_rom, outrs16, nextoutaddr, nextresult >> 1, 
                    mode_set, mode_clr);

    for (i = nextoutaddr; i < nextoutaddr + (nextresult>>1); i++)
        memory_bitmap[i >> 5] |= 1 << (i & 31);
}


flushhex()
/*
    description flush the intel hex line buffer at the end of
            the second pass
    globals     the intel hex line buffer
*/
{
    uint_32 size;
    uint_8  *rom_img;

#if 0
    if(hnextsub > 0)
        intelout(0, blockaddr, hnextsub, hlinebuff);
    if(endsymbol != SYMNULL && endsymbol->seg > 0)
        intelout(1, endsymbol->value, 0, "");
    else
        intelout(1, 0L, 0, "");
#endif
        
    if (binoutf && cfgoutf)
    {
        icb_write_bincfg(binoutf, cfgoutf, &icart_rom, 0);
    } 

    if (romoutf)
    {
        rom_img = icartrom_genrom(&icart_rom, &size);
        if (rom_img)
        {
            fwrite(rom_img, 1, size, romoutf);
            fflush(romoutf);
        }
        free(rom_img);
    }
}


#if 0
intelout(type, addr, count, data)
    int type;
    long addr;
    int count;
    char data[];
/*
    description print a line of intel format hex data to the output
            file
    parameters  see manual for record description
*/
{
#if 0
    register int temp, checksum;

    fputc(':', hexoutf);
    fputc(hexch(count>>4),hexoutf);
    fputc(hexch(count),hexoutf);
    fputc(hexch((int)addr>>12),hexoutf);
    fputc(hexch((int)addr>>8),hexoutf);
    fputc(hexch((int)addr>>4),hexoutf);
    fputc(hexch((int)addr),hexoutf);
    fputc(hexch(type>>4),hexoutf);
    fputc(hexch(type),hexoutf);

    checksum = ((addr >> 8) & 0xff) + (addr & 0xff) + (count & 0xff);
    checksum += type & 0xff;

    for(temp = 0; temp < count; temp ++)
    {
        checksum += data[temp] & 0xff;
        fputc(hexch(data[temp] >> 4), hexoutf);
        fputc(hexch(data[temp]), hexoutf);
    }

    checksum = (-checksum) & 0xff;
    fputc(hexch(checksum>>4), hexoutf);
    fputc(hexch(checksum), hexoutf);
    fputc('\n',hexoutf);
#else
//    fwrite(data, count, 1, hexoutf);
    int i;

    if (count > 0)
    {
        for (i = 0; i < (count >> 1); i++)
            rombuf[i] = (data[2*i] << 8) | (0xFF & data[2*i + 1]);
    }
#endif

}
#endif


frp2undef(symp)
    struct symel * symp;
/*
    description second pass - print undefined symbol error message on
            the output listing device.  If the the listing flag
            is false, the output device is the standard output, and
            the message format is different.
    parameters  a pointer to a symbol table element
    globals     the count of errors
*/
{
    if(listflag)
        flushsourceline();

    fprintf(loutf, "%s:%d: ERROR - undefined symbol  %s\n", 
            currentfnm, linenumber, symp->symstr);

    errorcnt++;
}

frp2warn(str)
    char * str;
/*
    description second pass - print a warning message on the listing
            file, varying the format for console messages.
    parameters  the message
    globals     the count of warnings
*/
{
    if(listflag)
        flushsourceline();

    fprintf(loutf, "%s:%d: WARNING - %s\n", currentfnm, linenumber, str);

    warncnt++;
}


frp2error(str)
    char * str;
/*
    description second pass - print a message on the listing file
    parameters  message
    globals     count of errors
*/
{
    flushsourceline();

    fprintf(loutf, "%s:%d: ERROR - %s\n", 
            currentfnm, linenumber, str);

    errorcnt++;
}

flushsourceline()
/*
    description flush listing line buffer before an error for
            that line is printed
*/
{
    if(listflag && lineLflag && show_noncode_lines)
    {
        fputs("\t\t\t", loutf);
        fputs(&lineLbuff[2], loutf);
    }
    lineLflag = FALSE;
}
