/****************************************************************************
*
*  This code is Public Domain.
*
*  ========================================================================
*
* Description:  listing support
*
****************************************************************************/

#include <stdarg.h>
#include <ctype.h>

#include "globals.h"
#include "memalloc.h"
#include "parser.h"
#include "reswords.h"
#include "segment.h"
#include "tokenize.h"
#include "macro.h"
#include "fastpass.h"
#include "listing.h"
#include "input.h"
#include "msgtext.h"
#include "types.h"
#include "omfspec.h"

#define CODEBYTES 9
#define OFSSIZE 8
#define PREFFMTSTR "25"
#define USELSLINE 1 /* also in assemble.c! */

#ifdef __UNIX__
#define NLSIZ 1
#define NLSTR "\n"
#else
#define NLSIZ 2
#define NLSTR "\r\n"
#endif

extern int_32  LastCodeBufSize;
#if STACKBASESUPP==0
extern enum special_token basereg[];
#endif

uint_32 list_pos; /* current pos in LST file */

#define DOTSMAX 32
static const char  dots[] = " . . . . . . . . . . . . . . . .";

static const char  szFmtProcStk[] = "  %s %s        %-17s %s %c %04" I32_SPEC "X";

enum list_strings {
#define ltext( index, string ) LS_ ## index,
#include "ltext.h"
#undef ltext
};

static const char * const strings[] = {
#define ltext( index, string ) string ,
#include "ltext.h"
#undef ltext
};

static const char szCount[] = "count";

/* cref definitions */

enum list_queues {
    LQ_MACROS,
    LQ_STRUCTS,
#ifdef DEBUG_OUT
    LQ_UNDEF_TYPES,
#endif
    LQ_RECORDS,
    LQ_TYPEDEFS,
    LQ_SEGS,
    LQ_GRPS,
    LQ_PROCS,
    LQ_LAST
};

enum pr_flags {
    PRF_ADDSEG = 0x01
};

struct print_item {
    short type;
    short flags;
    const short *capitems;
    void (*function)();
};


static const short maccap[] = { LS_TXT_MACROS,  LS_TXT_MACROCAP  ,0 };
static const short strcap[] = { LS_TXT_STRUCTS, LS_TXT_STRUCTCAP, 0 };
//static const short reccap[] = { LS_TXT_RECORDS, LS_TXT_RECORDCAP1, -1, LS_TXT_RECORDCAP2, 0 };
static const short reccap[] = { LS_TXT_RECORDS, LS_TXT_RECORDCAP, 0 };
static const short tdcap[]  = { LS_TXT_TYPEDEFS,LS_TXT_TYPEDEFCAP, 0 };
static const short segcap[] = { LS_TXT_SEGS,    LS_TXT_SEGCAP, 0 };
static const short prccap[] = { LS_TXT_PROCS,   LS_TXT_PROCCAP, 0 };

static void log_macro(   const struct asym * );
static void log_struct(  const struct asym *, const char *name, int_32 );
static void log_record(  const struct asym * );
static void log_typedef( const struct asym * );
static void log_segment( const struct asym *, const struct asym *group );
static void log_group(   const struct asym *, const struct dsym * );
static void log_proc(    const struct asym * );

static const struct print_item cr[] = {
    { LQ_MACROS,          0, maccap, log_macro   },
    { LQ_STRUCTS,         0, strcap, log_struct  },
#ifdef DEBUG_OUT
    { LQ_UNDEF_TYPES,     0, strcap, log_struct  },
#endif
    { LQ_RECORDS,         0, reccap, log_record  },
    { LQ_TYPEDEFS,        0, tdcap,  log_typedef },
    { LQ_SEGS,            0, segcap, log_segment },
    { LQ_GRPS,   PRF_ADDSEG, NULL,   log_group   },
    { LQ_PROCS,           0, prccap, log_proc    },
};

struct lstleft {
    struct lstleft *next;
    char buffer[4*8];
    char last;
};

/* write a source line to the listing file
 * global variables used inside:
 *  CurrSource:    the - expanded - source line
 *  CurrSeg:       current segment
 *  GeneratedCode: flag if code is generated
 *  MacroLevel:    macro depth
 *
 */

void LstWrite( enum lsttype type, uint_32 oldofs, void *value )
/*************************************************************/
{
    uint_32 newofs;
    struct asym *sym = value;
    int     len;
    int     len2;
    int     idx;
    int     srcfile;
    char    *p1;
    char    *p2;
    char    *pSrcline;
    struct lstleft *pll;
    struct lstleft ll;
    //char    buffer2[MAX_LINE_LEN]; /* stores text macro value */

    if ( ModuleInfo.list == FALSE || CurrFile[LST] == NULL || ( ModuleInfo.line_flags & LOF_LISTED ) )
        return;
    if ( ModuleInfo.GeneratedCode && ( ModuleInfo.list_generated_code == FALSE ) )
        return;
    if ( MacroLevel ) {
        switch ( ModuleInfo.list_macro ) {
        case LM_NOLISTMACRO:
            return;
        case LM_LISTMACRO:
            /* todo: filter certain macro lines */
            break;
        }
    }

    ModuleInfo.line_flags |= LOF_LISTED;

    DebugMsg1(("LstWrite( %u, %" I32_SPEC "u ): enter [ pos=%" I32_SPEC "u, GeneratedCode=%u, MacroLevel=%u ]\n", type, oldofs, list_pos, ModuleInfo.GeneratedCode, MacroLevel ));
    pSrcline = CurrSource;
#if FASTPASS
    if ( ( Parse_Pass > PASS_1 ) && UseSavedState ) {
        if ( ModuleInfo.GeneratedCode == 0 ) {
            if ( !( ModuleInfo.line_flags & LOF_SKIPPOS ) )
                list_pos = LineStoreCurr->list_pos;
#if USELSLINE /* either use CurrSource + CurrComment or LineStoreCurr->line (see assemble.c, OnePass() */
            pSrcline = LineStoreCurr->line;
            if ( ModuleInfo.CurrComment ) { /* if comment was removed, readd it! */
                *( LineStoreCurr->line + strlen( LineStoreCurr->line ) ) = ';';
                ModuleInfo.CurrComment = NULL;
            }
#endif
            DebugMsg1(("LstWrite: Pass=%u, stored pos=%" I32_SPEC "u\n", Parse_Pass+1, list_pos ));
        }
        fseek( CurrFile[LST], list_pos, SEEK_SET );
    }
#endif

    ll.next = NULL;
    memset( ll.buffer, ' ', sizeof( ll.buffer ) );
    srcfile = get_curr_srcfile();

    switch ( type ) {
    case LSTTYPE_DATA:
        if ( Parse_Pass == PASS_1 && Options.first_pass_listing == FALSE ) {
            break;
        }
        /* no break */
    case LSTTYPE_CODE:
        newofs = GetCurrOffset();
        sprintf( ll.buffer, "%08" I32_SPEC "X", oldofs );
        ll.buffer[OFSSIZE] = ' ';

        if ( CurrSeg == NULL )
            break;
        //if ( write_to_file == FALSE )
        if ( Options.first_pass_listing ) {
            if ( Parse_Pass > PASS_1 )
                break;
#ifdef DEBUG_OUT
        } else if ( Options.max_passes == 1 ) {
            ; /* write a listing in pass 1 */
#endif
        } else if ( Parse_Pass == PASS_1 )  /* changed v1.96 */
            break;

        len = CODEBYTES;
        p2 = ll.buffer + OFSSIZE + 2;

        if ( CurrSeg->e.seginfo->CodeBuffer == NULL ||
            CurrSeg->e.seginfo->written == FALSE ) {
            while ( oldofs < newofs && len ) {
                *p2++ = '0';
                *p2++ = '0';
                oldofs++;
                len--;
            }
            break;
        }

        /* OMF hold just a small buffer for one LEDATA record */
        /* if it has been flushed, use LastCodeBufSize */
        idx = (CurrSeg->e.seginfo->current_loc - CurrSeg->e.seginfo->start_loc)
            - (newofs - oldofs);
        if ( Options.output_format == OFORMAT_OMF ) {
            /* v2.11: additional check to make the hack more robust [ test case:  db 800000h dup (0) ] */
            if ( ( idx+LastCodeBufSize ) < 0 )
                break; /* just exit. The code bytes area will remain empty */
            while ( idx < 0 && len ) {
                sprintf( p2, "%02X", CurrSeg->e.seginfo->CodeBuffer[idx+LastCodeBufSize] );
                p2 += 2;
                idx++;
                oldofs++;
                len--;
            }
        } else if (idx < 0)
            idx = 0;

        while ( oldofs < newofs && len ) {
            sprintf( p2, "%02X", CurrSeg->e.seginfo->CodeBuffer[idx] );
            p2 += 2;
            idx++;
            oldofs++;
            len--;
        }
        *p2 = ' ';
        break;
    case LSTTYPE_EQUATE:
        /* v2.10: display current offset if equate is an alias for a label in this segment */
        idx = 1;
        if ( sym->segment && sym->segment == &CurrSeg->sym ) {
            sprintf( ll.buffer, "%08" I32_SPEC "X", GetCurrOffset() );
            idx = 10;
        }
        ll.buffer[idx] = '=';
#if AMD64_SUPPORT
        if ( sym->value3264 != 0 && ( sym->value3264 != -1 || sym->value >= 0 ) )
            /* v2.13: the addition & shift did not work. */
            //sprintf( &ll.buffer[idx+2], "%-" PREFFMTSTR I64_SPEC "X", (uint_64)sym->value + ( (uint_64)sym->value3264 << 32 ) );
            sprintf( &ll.buffer[idx+2], "%-" PREFFMTSTR I64_SPEC "X", (uint_64)sym->value3264 * 0x100000000 + sym->uvalue );
        else
#endif
            sprintf( &ll.buffer[idx+2], "%-" PREFFMTSTR I32_SPEC "X", sym->value );
        ll.buffer[28] = ' ';
        break;
    case LSTTYPE_TMACRO:
        ll.buffer[1] = '=';
        //GetLiteralValue( buffer2, sym->string_ptr );
        //strcpy( buffer2, sym->string_ptr );
        for ( p1 = sym->string_ptr, p2 = &ll.buffer[3], pll = &ll; *p1; ) {
            if ( p2 >= &pll->buffer[28] ) {
                struct lstleft *next = myalloca( sizeof( struct lstleft ) );
                pll->next = next;
                pll = next;
                pll->next = NULL;
                memset( pll->buffer, ' ', sizeof( pll->buffer) );
                p2 = &pll->buffer[3];
            }
            *p2++ = *p1++;
        }
        break;
    case LSTTYPE_MACROLINE:
        ll.buffer[1] = '>';
        pSrcline = value;
        break;
    case LSTTYPE_LABEL:
        oldofs = GetCurrOffset();
        /* no break */
    case LSTTYPE_STRUCT:
        sprintf( ll.buffer, "%08" I32_SPEC "X", oldofs );
        ll.buffer[8] = ' ';
        break;
    case LSTTYPE_DIRECTIVE:
        if ( CurrSeg || value ) {
            sprintf( ll.buffer, "%08" I32_SPEC "X", oldofs );
            ll.buffer[8] = ' ';
        }
        break;
    default: /* LSTTYPE_MACRO */
        if ( *pSrcline == NULLC && ModuleInfo.CurrComment == NULL && srcfile == ModuleInfo.srcfile ) {
            DebugMsg1(("LstWrite: type=%u, writing CRLF\n", type ));
            fwrite( NLSTR, 1, NLSIZ, CurrFile[LST] );
            list_pos += NLSIZ;
            return;
        }
        break;
    }

#if FASTPASS
    if ( Parse_Pass == PASS_1 || UseSavedState == FALSE ) {
#endif
        idx = sizeof( ll.buffer );
        if ( ModuleInfo.GeneratedCode )
            ll.buffer[28] = '*';
        if ( MacroLevel ) {
            len = sprintf( &ll.buffer[29], "%u", MacroLevel );
            ll.buffer[29+len] = ' ';
        }
        if ( srcfile != ModuleInfo.srcfile ) {
            ll.buffer[30] = 'C';
        }
#ifdef DEBUG_OUT
        ll.last = NULLC;
#endif
#if FASTPASS
    } else {
        idx = OFSSIZE + 2 + 2 * CODEBYTES;
#ifdef DEBUG_OUT
        ll.buffer[idx] = NULLC;
#endif
    }
#endif
    fwrite( ll.buffer, 1, idx, CurrFile[LST] );

    len = strlen( pSrcline );
    len2 = ( ModuleInfo.CurrComment ? strlen( ModuleInfo.CurrComment ) : 0 );

    list_pos += sizeof( ll.buffer ) + len + len2 + NLSIZ;
    DebugMsg1(("LstWrite: writing (%u b) >%s< [%u/%u], new pos=%" I32_SPEC "u\n", idx, ll.buffer, len, len2, list_pos ));

    /* write source and comment part */
#if FASTPASS
    if ( Parse_Pass == PASS_1 || UseSavedState == FALSE ) {
#endif
        if ( len )
            fwrite( pSrcline, 1, len, CurrFile[LST] );
        if ( len2 ) {
            fwrite( ModuleInfo.CurrComment, 1, len2, CurrFile[LST] );
            DebugMsg1(("LstWrite: writing (%u b) >%s%s<\n", len + len2 + NLSIZ, pSrcline, ModuleInfo.CurrComment ));
        }
#ifdef DEBUG_OUT
        else DebugMsg1(("LstWrite: writing (%u b) >%s<\n", len + NLSIZ, pSrcline ));
#endif
        fwrite( NLSTR, 1, NLSIZ, CurrFile[LST] );
#if FASTPASS
    }
#endif

    /* write optional additional lines.
     * currently works in pass one only.
     */
    for ( pll = ll.next; pll; pll = pll->next ) {
        fwrite( pll->buffer, 1, 32, CurrFile[LST] );
        fwrite( NLSTR, 1, NLSIZ, CurrFile[LST] );
        list_pos += 32 + NLSIZ;
        DebugMsg1(("LstWrite: additional line >%s<, new pos=%" I32_SPEC "u\n", pll->buffer, list_pos ));
    }
    return;
}

void LstWriteSrcLine( void )
/**************************/
{
    LstWrite( LSTTYPE_MACRO, 0, NULL );
}

void LstPrintf( const char *format, ... )
/***************************************/
{
    va_list     args;

    if( CurrFile[LST] ) {
        va_start( args, format );
        list_pos += vfprintf( CurrFile[LST], format, args );
        va_end( args );
    }
}

void LstNL( void )
/****************/
{
    if( CurrFile[LST] ) {
        fwrite( NLSTR, 1, NLSIZ, CurrFile[LST] );
        list_pos += NLSIZ;
    }
}

/* set the list file's position
 * this is only needed if generated code is to be
 * executed BEFORE the original source line is listed.
 */

#if FASTPASS
void LstSetPosition( void )
/*************************/
{
    if( CurrFile[LST] && ( Parse_Pass > PASS_1 ) && UseSavedState && ModuleInfo.GeneratedCode == 0 ) {
        list_pos = LineStoreCurr->list_pos;
        fseek( CurrFile[LST], list_pos, SEEK_SET );
        ModuleInfo.line_flags |= LOF_SKIPPOS;
    }
}
#endif

static const char *get_seg_align( const struct seg_info *seg, char *buffer )
/**************************************************************************/
{
    switch( seg->alignment ) {
    case 0:    return( strings[LS_BYTE]  );
    case 1:    return( strings[LS_WORD]  );
    case 2:    return( strings[LS_DWORD] );
    case 3:    return( strings[LS_QWORD] );
    case 4:    return( strings[LS_PARA]  );
    case 8:    return( strings[LS_PAGE]  );
    case MAX_SEGALIGNMENT:
               return( strings[LS_ABS]   );
    default:
        sprintf( buffer, "%u", 1 << seg->alignment );
        return( buffer );
    }
}

static const char *get_seg_combine( const struct seg_info *seg )
/**************************************************************/
{
    switch( seg->combine ) {
    case COMB_INVALID:    return( strings[LS_PRIVATE] );
    case COMB_STACK:      return( strings[LS_STACK]   );
    case COMB_ADDOFF:     return( strings[LS_PUBLIC]  );
    /* v2.06: added */
    case COMB_COMMON:     return( strings[LS_COMMON]  );
    }
    return( "?" );
}

static void log_macro( const struct asym *sym )
/*********************************************/
{
    int i = sym->name_size;
    const char *pdots;
    const char *type = (sym->isfunc) ? strings[LS_FUNC] : strings[LS_PROC];

    pdots = ((i >= DOTSMAX) ? "" : dots + i + 1 );
    LstPrintf( "%s %s        %s", sym->name, pdots ,type );
#ifdef DEBUG_OUT
    LstPrintf( " %5lu", ((struct dsym *)sym)->e.macroinfo->count );
#endif
    LstNL();
    return;
}

static const char *SimpleTypeString( enum memtype mem_type )
/**********************************************************/
{
    int size = ( mem_type & MT_SIZE_MASK ) + 1;
    switch ( size ) {
    case 1: return( strings[LS_BYTE] );
    case 2: return( strings[LS_WORD] );
    case 4: return( strings[LS_DWORD] );
    case 6: return( strings[LS_FWORD] );
    case 8: return( strings[LS_QWORD] );
    case 10:return( strings[LS_TBYTE] );
    case 16:return( strings[LS_OWORD] );
    }
    return( "" );
}

/* called by log_struct and log_typedef
 * that is, the symbol is ensured to be a TYPE!
 * argument 'buffer' is either NULL or "very" large ( StringBufferEnd ).
 */

static const char *GetMemtypeString( const struct asym *sym, char *buffer )
/*************************************************************************/
{
    const char *p;
    enum memtype mem_type;

    if ( (sym->mem_type & MT_SPECIAL) == 0 )
        return( SimpleTypeString( sym->mem_type ) );

    /* v2.05: improve display of stack vars */
    mem_type = sym->mem_type;
    if ( sym->state == SYM_STACK && sym->is_ptr )
        mem_type = MT_PTR;

    switch ( mem_type ) {
    case MT_PTR:
#if AMD64_SUPPORT
        if ( sym->Ofssize == USE64 )
            p = strings[LS_NEAR];
        else
#endif
            if ( sym->isfar )
                p = strings[LS_FAR16 + sym->Ofssize];
            else
                p = strings[LS_NEAR16 + sym->Ofssize];

        if ( buffer ) { /* Currently, 'buffer' is only != NULL for typedefs */
            int i;
            char *b2 = buffer;
            /* v2.10: improved pointer TYPEDEF display */
            for ( i = sym->is_ptr; i; i-- ) {
                b2 += sprintf( b2, "%s %s ", p, strings[LS_PTR] );
            }
            /* v2.05: added. */
            if ( sym->state == SYM_TYPE && sym->typekind == TYPE_TYPEDEF ) {
                //strcat( buffer, " ");
                if ( sym->target_type )
                    strcpy( b2, sym->target_type->name );
                else if ( ( sym->ptr_memtype & MT_SPECIAL ) == 0 )
                    strcpy( b2, SimpleTypeString( sym->ptr_memtype ) );
            }
            return( buffer );
        }
        return( p );
    case MT_FAR:
        if ( sym->segment )
            return( strings[LS_LFAR] );
        return( strings[LS_LFAR16 + GetSymOfssize( sym )] );
    case MT_NEAR:
        if ( sym->segment )
            return( strings[LS_LNEAR] );
        return( strings[LS_LNEAR16 + GetSymOfssize( sym )] );
    case MT_TYPE:
        if ( *(sym->type->name) )  /* there are a lot of unnamed types */
            return( sym->type->name );
        /* v2.04: changed */
        //return( strings[LS_PTR] );
        return( GetMemtypeString( sym->type, buffer ) );
    //case MT_ABS: /* v2.07: MT_ABS is obsolete */
    case MT_EMPTY: /* number, via EQU or = directive */
        return( strings[LS_NUMBER] );
#ifdef DEBUG_OUT /* v2.11: obsolete */
    case MT_PROC:
        printf("GetMemtypeString: found mem_type=MT_PROC for sym=%s\n", sym->name );
        break;
#endif
    }
    return("?");
}

static const char *GetLanguage( const struct asym *sym )
/******************************************************/
{
    if ( sym->langtype <= 7 )
        return( strings[sym->langtype + LS_VOID] );
    return( "?" );
}

/* display STRUCTs and UNIONs */

static void log_struct( const struct asym *sym, const char *name, int_32 ofs )
/****************************************************************************/
{
    unsigned      i;
    struct dsym   *dir;
    const char    *pdots;
    struct struct_info *si;
    struct sfield *f;
    static int    prefix = 0;

    dir = (struct dsym *)sym;

    /* filter typedefs and records */
    //if ( dir->sym.typekind != TYPE_STRUCT &&
    //     dir->sym.typekind != TYPE_UNION )
    //    return;

    si = dir->e.structinfo;

    if ( !name )
        name = sym->name;
    i = strlen ( name );
    pdots = (( (i+prefix) >= DOTSMAX) ? "" : dots + i + prefix + 1 );
    for ( i = 0; i < prefix; i++ )
        LstPrintf(" ");
    if ( prefix == 0 )
        if ( dir->e.structinfo->alignment > 1)
            LstPrintf( "%s %s        %8" I32_SPEC "X (%u)", name, pdots, sym->total_size, si->alignment );
        else
            LstPrintf( "%s %s        %8" I32_SPEC "X", name, pdots, sym->total_size );
    else
        LstPrintf( "%s %s        %8" I32_SPEC "X", name, pdots, sym->offset + ofs);
    LstNL();
    prefix += 2;
    for( f = si->head; f; f = f->next ) {
        /* recursion if an embedded struct occurs */
        /* v2.09: field init_dir removed */
        //if ( f->sym->mem_type == MT_TYPE && f->init_dir == NULL ) {
        if ( f->sym.mem_type == MT_TYPE && f->ivalue[0] == NULLC ) {
            log_struct( f->sym.type, f->sym.name, f->sym.offset + ofs );
        } else {
            /* don't list unstructured fields without name */
            /* but do list them if they are structured */
            if (*(f->sym.name) || (f->sym.mem_type == MT_TYPE)) {
                i = f->sym.name_size + prefix;
                pdots = ((i >= DOTSMAX) ? "" : dots + i + 1 );
                for ( i = 0; i < prefix; i++ )
                    LstPrintf(" ");
                LstPrintf( "%s %s        %8" I32_SPEC "X   ", f->sym.name, pdots, f->sym.offset + sym->offset + ofs);
                LstPrintf( "%s", GetMemtypeString( &f->sym, NULL ) );
                if ( f->sym.isarray )
                    LstPrintf( "[%u]",f->sym.total_length );
                LstNL();
            }
        }
    }
    prefix -= 2;
}

static void log_record( const struct asym *sym )
/**********************************************/
{
#if AMD64_SUPPORT
    uint_64         mask;
#else
    uint_32         mask;
#endif
    struct dsym     *dir = (struct dsym *)sym;
    struct struct_info *si = dir->e.structinfo;
    struct sfield   *f;
    int i = sym->name_size;
    const char *pdots;

    pdots = ((i >= DOTSMAX) ? "" : dots + i + 1 );
    for( i = 0,f = si->head; f; f = f->next,i++ );
    LstPrintf( "%s %s      %6" I32_SPEC "X  %7X", sym->name, pdots, sym->total_size*8, i );
    LstNL();
    for( f = si->head; f; f = f->next ) {
        i = f->sym.name_size + 2;
        pdots = ((i >= DOTSMAX) ? "" : dots + i + 1 );
        for ( i = f->sym.offset, mask = 0; i < f->sym.offset + f->sym.total_size; i++ )
#if AMD64_SUPPORT
#if defined(LLONG_MAX) || defined(__GNUC__) || defined(__TINYC__)
            mask |= 1ULL << i;
#else
            mask |= 1i64 << i;
#endif
        if ( sym->total_size > 4 )
            LstPrintf( "  %s %s      %6" I32_SPEC "X  %7" I32_SPEC "X  %016" I64_SPEC "X %s", f->sym.name, pdots, f->sym.offset, f->sym.total_size, mask, f->ivalue[0] ? f->ivalue : "?" );
        else
            LstPrintf( "  %s %s      %6" I32_SPEC "X  %7" I32_SPEC "X  %08" I32_SPEC "X %s", f->sym.name, pdots, f->sym.offset, f->sym.total_size, (uint_32)mask, f->ivalue[0] ? f->ivalue : "?" );
#else
            mask |= 1 << i;
        LstPrintf( "  %s %s      %6" I32_SPEC "X  %7" I32_SPEC "X  %08" I32_SPEC "X %s", f->sym.name, pdots, f->sym.offset, f->sym.total_size, mask, f->ivalue[0] ? f->ivalue : "?" );
#endif
        LstNL();
    }
}

/* a typedef is a simple struct with no fields. Size might be 0. */

static void log_typedef( const struct asym *sym )
/***********************************************/
{
    //struct dsym         *dir = (struct dsym *)sym;
    //struct struct_info  *si = dir->e.structinfo;
    char *p;
    int i = sym->name_size;
    const char *pdots;
    //char buffer[256];

    pdots = (( i >= DOTSMAX ) ? "" : dots + i + 1 );
    p = StringBufferEnd;
    *p = NULLC;
    if ( sym->mem_type == MT_PROC && sym->target_type ) { /* typedef proto? */
        strcat( p, strings[LS_PROC] );
        strcat( p, " " );
        if ( *sym->target_type->name ) {  /* the name may be "" */
            strcat( p, sym->target_type->name );
            strcat( p," ");
        }
        /* v2.11: target_type has state SYM_TYPE (since v2.09).
         * This state isn't handled properly by GetSymOfsSize(), which is called by GetMemtypeString(),
         * so get the strings here.
         */
        //strcat( p, GetMemtypeString( sym->target_type, NULL ) );
        strcat( p, strings[( sym->target_type->mem_type == MT_NEAR ? LS_LNEAR16 : LS_LFAR16 ) + sym->Ofssize ] );
        strcat( p," " );
        strcat( p, GetLanguage( sym->target_type ) );
    } else
        p = (char *)GetMemtypeString( sym, p );
    LstPrintf( "%s %s    %8" I32_SPEC "u  %s", sym->name, pdots, sym->total_size, p );
    LstNL();
}

static void log_segment( const struct asym *sym, const struct asym *group )
/*************************************************************************/
{
    char buffer[32];
    struct seg_info *seg = ((struct dsym *)sym)->e.seginfo;

    if( seg->group == group ) {
        int i = sym->name_size;
        const char *pdots;
        pdots = (( i >= DOTSMAX ) ? "" : dots + i + 1);
        LstPrintf( "%s %s        ", sym->name, pdots );
        if( seg->Ofssize == USE32 ) {
            //LstPrintf( "32 Bit   %08" I32_SPEC "X ", seg->current_loc );
            LstPrintf( "32 Bit   %08" I32_SPEC "X ", sym->max_offset );
#if AMD64_SUPPORT
        } else if( seg->Ofssize == USE64 ) {
            LstPrintf( "64 Bit   %08" I32_SPEC "X ", sym->max_offset );
#endif
        } else {
            //LstPrintf( "16 Bit   %04" I32_SPEC "X     ", seg->current_loc );
            LstPrintf( "16 Bit   %04" I32_SPEC "X     ", sym->max_offset );
        }
        LstPrintf( "%-7s %-8s", get_seg_align( seg, buffer ), get_seg_combine( seg ) );
        LstPrintf( "'%s'", seg->clsym ? seg->clsym->name : "" );
#if 0
        if ( group != NULL )
            LstPrintf( " %s", group->name );
#endif
        LstNL();
    }
}

static void log_group( const struct asym *grp, const struct dsym *segs )
/**********************************************************************/
{
    unsigned i;
    const char *pdots;
    struct seg_item *curr;

    i = grp->name_size;
    pdots = (( i >= DOTSMAX ) ? "" : dots + i + 1);
    LstPrintf( "%s %s        %s", grp->name, pdots, strings[LS_GROUP] );
    LstNL();

    /* the FLAT groups is always empty */
    if ( grp == (struct asym *)ModuleInfo.flat_grp ) {
        for( ; segs; segs = segs->next ) {
            log_segment( (struct asym *)segs, grp );
        }
    } else
        for( curr = ((struct dsym *)grp)->e.grpinfo->seglist; curr; curr = curr->next ) {
            log_segment( (struct asym *)curr->seg, grp );
        }
}

static const char *get_proc_type( const struct asym *sym )
/********************************************************/
{
    /* if there's no segment associated with the symbol,
     add the symbol's offset size to the distance */
    switch( sym->mem_type ) {
    case MT_NEAR:
        if ( sym->segment == NULL ) {
            return( strings[LS_NEAR16 + GetSymOfssize( sym )] );
        }
        return( strings[LS_NEAR] );
    case MT_FAR:
        if ( sym->segment == NULL ) {
            return( strings[LS_FAR16 + GetSymOfssize( sym )] );
        }
        return( strings[LS_FAR] );
    }
    return( " " );
}

static const char *get_sym_seg_name( const struct asym *sym )
/***********************************************************/
{
    if( sym->segment ) {
        return( sym->segment->name );
    } else {
        return( strings[LS_NOSEG] );
    }
}

/* list Procedures and Prototypes */

static void log_proc( const struct asym *sym )
/********************************************/
{
    struct dsym *f;
    struct dsym *l;
    const char *p;
    struct dsym *dir = (struct dsym *)sym;
    int i = sym->name_size;
    char Ofssize = GetSymOfssize( sym );
    const char *pdots;

    pdots = (( i >= DOTSMAX ) ? "" : dots + i + 1 );
    if ( Ofssize )
        p = "%s %s        P %-6s %08" I32_SPEC "X %-8s ";
    else
        p = "%s %s        P %-6s %04" I32_SPEC "X     %-8s ";
    LstPrintf( p,
              sym->name,
              pdots,
              get_proc_type( sym ),
              sym->offset,
              get_sym_seg_name( sym ));

    /* externals (PROTO) don't have a size. Masm always prints 0000 or 00000000 */
    LstPrintf( "%0*" I32_SPEC "X ", Ofssize > USE16 ? 8 : 4, sym->state == SYM_INTERNAL ? sym->total_size : 0 );

#ifdef DEBUG_OUT
    if ( sym->fwdref )
        LstPrintf( "(F) " );
#endif
    if( sym->ispublic ) {
        LstPrintf( "%-9s", strings[LS_PUBLIC] );
    } else if ( sym->state == SYM_INTERNAL ) {
        LstPrintf( "%-9s", strings[LS_PRIVATE] );
    } else {
        LstPrintf( sym->weak ? "*%-8s " : "%-9s ", strings[LS_EXTERNAL] );
#if DLLIMPORT
        if ( sym->dll )
            LstPrintf( "(%.8s) ", sym->dll->name );
#endif
    }

    LstPrintf( "%s", GetLanguage( sym ) );
    LstNL();
    /* for PROTOs, list optional altname */
    if ( sym->state == SYM_EXTERNAL && sym->altname ) {
        struct asym *sym2 = sym->altname;
        LstPrintf( "  ");
        LstPrintf( p,
                  sym2->name,
                  pdots+2,
                  get_proc_type( sym2 ),
                  sym2->offset,
                  get_sym_seg_name( sym2 ));
        LstNL();
    }
    /* for PROCs, list parameters and locals */
    if ( sym->state == SYM_INTERNAL ) {

        /* print the procedure's parameters */
        if ( sym->langtype == LANG_C ||
            sym->langtype == LANG_SYSCALL ||
            sym->langtype == LANG_STDCALL ||
            sym->langtype == LANG_FASTCALL ) {
            int cnt;
            /* position f2 to last param */
            for ( cnt = 0, f = dir->e.procinfo->paralist; f; f = f->nextparam )
                cnt++;
            for ( ; cnt; cnt-- ) {
                int curr;
                for ( curr = 1,f = dir->e.procinfo->paralist; curr < cnt;f = f->nextparam, curr++ );
                i = f->sym.name_size;
                pdots = (( i >= DOTSMAX-2 ) ? "" : dots + i + 1 + 2 );
                /* FASTCALL: parameter may be a text macro (=register name) */
                if ( f->sym.state == SYM_TMACRO )
                    LstPrintf( "  %s %s        %-17s %s", f->sym.name, pdots, GetMemtypeString( &f->sym, NULL ), f->sym.string_ptr );
                else
                    LstPrintf( szFmtProcStk, f->sym.name, pdots,
                            f->sym.is_vararg ? strings[LS_VARARG] : GetMemtypeString( &f->sym, NULL ),
#if STACKBASESUPP
                              GetResWName( dir->e.procinfo->basereg, NULL ),
#else
                              GetResWName( basereg[Ofssize], NULL ),
#endif
                            '+', f->sym.offset );
                LstNL();
            }
        } else {
            for ( f = dir->e.procinfo->paralist; f; f = f->nextparam ) {
                i = f->sym.name_size;
                pdots = (( i >= DOTSMAX-2 ) ? "" : dots + i + 1 + 2 );
                LstPrintf( szFmtProcStk, f->sym.name, pdots, GetMemtypeString( &f->sym, NULL ),
#if STACKBASESUPP
                          GetResWName( dir->e.procinfo->basereg, NULL ),
#else
                          GetResWName( basereg[Ofssize], NULL ),
#endif
                          '+', f->sym.offset );
                LstNL();
            }
        }

        /* print the procedure's locals */
        for ( l = dir->e.procinfo->locallist; l; l = l->nextlocal ) {
            char buffer[32];
            i = l->sym.name_size;
            pdots = (( i >= DOTSMAX-2 ) ? "" : dots + i + 1 + 2);
            if ( l->sym.isarray )
                sprintf( buffer, "%s[%" I32_SPEC "u]", GetMemtypeString(&l->sym, NULL), l->sym.total_length );
            else
                strcpy( buffer, GetMemtypeString( &l->sym, NULL ) );
            LstPrintf( szFmtProcStk, l->sym.name, pdots, buffer,
#if STACKBASESUPP
                      GetResWName( dir->e.procinfo->basereg, NULL ),
                      l->sym.offset >= 0 ? '+' : '-', l->sym.offset >= 0 ? l->sym.offset : - l->sym.offset );
#else
                      GetResWName( basereg[Ofssize], NULL ),
                      '-', - l->sym.offset );
#endif
            LstNL();
        }

        for ( l = dir->e.procinfo->labellist; l ; l = l->e.nextll ) {
            struct dsym *l2;
            for ( l2 = l; l2; l2 = (struct dsym *)l2->sym.nextitem ) {
                /* filter params and locals! */
                if ( l2->sym.state == SYM_STACK || l2->sym.state == SYM_TMACRO )
                    continue;
                i = l2->sym.name_size;
                pdots = (( i >= DOTSMAX-2 ) ? "" : dots + i + 1 + 2);
                if ( Ofssize )
                    p = "  %s %s        L %-6s %08" I32_SPEC "X %s";
                else
                    p = "  %s %s        L %-6s %04" I32_SPEC "X     %s";
                LstPrintf( p,
                          l2->sym.name,
                          pdots,
                          get_proc_type( &l2->sym ),
                          l2->sym.offset,
                          get_sym_seg_name( &l2->sym ));
#ifdef DEBUG_OUT
                if ( l2->sym.fwdref )
                    LstPrintf( " (F)" );
#endif
                LstNL();
            }
        }
    }
}

/* list symbols */

static void log_symbol( const struct asym *sym )
/**********************************************/
{
    int i = sym->name_size;
    const char *pdots;
    //char buffer[MAX_LINE_LEN];

    pdots = ((i >= DOTSMAX) ? "" : dots + i + 1 );

    switch ( sym->state ) {
    case SYM_UNDEFINED:
    case SYM_INTERNAL:
    case SYM_EXTERNAL:
        LstPrintf( "%s %s        ", sym->name, pdots );

        if ( sym->isarray ) {
            i = sprintf( StringBufferEnd, "%s[%u]", GetMemtypeString( sym, NULL ), sym->total_length );
            LstPrintf( "%-10s ", StringBufferEnd );
        } else if ( sym->state == SYM_EXTERNAL && sym->iscomm == TRUE ) {
            LstPrintf( "%-10s ", strings[LS_COMM] );
        } else
            LstPrintf( "%-10s ", GetMemtypeString( sym, NULL ) );

        /* print value */
        /* v2.07: MT_ABS is obsolete */
        //if ( sym->mem_type == MT_ABS )
        if ( sym->state == SYM_EXTERNAL && sym->iscomm == TRUE )
            LstPrintf( " %8" I32_SPEC "Xh ", sym->total_size / sym->total_length );
        else if ( sym->mem_type == MT_EMPTY ) { /* also check segment? might be != NULL for equates (var = offset x) */
            if ( sym->value3264 != 0 && sym->value3264 != -1 )
                LstPrintf( " %" I64_SPEC "Xh ", sym->uvalue, sym->value3264 );
            else if ( sym->value3264 < 0 )
                LstPrintf( "-%08" I32_SPEC "Xh ", 0 - sym->uvalue );
            else
                LstPrintf( " %8" I32_SPEC "Xh ", sym->offset );
        } else
            LstPrintf( " %8" I32_SPEC "Xh ", sym->offset );

        /* print segment */
        //if ( sym->mem_type == MT_ABS || sym->state == SYM_UNDEFINED )
        //    ;
        //else
        if ( sym->segment )
            LstPrintf( "%s ", get_sym_seg_name( sym ) );

#ifdef DEBUG_OUT
        if ( sym->fwdref )
            LstPrintf( "(F) " );
#endif
        if ( sym->state == SYM_EXTERNAL && sym->iscomm == TRUE )
            LstPrintf( "%s=%u ", szCount, sym->total_length );

        if( sym->ispublic )
            LstPrintf( "%s ", strings[LS_PUBLIC] );

        if ( sym->state == SYM_EXTERNAL ) {
            LstPrintf( sym->weak ? "*%s " : "%s ", strings[LS_EXTERNAL] );
        } else if ( sym->state == SYM_UNDEFINED ) {
            LstPrintf( "%s ", strings[LS_UNDEFINED] );
        }

        LstPrintf( "%s", GetLanguage( sym ) );
        LstNL();
        break;
    case SYM_TMACRO:
        //GetLiteralValue( buffer, sym->string_ptr );
        //strcpy( buffer, sym->string_ptr );
        LstPrintf( "%s %s        %s   %s", sym->name, pdots, strings[LS_TEXT], sym->string_ptr );
        LstNL();
        break;
    case SYM_ALIAS:
        LstPrintf( "%s %s        %s  %s", sym->name, pdots, strings[LS_ALIAS], sym->substitute->name );
        LstNL();
        break;
    }
}

static void LstCaption( const char *caption, int prefNL )
/*******************************************************/
{
    for (; prefNL; prefNL--)
        LstNL();
    LstPrintf( caption );
    LstNL();
    LstNL();
}

static int compare_syms( const void *p1, const void *p2 )
/*******************************************************/
{
    return( strcmp( (*(struct asym * *)p1)->name, (*(struct asym * *)p2)->name ) );
}

/* write symbol table listing */

void LstWriteCRef( void )
/***********************/
{
    struct asym     **syms;
    struct dsym     *dir;
    struct struct_info *si;
    int             idx;
    uint_32         i;
    uint_32         SymCount;
    struct qdesc    queues[LQ_LAST];

    /* no point going through the motions if lst file isn't open */
    if( CurrFile[LST] == NULL || Options.no_symbol_listing == TRUE ) {
        return;
    }

    /* go to EOF */
    fseek( CurrFile[LST], 0, SEEK_END );

    SymCount = SymGetCount();
    syms = MemAlloc( SymCount * sizeof( struct asym * ) );
    SymGetAll( syms );

    DebugMsg(("LstWriteCRef: calling qsort\n"));
    /* sort 'em */
    qsort( syms, SymCount, sizeof( struct asym * ), compare_syms );

    memset( queues, 0, sizeof( queues ) );
    for( i = 0; i < SymCount; ++i ) {
        struct qdesc *q;
        if ( syms[i]->list == FALSE )
            continue;
        switch (syms[i]->state) {
        case SYM_TYPE:
            si = ((struct dsym *)syms[i])->e.structinfo;
            switch ( syms[i]->typekind ) {
            case TYPE_RECORD:  idx = LQ_RECORDS; break;
            case TYPE_TYPEDEF: idx = LQ_TYPEDEFS;break;
            case TYPE_STRUCT:
            case TYPE_UNION:   idx = LQ_STRUCTS ;break;
#ifdef DEBUG_OUT
            default:           idx = LQ_UNDEF_TYPES ;break;
#else
            default: continue; /* skip "undefined" types */
#endif
            }
            break;
        case SYM_MACRO:
            idx = LQ_MACROS;
            break;
        case SYM_SEG:
            idx = LQ_SEGS;
            break;
        case SYM_GRP:
            idx = LQ_GRPS;
            break;
        case SYM_INTERNAL:
        case SYM_EXTERNAL: /* v2.04: added, since PROTOs are now externals */
            if ( syms[i]->isproc ) {
                idx = LQ_PROCS;
                break;
            }
            /* no break */
        default:
            continue;
        }
        q = &queues[idx];
        if( q->head == NULL ) {
            q->head = syms[i];
        } else {
            ((struct dsym *)q->tail)->next = (struct dsym *)syms[i];
        }
        q->tail = syms[i];
        ((struct dsym *)syms[i])->next = NULL;
    }
    for ( idx = 0; idx < ( sizeof( cr ) / sizeof(cr[0] ) ); idx++ ) {
        if ( queues[cr[idx].type].head ) {
            if ( cr[idx].capitems ) {
                const short *ps;
                for ( ps = cr[idx].capitems; *ps; ps++ ) {
                    //if ( *ps == -1 )
                    //    LstNL();
                    //else
                        LstCaption( strings[ *ps ], ps == cr[idx].capitems ? 2 : 0 );
                }
            }
            for( dir = queues[cr[idx].type].head; dir ; dir = dir->next ) {
                cr[idx].function( &dir->sym, ( cr[idx].flags & PRF_ADDSEG ) ? queues[LQ_SEGS].head : NULL, 0 );
            }
        }
    }

    /* write out symbols */
    LstCaption( strings[ LS_TXT_SYMBOLS ], 2 );
    LstCaption( strings[ LS_TXT_SYMCAP ], 0 );
    for( i = 0; i < SymCount; ++i ) {
        if ( syms[i]->list == TRUE && syms[i]->isproc == FALSE ) {
            DebugMsg(("LstWriteCRef: log_symbol( %s )\n", syms[i]->name ));
            log_symbol( syms[i] );
        }
#ifdef DEBUG_OUT
        else
            DebugMsg(("LstWriteCRef: %s suppressed\n", syms[i]->name ));
#endif
    }
    LstNL();

    /* free the sorted symbols */
    DebugMsg(("LstWriteCRef: free sorted symbols\n"));
    MemFree( syms );
}

/* .[NO|X]LIST, .[NO|X]CREF, .LISTALL, 
 * .[NO]LISTIF, .[LF|SF|TF]COND,
 * PAGE, TITLE, SUBTITLE, SUBTTL directives
 */
ret_code ListingDirective( int i, struct asm_tok tokenarray[] )
/*************************************************************/
{
    int directive = tokenarray[i].tokval;
    i++;

    switch ( directive ) {
    case T_DOT_LIST:
        if ( CurrFile[LST] )
            ModuleInfo.list = TRUE;
        break;
    case T_DOT_CREF:
        ModuleInfo.cref = TRUE;
        break;
    case T_DOT_NOLIST:
    case T_DOT_XLIST:
        ModuleInfo.list = FALSE;
        break;
    case T_DOT_NOCREF:
    case T_DOT_XCREF:
        if ( i == Token_Count ) {
            ModuleInfo.cref = FALSE;
            break;
        }
        do {
            struct asym *sym;
            if ( tokenarray[i].token != T_ID ) {
                return( EmitErr( SYNTAX_ERROR_EX, tokenarray[i].tokpos ) );
            }
            /* the name may be a forward reference. In this case it will
             * be created here.
             * v2.11: function call cannot fail. no need for checks.
             */
            sym = SymLookup( tokenarray[i].string_ptr );
            sym->list = FALSE;
            i++;
            if ( i < Token_Count ) {
                if ( tokenarray[i].token != T_COMMA )
                    return( EmitErr( EXPECTING_COMMA, tokenarray[i].tokpos ) );

                /* if there's nothing after the comma, don't increment */
                if ( i < ( Token_Count - 1 ) )
                    i++;
            }
        } while ( i < Token_Count );
        break;
    case T_DOT_LISTALL: /* list false conditionals and generated code */
        if ( CurrFile[LST] )
            ModuleInfo.list = TRUE;
        ModuleInfo.list_generated_code = TRUE;
        /* fall through */
    case T_DOT_LISTIF:
    case T_DOT_LFCOND: /* .LFCOND is synonym for .LISTIF */
        ModuleInfo.listif = TRUE;
        break;
    case T_DOT_NOLISTIF:
    case T_DOT_SFCOND: /* .SFCOND is synonym for .NOLISTIF */
        ModuleInfo.listif = FALSE;
        break;
    case T_DOT_TFCOND: /* .TFCOND toggles .LFCOND, .SFCOND */
        ModuleInfo.listif = !ModuleInfo.listif;
        break;
    case T_PAGE:
    default: /* TITLE, SUBTITLE, SUBTTL */
        /* tiny checks to ensure that these directives
         aren't used as code labels or struct fields */
        if ( tokenarray[i].token == T_COLON )
            break;
        /* this isn't really Masm-compatible, but ensures we don't get
         * struct fields with names page, title, subtitle, subttl.
         */
        if( CurrStruct ) {
            return( EmitError( STATEMENT_NOT_ALLOWED_INSIDE_STRUCTURE_DEFINITION ) );
        }
        if ( Parse_Pass == PASS_1 )
            EmitWarn( 4, DIRECTIVE_IGNORED, tokenarray[i-1].string_ptr );
        while ( tokenarray[i].token != T_FINAL) i++;
    }

    if ( tokenarray[i].token != T_FINAL ) {
        return( EmitErr( SYNTAX_ERROR_EX, tokenarray[i].string_ptr ) );
    }

    return( NOT_ERROR );
}

/* directives .[NO]LISTMACRO, .LISTMACROALL, .[X|L|S]ALL */

ret_code ListMacroDirective( int i, struct asm_tok tokenarray[] )
/***************************************************************/
{
    if ( tokenarray[i+1].token != T_FINAL ) {
        return( EmitErr( SYNTAX_ERROR_EX, tokenarray[i+1].string_ptr ) );
    }

    ModuleInfo.list_macro = GetSflagsSp( tokenarray[i].tokval );

    return( NOT_ERROR );
}

void LstInit( void )
/******************/
{
    const struct fname_item *fn;
    const char *buffer;

    list_pos = 0;
    if( Options.write_listing ) {
        int namelen;
        buffer = MsgGetEx( MSG_JWASM );
        list_pos = strlen( buffer );
        fwrite( buffer, 1, list_pos, CurrFile[LST] );
        LstNL();
        fn = GetFName( ModuleInfo.srcfile );
        namelen = strlen( fn->fname );
        fwrite( fn->fname, 1, namelen, CurrFile[LST] );
        list_pos += namelen;
        LstNL();
    }

}
