DRUID Development Docs: ju_convert.c

Gadget Sourcecode: ju_convert.c

/*
 * Justice Unlimited Gadget Text Conversion Utility
 *
 * Copyrigh (C) 2004, Nathan (Acorn) Pooley
 *
 *
 *
 */

/*
 *
 * This program parses files in a format described at 
 * the top of gadget_strings.txt
 *
 */

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

int DB_show_strings = 0;

typedef struct MString_s MString;
typedef struct MStrBlock_s MStrBlock;
typedef struct MMode_s MMode;
typedef struct MMenu_s MMenu;
typedef struct MMItem_s MMItem;
typedef struct MText_s MText;
typedef struct MCode_s MCode;
typedef struct MFunc_s MFunc;
typedef struct MFPtr_s MFPtr;
typedef struct MEntry_s MEntry;
typedef struct MUnknown_s MUnknown;
typedef struct MInfo_s MInfo;
typedef struct MDefine_s MDefine;
typedef struct MIfStack_s MIfStack;
typedef struct MInFile_s MInFile;
typedef struct MSpecialChar_s MSpecialChar;
typedef struct MSCFunc_s MSCFunc;
typedef struct MDep_s MDep;

#define SPECIAL_CHAR_MAX_LEN    4
struct MSpecialChar_s {
    int     bs_letter;
    int     len;
    int     val[SPECIAL_CHAR_MAX_LEN];
    char    *func;
    int     skip_cnt;
};

struct MSCFunc_s {
    char    *name;
    int      sc_val;
    MSCFunc *next;
};

enum {
    ENTRY_MASK_LOWER        =   0x01,
    ENTRY_MASK_UPPER        =   0x02,
    ENTRY_MASK_DIGIT        =   0x04,
    ENTRY_MASK_SPACE        =   0x08,
    ENTRY_MASK_END
};

//
// characters for serial display
//
enum {
    SCHAR_ESC           =   0xfe,

    //
    // these follow the escape character
    //
    SCHAR_POS           =   0x47,       // followed by col, row
    SCHAR_CLEAR         =   0x58,
    SCHAR_BS            =   0x4c,
    SCHAR_LIGHT_ON      =   0x42,
    SCHAR_LIGHT_OFF     =   0x46,
    SCHAR_CURSOR_ON     =   0x4a,
    SCHAR_CURSOR_OFF    =   0x4b,
    SCHAR_WRAP_ON       =   0x43,
    SCHAR_WRAP_OFF      =   0x44,

    SCHAR_END
};


//
// characters common to both displays
//
enum {

    //
    // these characters stand on their own
    //
    CHAR_BULLET         =   0xa5,

    CHAR_END
};

enum {
    SPECIAL_CHAR_MIN    =   0x80,
    SPECIAL_CHAR_MAX    =   0x9f,
    SPECIAL_CHAR_END,
    SPECIAL_CHAR_BAD    =   0x1000
};

/*
 * DSCn() - define special character macros
 */
#define DSC0(in)                { in,  0, { 0,           }, 0, 0, }
#define DSC1(in, out1)          { in,  1, { out1,        }, 0, 0, }
#define DSC2(in, o1,o2)         { in,  2, { o1,o2,       }, 0, 0, }
#define DSC3(in, o1,o2,o3)      { in,  3, { o1,o2,o3,    }, 0, 0, }
#define DSC4(in, o1,o2,o3,o4)   { in,  4, { o1,o2,o3,o4, }, 0, 0, }
#define DSCFUNC(in, func)       { in, -1, { 0,           }, func, 0, }
#define DSCFUNCS(in, func,skip) { in, -1, { 0,           }, func, skip, }
#define DSCEND()                { -1,  0, { 0,           }, 0, 0, }

/*
 * Backslash sequence characters:
 *
 *   IN USE:    abcd   hi  l n   rst  w   
 *   IN USE:    ABCD       L N P RST  W   
 *   IN USE:    012345678
 *
 *   UNUSED:        efg  jk m opq   uv xyz
 *   UNUSED:       DEFGHIJK M O Q   UV XYZ
 *   UNUSED:             9
 *
 *   NOTE: \n is reserved but is an error to use
 *   NOTE: \P## is set position and is handled specially in the
 *         read_string() function (below).
 */

MSpecialChar special_chars_serial[] = {
    DSC2('C', SCHAR_ESC, SCHAR_CLEAR),
    DSC2('h', SCHAR_ESC, SCHAR_BS),
    DSC2('s', SCHAR_ESC, SCHAR_CURSOR_OFF),
    DSC2('S', SCHAR_ESC, SCHAR_CURSOR_ON),
    DSC2('l', SCHAR_ESC, SCHAR_LIGHT_OFF),
    DSC3('L', SCHAR_ESC, SCHAR_LIGHT_ON, 0x00),
    DSC2('w', SCHAR_ESC, SCHAR_WRAP_OFF),
    DSC2('W', SCHAR_ESC, SCHAR_WRAP_ON),

    DSCFUNC('R', "sc_oerr_restart"),
    DSCFUNC('a', "sc_oerr_noack"),
    DSCFUNC('A', "sc_oerr_en_retry"),
    DSCFUNC('B', "sc_oerr_bus"),
    DSCFUNC('c', "sc_oerr_wcol"),
    DSCFUNC('d', "sc_oerr_nosspif"),
    DSCFUNC('D', "sc_cnt_outenab"),
    DSCEND()
};


MSpecialChar special_chars_parallel[] = {
    DSCFUNCS('C', "sc_clr", -1),
    DSCFUNCS('h', "sc_bs", 2),
    DSCFUNCS('s', "sc_cursor_off", 1),
    DSCFUNCS('S', "sc_cursor_on", 1),
    DSCFUNCS('l', "sc_light_off", 1),
    DSCFUNCS('L', "sc_light_on", 1),
    DSCFUNCS('N', "sc_char_def", 10),

    DSC0('w'),
    DSC0('W'),
    DSC2('R', '-','-'),
    DSC2('a', '-','-'),
    DSC2('A', '-','-'),
    DSC2('B', '-','-'),
    DSC2('c', '-','-'),
    DSC2('d', '-','-'),
    DSC2('D', '-','-'),
    DSCEND()
};


MSpecialChar special_chars_common[] = {
    DSC1('b', CHAR_BULLET),
    DSCFUNC('K', "sc_clue_name"),
    DSCFUNC('k', "sc_clue_time"),
    DSCFUNC('t', "sc_game_time"),
    DSCFUNC('T', "sc_real_time"),
    DSCFUNC('r', "sc_cnt_turnon"),
    DSCFUNC('i', "sc_irlook"),
    DSCEND()
};

typedef enum ModeType_e {
    MTYPE_MENU      =   0x00,
    MTYPE_TEXT      =   0x40,
    MTYPE_ENTRY     =   0x80,
    MTYPE_FUNC      =   0xc0,
    MTYPE_UNKNOWN   =   0x1ff,
    MTYPE_END
} ModeType;

struct MString_s {
    char    *name;
    int      string_group;
    char    *string;
    int      len;
    int      idx;
    MString *next;
    MString *next_order;
};

struct MMode_s {
    ModeType         type;
    int              idx;
    char            *name;
    MMode           *next_mode;
    MMode           *next_mode_order;
};

struct MMenu_s {
    MMode    mode;
    int      cnt;
    MString *first_string;
    MMItem  *head;
    MMItem  **tail;
    MMenu   *next;
};

struct MMItem_s {
    char    *mode;
    int      do_push;
    MString *string;
    MMItem  *next;
};

struct MText_s {
    MMode    mode;
    int      cnt;
    int      do_push;
    MString *first_string;
    char    *next_mode;
    MText   *next;
};

struct MFunc_s {
    MMode    mode;
    MFPtr   *fptr;
    char    *parm0;
    char    *parm1;
    char    *parm2;
    MFunc   *next;
};

struct MFPtr_s {
    char    *func_name; // WITHOUT the func_ part
    MFPtr   *next;
};

struct MEntry_s {
    MMode    mode;
    int      mask;
    MString *prompt_string;
    char    *enter_func;
    MEntry  *next;
};

struct MUnknown_s {
    MMode        mode;
    MUnknown    *next;
};

struct MCode_s {
    char    *code;
    char    *parm0;
    char    *parm1_max;
    char    *parm2_max;
    MFPtr   *fptr;
    MCode   *next;
};

struct MDefine_s {
    char    *name;
    char    *def;
    MDefine *next;
};

struct MIfStack_s {
    int         is_else;    /* 0=if  1=else */
    int         enable;     /* 0=ignore 1=parse */
    int         if_line;
    int         else_line;
    MIfStack    *next;
};

struct MInFile_s {
    char    *filename;
    FILE    *fp;
    int      linenum;
    MInFile *next;
};

struct MDep_s {
    char    *filename;
    MDep    *next;
};

#define MAX_LINE_CHARS      1000
#define MAX_LINE_WORDS      10
struct MInfo_s {
    char    *string_filename;
    char    *func_filename;
    char    *schar_filename;
    char    *def_filename;
    char    *dep_filename;
    char    *in_filename;
    FILE    *in;
    FILE    *out;
    char     line[MAX_LINE_CHARS];
    int      word_cnt;
    char    *words[MAX_LINE_WORDS];
    char    *string;
    char    *s;
    int      in_linenum;
    int      string_len;
    int      next_sc_val;

    int     sc_skip[SPECIAL_CHAR_END];

    MSpecialChar    **special_chars;

    char    *last_byte_comment;
    char    *last_byte_label;
    char    *last_byte;
    int      byte_cnt;

    void    *current_ptr;
    void    (*current_func)(MInfo *info);
    int      current_string_group;

    int      string_cnt;
    int      mode_cnt;

    MString     *string_order_head;
    MString     **string_order_tail;

    MMode       *mode_order_head;
    MMode       **mode_order_tail;


    MString     *string_head;
    MString     **string_tail;


    MMode       *mode_head;
    MMode       **mode_tail;

    MMenu       *menu_head;
    MMenu       **menu_tail;

    MText       *text_head;
    MText       **text_tail;

    MFunc       *func_head;
    MFunc       **func_tail;

    MEntry      *entry_head;
    MEntry      **entry_tail;

    MCode       *code_head;
    MCode       **code_tail;

    MUnknown    *unknown_head;
    MUnknown    **unknown_tail;


    MFPtr       *fptr_head;
    MFPtr       **fptr_tail;

    MDefine     *def_head;
    MDefine     **def_tail;

    MSCFunc     *scfunc_head;
    MSCFunc     **scfunc_tail;

    MIfStack    *if_stack;
    int          enable;

    MInFile     *infile_stack;

    MDep        *dep_head;
    MDep        **dep_tail;
};

void func_default(MInfo *info);
MString *get_string(MInfo *info);
MString *find_string(MInfo *info, char *name);
MFPtr *find_func(MInfo *info, char *name, int add);

#define ASSERT(cond)    if (!(cond)) { assert_err( #cond ); }

void assert_err(char *str)
{
    fprintf(stderr,"ASSERT FAILED: %s\n",str);
    exit(1);
}

void warn(MInfo *info, char *str)
{
    fprintf(stderr,"WARNING (at %s:%d): %s\n",
        info->in_filename,
        info->in_linenum-1,
        str);
}

int my_isprint(int c)
{
    if (c<-127 && c>-256) c = -c;
    if (c<=0 || c>255) return 0;
    if (!isprint(c)) return 0;
    if (isalnum(c)) return 1;
    if (strchr(" .!@#$%^&*();:.,<>[]{}",c)) return 1;
    return 0;
}


//
// find (or allocate) special character associated with a special char function
//
int find_scfunc(MInfo *info, char *func, MSpecialChar *sc)
{
    MSCFunc *scf;

    scf = info->scfunc_head;
    while(scf) {
        if (!strcmp(func, scf->name)) {
            return scf->sc_val;
        }

        scf = scf->next;
    }

    if (info->next_sc_val > SPECIAL_CHAR_MAX) {
        fprintf(stderr, "ERROR: Too many special character functions!\n");
        fprintf(stderr, "       func '%s' will never be called!!\n",func);
        return '*';
    }

    scf = (MSCFunc*)malloc(sizeof(MSCFunc));

    scf->name   = strdup(func);
    scf->sc_val = info->next_sc_val++;
    scf->next   = 0;

    ASSERT(info->sc_skip[scf->sc_val] == 0);
    info->sc_skip[scf->sc_val] = sc->skip_cnt;

    *info->scfunc_tail = scf;
    info->scfunc_tail  = &scf->next;

    return scf->sc_val;
}

void add_dep(MInfo *info, char *filename)
{
    MDep *dp = info->dep_head;

    while(dp) {
        if (!strcmp(dp->filename, filename)) {
            return;
        }
        dp = dp->next;
    }

    dp = (MDep*)malloc(sizeof(MDep));
    dp->filename = strdup(filename);
    dp->next = 0;

    *info->dep_tail = dp;
    info->dep_tail  = &dp->next;
}

void push_infile(MInfo *info, char *filename)
{
    MInFile *f;
    FILE *fp = fopen(filename, "r");
    if (!fp) {
        fprintf(stderr,"Error reading file '%s'\n",filename);
        warn(info, "Could not open file");
        return;
    }

    add_dep(info, filename);

    //
    // push current file onto stack
    //
    f = (MInFile*)malloc(sizeof(MInFile));

    f->filename = info->in_filename;
    f->fp = info->in;
    f->linenum = info->in_linenum;

    f->next = info->infile_stack;
    info->infile_stack = f;

    //
    // new file becomes current file
    //
    info->in = fp;
    info->in_linenum = 1;
    info->in_filename = strdup(filename);
}

void pop_infile(MInfo *info)
{
    MInFile *f = info->infile_stack;
    ASSERT(info->in);
    ASSERT(info->in_filename);

    fclose(info->in);
    free(info->in_filename);

    if (f) {
        info->in_filename = f->filename;
        info->in_linenum  = f->linenum;
        info->in          = f->fp;
        info->infile_stack = f->next;
        free(f);
    } else {
        info->in_filename = strdup("EOF");
        info->in_linenum  = 0;
        info->in = 0;
    }
}

void eat_space(FILE *in)
{
    int c = getc(in);
    while (c != '\n' && isspace(c)) {
        c = getc(in);
    }
    if (c != EOF) {
        ungetc(c, in);
    }
}

void add_char(MInfo *info, int c, int to_string)
{
    if (info->s - info->line >= MAX_LINE_CHARS) {
        warn(info, "Line too long");
        info->s = info->line + MAX_LINE_CHARS - 1;
        c = 0;
        info->string = 0;
        info->word_cnt = 0;
    }
    *(info->s++) = c;
    if (to_string) info->string_len++;
}

int kill_comment(MInfo *info, int could_be_directive)
{
    int c;
    char word[20];
    char *s = word;
    
    do {
        c = getc(info->in);
    } while (isspace(c) && c!= '\n' && c!= EOF);
    while(islower(c) && s!=&word[sizeof(word)-1]) {
        *(s++) = c;
        c = getc(info->in);
    }
    *s = 0;
    if (s!=word &&
        info->word_cnt == 0 &&
        could_be_directive &&
        (   !strcmp(word,"define") ||
            !strcmp(word,"include") ||
            !strcmp(word,"if") ||
            !strcmp(word,"endif") ||
            !strcmp(word,"else")))
    {
        info->words[info->word_cnt++] = info->s;
        s = word;
        while(*s) {
            add_char(info, *s, 0);
            s++;
        }
        add_char(info, 0, 0);
        return c;
    }

    while(c!= '\n' && c!= EOF) {
        c = getc(info->in);
    }
    return c;
}

int read_symbol(MInfo *info, int c)
{
    ASSERT(info->word_cnt < MAX_LINE_WORDS);
    info->words[info->word_cnt++] = info->s;
    while(isalnum(c) || c=='_') {
        add_char(info, c, 0);
        c = getc(info->in);
    }
    add_char(info, 0, 0);
    return c;
}

void def_special_chars(MInfo *info, MSpecialChar *list, int *sc_ptr)
{
    while (list->bs_letter != -1) {
        ASSERT(list->bs_letter >= 0 && list->bs_letter <= 255);
        if (info->special_chars[list->bs_letter]) {
            fprintf(stderr, "ERROR: "
                "Attempt to define special character '%c' (0x%02x) twice\n",
                my_isprint(list->bs_letter)?list->bs_letter:'?',
                list->bs_letter);
        } else {
            info->special_chars[list->bs_letter] = list;
        }
        list++;
    }
}

void prep_special_chars(MInfo *info)
{
    int special_char_id = SPECIAL_CHAR_MIN;
    int size = sizeof(MSpecialChar*) * 256;
    info->special_chars = (MSpecialChar**)malloc(size);

    memset(info->special_chars, 0, size);

    def_special_chars(info, special_chars_common, &special_char_id);

    if (eval(info, "OUTPUT_PARALLEL")) {
        def_special_chars(info, special_chars_parallel, &special_char_id);
    } else {
        def_special_chars(info, special_chars_serial, &special_char_id);
    }

    ASSERT(special_char_id <= SPECIAL_CHAR_MAX);
    ASSERT(info->special_chars['0'] == 0);  // hex ASCII sequence
    ASSERT(info->special_chars['P'] == 0);  // cursor position command
}

void read_char_def(MInfo *info)
{
    int c = getc(info->in);
    int err = 0;
    int cnum = 0;
    int caddr;
    int cmmt;
    int i,j;
    int data[8] = {0,0,0,0,0,0,0};

    if (c < '1' || c > '8') {
        warn(info, "Bad character definition ID");
        err |= 1;
    } else if (c == '8') {
        cnum = 0;
        c = getc(info->in);
    } else {
        cnum = c - '0';
        c = getc(info->in);
    }

    caddr = (cnum * 8) | 0x40;  // set CGRAM addr code
    add_char(info, caddr, 1);

    for (i=0; ; i++) {
        cmmt = 0;
        while(c != '\n' && c != EOF) {
            if (!cmmt && !isspace(c)) {
                if (c == '#') {
                    cmmt = 1;
                } else if (c == '"') {
                    warn(info, "partial character definition");
                    ungetc(c, info->in);
                    return;
                } else {
                    err |= 2;
                }
            }
            c = getc(info->in);
        }
        info->in_linenum++;
        if (c == EOF) {
            err |= 4;
            break;
        }
        if (i == 8) break;

        for (j=0; ; j++) {
            c = getc(info->in);
            if (c == '"') {
                warn(info, "partial character definition");
                ungetc(c, info->in);
                return;
            }
            if (j == 5) {
                break;
            }
            if (c == EOF) {
                err |= 4;
                break;
            }
            if (c == '\n') {
                break;
            }
            if (c =='\t') {
                j--;
                continue;
            }
            data[i] <<= 1;
            if (c !='.' && c !=' ') {
                data[i] |= 1;
            }
        }
    }

    if (err) {
        warn(info, "Error parsing character definition");
    }

    for (i=0; i<8; i++) {
        add_char(info, data[i], 1);
    }
}

void read_string_special_char(MInfo *info)
{
    static char err_str[] = "Unknown backslash sequence \\xxxxxxxxx";
    char *err_p;
    MSpecialChar *sc;
    int c;
    int val;

    if (!info->special_chars) {
        prep_special_chars(info);
    }

    c = getc(info->in);

    if (c == EOF) {
        warn(info, "File ends with backslash inside a string");
        return;
    }

    ASSERT(c>=0 && c<=255);

    if (c == '"') {
        add_char(info,c,1);
        return;
    }

    if (c >= '1' && c <= '8') {
        if (c == '8') {
            add_char(info, 0, 1);
        } else {
            add_char(info, c-'0', 1);
        }
        return;
    }

    //
    // special character?
    //
    sc = info->special_chars[c];
    if (sc) {
        int i;

        //
        // undefined function? - define it
        //
        if (sc->len == -1) {
            ASSERT(sc->func && sc->func[0]);
            sc->len = 1;
            sc->val[0] = find_scfunc(info, sc->func, sc);
        }

        for (i=0; i<sc->len; i++) {
            add_char(info, sc->val[i], 1);
        }

        //
        // define character?
        //
        if (c == 'N') {
            read_char_def(info);
        }

        return;
    }

    //
    // backslash
    //
    if (c == '\\') {
        add_char(info, '\\', 1);
        return;
    }

    //
    // \P special character (position cursor)?
    //
    if (c == 'P') {
        int cnt = 0;
        c = getc(info->in);
        if (!isdigit(c)) {
            warn(info,"Bad position (\\Pnn) in string (no digits)");
            return;
        }
        while(isdigit(c)) {
            val *= 10;
            val += c - '0';
            c = getc(info->in);
            cnt++;
        }
        if (val > 39 || val < 0 || cnt != 2) {
            warn(info, "Bad position (must be 00-39 decimal)");
            val = 0;
        }
        if (eval(info, "OUTPUT_PARALLEL")) {
            int sc_pos = find_scfunc(info, "sc_pos", 0);
            add_char(info, sc_pos, 1);
            add_char(info, val, 1);
        } else {
            add_char(info, SCHAR_ESC, 1);
            add_char(info, SCHAR_POS, 1);
            if (val > 19) {
                add_char(info, val, 1);
                add_char(info, 2, 1);
            } else {
                add_char(info, val-19, 1);
                add_char(info, 1,    1);
            }
        }
        return;
    }
    
    //
    // otherwise it is an \0xnn ascii hex value or an error
    //
    val = 0;
    err_p = strchr(err_str, '\\');
    ASSERT(err_p);
    err_p++;
    *(err_p++) = c;
    if (c != '0') val += 0x100;
    if (val < 0x100) {
        c = getc(info->in);
        *(err_p++) = c;
        if (c != 'x' && c!='X') val += 0x100;
    }
    if (val < 0x100) {
        c = getc(info->in);
        *(err_p++) = c;
        val +=  (c >= '0' && c <= '9') ? (c - '0') :
                (c >= 'a' && c <= 'f') ? (c - 'a' + 10) :
                (c >= 'A' && c <= 'F') ? (c - 'a' + 10) :
                0x100;
    }
    if (val < 0x100) {
        val *= 0x10;
        c = getc(info->in);
        *(err_p++) = c;
        val +=  (c >= '0' && c <= '9') ? (c - '0') :
                (c >= 'a' && c <= 'f') ? (c - 'a' + 10) :
                (c >= 'A' && c <= 'F') ? (c - 'a' + 10) :
                0x100;
    }

    if (val < 0 || val > 255) {
        *(err_p++) = 0;
        warn(info, err_str);
        return;
    }

    add_char(info, val, 1);
}

int read_string(MInfo *info, int c)
{
    int quote = c;
    while(1) {
        c = getc(info->in);
        if (c == EOF || c == '\n') {
            warn(info, "Untermnated string");
            break;
        }
        if (c == quote) {
            break;
        }

        if (c == '\\') {
            read_string_special_char(info);
            continue;
        }
        
        add_char(info, c, 1);
    }
    add_char(info, 0, 0);

    return ' ';
}


int read_line(MInfo *info)
{
    int first_c = 1;
    int c;
    info->in_linenum++;
    info->s = info->line;

    c = getc(info->in);
    while (c == EOF) {
        pop_infile(info);
        if (info->in == 0) return 0;
        c = getc(info->in);
    }

    info->word_cnt = 0;
    info->string   = 0;
    info->string_len = 0;
    
    while (1) {

        if (c == '\n' || c == EOF) break;
        if (isspace(c) && c != '\n') {
            c = getc(info->in);
            continue;
        }

        if (c == '#') {
            c = kill_comment(info, first_c);
            first_c = 0;
            continue;
        }

        if (isalnum(c)) {
            first_c = 0;
            c = read_symbol(info, c);
            continue;
        }

        if (c == '"' || c == '\'') {
            first_c = 0;
            if (info->string) {
                if (info->s != info->string + info->string_len) {
                    warn(info, "Two strings on line");
                    info->string = info->s;
                }
            } else {
                info->string = info->s;
            }
            c = read_string(info, c);
            continue;
        }

        first_c = 0;
        warn(info, "could not parse line");
        fprintf(stderr,"c=0x%02x = '%c'\n",c,isprint(c)?c:'?');
        while (c!='\n' && c!=EOF) {
            c = getc(info->in);
        }
    }
    return 1;
}

MString *add_string(MInfo *info, char *name, char *str, int len)
{
    MString *ms = 0;
    if (name && name[0]) ms = find_string(info, name);
    if (ms) {
        warn(info, "Duplicate string name!:");
        fprintf(stderr,"      name=%s\n",name);
        return ms;
    }

    if (len < 1) {
        len = 1;
        str = " ";
    }

    //
    // check that string is 20 chars or less
    //
    if (len > 20) {
        char *str_cp;
        int i;
        int skip_cnt = 0;
        int cnt = 0;
        int maxcnt = 0;
        str_cp = (char *)malloc(len+1);
        memcpy(str_cp, str, len);
        str_cp[len] = 0;
        for (i=0; i<len; i++) {
            if (skip_cnt > 0) {
                skip_cnt--;
            } else {
                int c = 0xff & (int)str[i];
                cnt++;
                if (c < SPECIAL_CHAR_END && info->sc_skip[c]) {
                    if (info->sc_skip[c] < 0) {
                        cnt = 0;
                    } else {
                        skip_cnt = info->sc_skip[c] - 1;
                        cnt--;
                    }
                }
#if 0
                if (c == 0xfe) {
                    cnt = -1;
                }
#endif
                if (cnt > maxcnt) maxcnt = cnt;
            }
            if (!my_isprint(str_cp[i])) {
                str_cp[i] = '.';
            }
        }
        if (maxcnt > 20) {
            warn(info, "String is longer than 20 characters");
            fprintf(stderr,"\t\tstr[%d]:\"%s\"\n", len,str_cp);
        }
        free(str_cp);
    }

    ms = (MString*)malloc(sizeof(MString));

    ms->name   = strdup(name?name:"");
    ms->idx    = -1;
    info->string_cnt++;
    ms->string_group = info->current_string_group;
    ms->len    = len;
    ms->string = (char*) malloc(len+1);
    memcpy(ms->string, str, len);
    ms->string[len] = 0;
    ms->next = 0;

    *info->string_tail = ms;
    info->string_tail  = &ms->next;

    return ms;
}

init_mode(MInfo *info, MMode *mm, char *name, ModeType type)
{
    ASSERT(name && name[0]);
    mm->type  = type;
    mm->name = strdup(name);
    mm->idx = -1;
    mm->next_mode       = 0;
    mm->next_mode_order = 0;

    *info->mode_tail = mm;
    info->mode_tail  = &mm->next_mode;
}

MUnknown *add_unknown(MInfo *info, char *name)
{
    MUnknown *mu;

    mu = (MUnknown*)malloc(sizeof(MUnknown));

    init_mode(info, &mu->mode, name, MTYPE_UNKNOWN);
    //mu->name             = strdup(name);
    mu->next = 0;

    *info->unknown_tail = mu;
    info->unknown_tail  = &mu->next;

    return mu;
}

MFPtr *find_fptr(MInfo *info, char *name, int add)
{
    if (!name || !name[0]) {
        warn(info, "Bad function pointer name");
        return 0;
    }

    if (strncmp(name, "func_", 5) ||
        strlen(name) < 6) {
        warn(info, "Bad function pointer name");
        return 0;
    }

    return find_func(info, name+5, add);
}

MFPtr *find_func(MInfo *info, char *name, int add)
{
    MFPtr *mf = info->fptr_head;

    if (!name || !name[0]) {
        warn(info, "Bad function name");
        return 0;
    }

    if (!strncmp(name,"func_",5)) {
        warn(info, "Function name starts with 'func_'");
    }

    while(mf) {
        if (!strcmp(name, mf->func_name)) return mf;
        mf = mf->next;
    }

    if (add) {
        mf = (MFPtr*)malloc(sizeof(MFPtr));

        mf->func_name             = strdup(name);
        mf->next = 0;

        *info->fptr_tail = mf;
        info->fptr_tail  = &mf->next;

        return mf;
    } else {
        return 0;
    }
}


char *quote_string(MInfo *info, MString *ms)
{
    static char str[1000];
    int i;

    if (!ms) {
        return "???UNKNOWN STRING???";
    }

    str[0] = '"';
    for (i=0; i<ms->len && i<900; i++) {
        str[i+1] = ms->string[i];
        if (!my_isprint(str[i+1])) {
            str[i+1] = '.';
        }
    }
    str[1+i++] = '"';
    sprintf(&str[1+i], "[%d,l=%d]", ms->idx, ms->len);
    return str;
}

MString *find_string(MInfo *info, char *name)
{
    MString *ms = info->string_head;

    while(ms) {
        if (!strcmp(name, ms->name)) return ms;
        ms = ms->next;
    }
    return 0;
}

#if 1
MMode *find_mode(MInfo *info, char *name, int add)
{
    MMode *mm = info->mode_head;

    while(mm) {
        if (!strcmp(name, mm->name)) return mm;
        mm = mm->next_mode;
    }

    if (add) {
        return &add_unknown(info, name)->mode;
    } else {
        return 0;
    }
}
#else
MUnknown *find_mode(MInfo *info, char *name, int add)
{
    MMenu *mm = info->menu_head;
    MText *mt = info->text_head;
    MEntry *me = info->entry_head;
    MFunc *mf = info->func_head;
    MUnknown *mu = info->unknown_head;

    while(mm) {
        if (!strcmp(name, mm->name)) return (MUnknown*)mm;
        mm = mm->next;
    }

    while(mt) {
        if (!strcmp(name, mt->name)) return (MUnknown*)mt;
        mt = mt->next;
    }

    while(me) {
        if (!strcmp(name, me->name)) return (MUnknown*)me;
        me = me->next;
    }

    while(mf) {
        if (!strcmp(name, mf->name)) return (MUnknown*)mf;
        mf = mf->next;
    }

    while(mu) {
        if (!strcmp(name, mu->name)) return (MUnknown*)mu;
        mu = mu->next;
    }

    if (add) {
        return add_unknown(info, name);
    } else {
        return 0;
    }
}
#endif

/* return 0 if OK, 1 if name already in use */
int check_new_mode_name(MInfo *info, char *name)
{
    if (!name || !name[0]) {
        warn(info, "Bad mode name");
        return 1;
    }
    if (find_mode(info, name, 0)) {
        warn(info, "Duplicate mode name");
        fprintf(stderr,"      mode name: %s\n",name);
        return 1;
    }
    return 0;
}

#if 0
/* return 0 if OK, 1 if name already in use */
int check_new_fptr_name(MInfo *info, char *name)
{
    if (!name || !name[0]) {
        warn(info, "Bad fptr name");
        return 1;
    }
    if (find_fptr(info, name, 0)) {
        warn(info, "Duplicate fptr name");
        fprintf(stderr,"      fptr name: %s\n",name);
        return 1;
    }
    return 0;
}
#endif

void func_menu(MInfo *info)
{
    MMenu *mm = (MMenu*)info->current_ptr;
    MMItem *mmi;
    MString *ms;
    char *name;
    int do_push = 1;

    if (info->word_cnt > 0 && !strcmp(info->words[0],"END")) {
        info->current_ptr  = 0;
        info->current_func = func_default;
        return;
    }

    if (info->word_cnt > 0 && !strcmp(info->words[0],"PUSH")) {
        int i;
        info->word_cnt--;
        for (i=0; i<info->word_cnt; i++) {
            info->words[i] = info->words[i+1];
        }
        do_push = 1;    // this is the default
    }

    if (info->word_cnt > 0 && !strcmp(info->words[0],"SET")) {
        int i;
        info->word_cnt--;
        for (i=0; i<info->word_cnt; i++) {
            info->words[i] = info->words[i+1];
        }
        do_push = 0;
    }


    if (!info->string) {
        warn(info, "Menu item has no string");
        return;
    }
    if (info->word_cnt > 2) {
        warn(info, "too many symbols in Menu Item");
    }
    if (info->word_cnt < 1) {
        warn(info, "No mode name for Menu Item");
        return;
    }

    name = info->words[0];

    if (info->word_cnt > 1) {
        info->words[0] = info->words[1];
    } else {
        info->words[0] = "";
    }

    ms = get_string(info);

    if (!mm) {
        warn(info, "Skipping line in bad MENU definition");
        return;
    }

    if (!ms) {
        warn(info, "Could not find string for menu item");
        return;
    }

    mmi = (MMItem*)malloc(sizeof(MMItem));
    mmi->mode = strdup(name);
    mmi->string = ms;
    mmi->do_push = do_push;
    mmi->next = 0;

    mm->cnt++;
    *mm->tail = mmi;
    mm->tail  = &mmi->next;
}

void func_text(MInfo *info)
{
    MText *mt = (MText*)info->current_ptr;
    MString *ms;
    if (info->word_cnt > 0 && !strcmp(info->words[0],"END")) {

        if (mt && mt->cnt < 2) {
            //warn(info, "TEXT is too short (min 2 lines) - adding line(s)");
            ms = add_string(info, "", " ", 1);
            ASSERT(ms);
            if (mt->cnt == 0) {
                mt->first_string = ms;
            }
            mt->cnt++;
            if (mt->cnt < 2) {
                ms = add_string(info, "", " ", 1);
                ASSERT(ms);
                mt->cnt++;
            }
        }

        info->current_ptr  = 0;
        info->current_func = func_default;
        return;
    }

    ms = get_string(info);

    if (!mt) {
        warn(info, "Skipping line in bad TEXT definition");
        return;
    }

    if (ms) {
        if (mt->cnt == 0) {
            mt->first_string = ms;
        }

        mt->cnt++;
    }
}

void get_mmenu(MInfo *info)
{
    MMenu *mm;

    info->current_ptr = 0;
    info->current_func = func_menu;

    if (!info->string) {
        warn(info, "No string on MENU line");
        info->string = "Choose from menu:";
        info->string_len = strlen(info->string);
    }
    if (info->word_cnt > 3) {
        warn(info, "Too many symbols on MENU line");
    }
    if (info->word_cnt < 2) {
        warn(info, "not enough symbols on MENU line");
        return;
    }
    if (info->word_cnt < 3) {
        info->words[2] = "";
    }

    if (check_new_mode_name(info, info->words[1])) {
        return;
    }

    mm = (MMenu*)malloc(sizeof(MMenu));

    init_mode(info, &mm->mode, info->words[1], MTYPE_MENU);
    //mm->name          = strdup(info->words[1]);
    mm->cnt           = 0;
    mm->first_string  = add_string(info, info->words[2], info->string,
                                        info->string_len);
    mm->head          = 0;
    mm->tail          = &mm->head;
    mm->next = 0;

    *info->menu_tail = mm;
    info->menu_tail  = &mm->next;

    info->current_ptr = (void*) mm;
}

void get_mtext(MInfo *info)
{
    MText *mt;
    int do_push = 0;

    info->current_ptr = 0;
    info->current_func = func_text;

    if (info->string) {
        warn(info, "String on TEXT line");
    }

    if (info->word_cnt > 3 && !strcmp(info->words[2],"PUSH")) {
        int i;
        info->word_cnt--;
        for (i=2; i<info->word_cnt; i++) {
            info->words[i] = info->words[i+1];
        }
        do_push = 1;
    }

    if (info->word_cnt > 3 && !strcmp(info->words[2],"SET")) {
        int i;
        info->word_cnt--;
        for (i=2; i<info->word_cnt; i++) {
            info->words[i] = info->words[i+1];
        }
        do_push = 0;    // this is the default
    }

    if (info->word_cnt > 3) {
        warn(info, "Too many symbols on TEXT line");
    }
    if (info->word_cnt < 3) {
        warn(info, "not enough symbols on TEXT line");
        return;
    }

    if (check_new_mode_name(info, info->words[1])) {
        return;
    }

    mt = (MText*)malloc(sizeof(MText));

    init_mode(info, &mt->mode, info->words[1], MTYPE_TEXT);
    //mt->name          = strdup(info->words[1]);
    mt->cnt           = 0;
    mt->first_string  = 0;
    mt->next_mode     = strdup(info->words[2]);
    mt->do_push       = do_push;
    mt->next = 0;

    *info->text_tail = mt;
    info->text_tail  = &mt->next;

    info->current_ptr = (void*) mt;
}

void get_code(MInfo *info)
{
    MCode *mc;
    MFPtr *mfp;

    if (!info->string) {
        warn(info, "CODE line with no code string!");
        return;
    }
    if (info->word_cnt < 2) {
        warn(info, "CODE line with no function name");
        return;
    }
    if (info->word_cnt > 5) {
        warn(info, "Too many parameters to CODE line");
    }

    mfp = find_fptr(info, info->words[1], 1);
    if (!mfp) {
        warn(info, "No function pointer in CODE statement");
        return;
    }

    mc = (MCode*)malloc(sizeof(MCode));

    mc->code      = strdup(info->string);
    mc->fptr      = mfp;
    mc->parm0     = 0;
    mc->parm1_max = 0;
    mc->parm2_max = 0;
    mc->next = 0;

    if (info->word_cnt > 2) {
        mc->parm0     = strdup(info->words[2]);
    }
    if (info->word_cnt > 3) {
        mc->parm1_max = strdup(info->words[3]);
    }
    if (info->word_cnt > 4) {
        mc->parm2_max = strdup(info->words[4]);
    }

    *info->code_tail = mc;
    info->code_tail  = &mc->next;
}

void get_mentry(MInfo *info)
{
    MEntry *me;

    if (!info->string) {
        warn(info, "No string in ENTRY definition");
        return;
    }
    if (info->word_cnt > 5) {
        warn(info, "Too many symbols on line");
    }
    if (info->word_cnt < 4) {
        warn(info, "not enough symbols on ENTRY line");
        return;
    }
    if (info->word_cnt < 5) {
        info->words[4] = "";
    }

    if (check_new_mode_name(info, info->words[1])) {
        return;
    }

    me = (MEntry*)malloc(sizeof(MEntry));

    init_mode(info, &me->mode, info->words[1], MTYPE_ENTRY);
    //me->name          = strdup(info->words[1]);
    me->prompt_string = add_string(info, info->words[4], info->string, 
                                info->string_len);
    me->enter_func    = strdup(info->words[2]);
    me->mask = 0;
    if (strncmp(info->words[3],"msk",3)) {
        warn(info, "Bad mask");
    }
    if (strchr(info->words[3],'A')) {
        me->mask |= ENTRY_MASK_UPPER;
    }
    if (strchr(info->words[3],'a')) {
        me->mask |= ENTRY_MASK_LOWER;
    }
    if (strchr(info->words[3],'0')) {
        me->mask |= ENTRY_MASK_DIGIT;
    }
    if (strchr(info->words[3],'_')) {
        me->mask |= ENTRY_MASK_SPACE;
    }
    me->next = 0;

    *info->entry_tail = me;
    info->entry_tail  = &me->next;
}

void get_mfunc(MInfo *info)
{
    MFunc *mf;
    MFPtr *mfp;
    int i;

    if (info->string) {
        warn(info, "String in FUNC definition");
    }
    if (info->word_cnt > 5) {
        warn(info, "Too many symbols on FUNC line");
    }
    if (info->word_cnt < 3) {
        warn(info, "not enough symbols on FUNC line");
        return;
    }

    for (i=info->word_cnt; i<6; i++) {
        info->words[i] = 0;
    }
    
    mfp = find_fptr(info, info->words[2], 1);
    if (!mfp) return;

    mf = (MFunc*)malloc(sizeof(MFunc));

    init_mode(info, &mf->mode, info->words[1], MTYPE_FUNC);
    //mf->name      = strdup(info->words[1]);
    mf->fptr      = mfp;
    mf->parm0     = info->words[3] ? strdup(info->words[3]) : 0;
    mf->parm1     = info->words[4] ? strdup(info->words[4]) : 0;
    mf->parm2     = info->words[5] ? strdup(info->words[5]) : 0;
    mf->next = 0;

    *info->func_tail = mf;
    info->func_tail  = &mf->next;
}

void get_fptr(MInfo *info)
{
    int i;

    if (info->string) {
        warn(info, "String in FUNC definition");
    }
    if (info->word_cnt > 2) {
        warn(info, "Too many symbols on FPTR line");
    }
    if (info->word_cnt < 2) {
        warn(info, "not enough symbols on FPTR line");
        return;
    }

    find_func(info, info->words[1], 1);
}

MString *get_string(MInfo *info)
{
    if (!info->string) {
        warn(info, "No string found");
        return 0;
    }

    if (info->word_cnt > 1) {
        warn(info, "Too many symbols on line");
    }

    if (info->word_cnt < 1) {
        info->words[0] = "";
    }

    return add_string(info, info->words[0], info->string, info->string_len);
}

void get_include(MInfo *info)
{
    MDefine *md;

    if (!info->string) {
        warn(info, "No filename on include line");
        return;
    }
    if (info->word_cnt != 1) {
        warn(info, "extra stuff on include line");
    }

    info->in_linenum++;
    push_infile(info, info->string);
}


void add_define(MInfo *info, char *name, char *def)
{
    MDefine *md;

    ASSERT(name && name[0]);
    ASSERT(def && def[0]);

    md = (MDefine*)malloc(sizeof(MDefine));

    md->name = strdup(name);
    md->def  = strdup(def);
    md->next = 0;

    *info->def_tail = md;
    info->def_tail = &md->next;
}

void get_define(MInfo *info)
{
    MDefine *md;

    if (info->string) {
        warn(info, "String on define line");
    }
    if (info->word_cnt != 3) {
        warn(info, "Bad define statement");
        return;
    }
#if 1
    add_define(info, info->words[1], info->words[2]);
#else

    md = (MDefine*)malloc(sizeof(MDefine));

    md->name = strdup(info->words[1]);
    md->def  = strdup(info->words[2]);
    md->next = 0;

    *info->def_tail = md;
    info->def_tail = &md->next;
#endif
}

int eval_r(MInfo *info, char *exp)
{
    MDefine *md;
    int val;
    char *e;
    int paren=0;

    if (!exp) {
        warn(info, "Empty expression");
        return 0;
    }
    while (isspace(*exp)) exp++;
    e = exp + strlen(exp);
    while(e != exp && isspace(e[-1])) e--;
    *e = 0;

    if (!exp || !exp[0]) {
        warn(info, "Empty expression");
        return 0;
    }

    for (e=exp; *e; e++) {
        if (*e == '(') {
            paren++;
        } else if (*e == ')') {
            if (paren <= 0) {
                warn(info, "Bad expression - unmatched ')'");
                return 0;
            }
            paren--;
        } else if (!paren) {
            int isand = !strncmp(e,"&&",2);
            int isor  = !strncmp(e,"||",2);
            if (isand || isor) {
                int rva, rvb;
                *e = 0;
                rva = eval_r(info, exp);
                rvb = eval_r(info, e+2);
                return (isand) ? (rva && rvb) : (rva || rvb);
            }
        }
    }

    if (paren > 0) {
        warn(info, "Bad expression - unmatched '('");
        return 0;
    }

    if (*exp == '!') {
        return !eval_r(info, exp+1);
    }
    e = exp + strlen(exp);
    if (*exp == '(' && e != exp && *(e - 1) == ')') {
        *(e-1) = 0;
        return eval_r(info, exp+1);
    }

    val = strtol(exp,&e,0);
    if (e && *e==0) {
        return val;
    }

    md = info->def_head;
    while(md) {
        if (!strcmp(md->name, exp)) {
            return eval(info, md->def);
        }

        md = md->next;
    }

    warn(info, "Could not parse expression");
    return 0;
}

int eval(MInfo *info, char *exp)
{
    char *exp_cp = strdup(exp);
    int rv = eval_r(info, exp_cp);
    free(exp_cp);
    return rv;
}


void get_if(MInfo *info)
{
    MIfStack *is;

    if (info->string) {
        if (info->word_cnt == 1) {
            info->word_cnt = 2;
            info->words[1] = info->string;
            info->string = 0;
        } else {
            warn(info, "String on if line");
        }
    }
    if (info->word_cnt != 2) {
        warn(info, "Bad if statement");
        return;
    }

    is = (MIfStack*)malloc(sizeof(MIfStack));

    is->is_else = 0;
    is->enable = eval(info, info->words[1]);
    is->if_line = info->in_linenum;
    is->else_line = -1;

    is->next = info->if_stack;
    info->if_stack = is;    

    info->enable = info->enable && is->enable;
}

void get_else(MInfo *info)
{
    MIfStack *is = info->if_stack;

    if (info->string) {
        warn(info, "String on else line");
    }
    if (info->word_cnt != 1) {
        warn(info, "extra words on else line");
    }

    if (is == 0) {
        warn(info, "Unmatched else");
        return;
    }

    if (is->is_else) {
        warn(info, "double else");
        return;
    }

    is->is_else = 1;
    is->enable = !is->enable;
    is->else_line = info->in_linenum;

    info->enable = 1;

    while(is) {
        if (is->enable == 0) {
            info->enable = 0;
            break;
        }
        is = is->next;
    }
}

void get_endif(MInfo *info)
{
    MIfStack *is = info->if_stack;

    if (info->string) {
        warn(info, "String on endif line");
    }
    if (info->word_cnt != 1) {
        warn(info, "extra words on endif line");
    }

    if (is == 0) {
        warn(info, "Unmatched endif");
        return;
    }

    info->if_stack = is->next;

    free(is);

    info->enable = 1;

    is = info->if_stack;
    while(is) {
        if (is->enable == 0) {
            info->enable = 0;
            break;
        }
        is = is->next;
    }
}


void func_default(MInfo *info)
{
    if (info->word_cnt > 0) {
        if (!strcmp(info->words[0],"MENU")) {
            info->current_string_group++;
            get_mmenu(info);
            return;
        }
        if (!strcmp(info->words[0],"TEXT")) {
            info->current_string_group++;
            get_mtext(info);
            return;
        }
        if (!strcmp(info->words[0],"CODE")) {
            info->current_string_group++;
            get_code(info);
            return;
        }
        if (!strcmp(info->words[0],"ENTRY")) {
            info->current_string_group++;
            get_mentry(info);
            return;
        }
        if (!strcmp(info->words[0],"FUNC")) {
            info->current_string_group++;
            get_mfunc(info);
            return;
        }
        if (!strcmp(info->words[0],"FPTR")) {
            info->current_string_group++;
            get_fptr(info);
            return;
        }
        if (!strncmp(info->words[0],"str_",4)) {
            info->current_string_group++;
            get_string(info);
            return;
        }
        warn(info, "Could not parse line");
        return;
    }

    if (info->string) {
        info->current_string_group++;
        get_string(info);
        return;
    }
}


void input(MInfo *info)
{
    MIfStack *is;

    //
    // open input file
    //
    if (info->in_filename) {
        info->in = fopen(info->in_filename, "r");
        if (!info->in) {
            fprintf(stderr,"Attempting to open input file '%s'",
                info->in_filename);
            warn(info, "Could not open input file");
            exit(1);
        }
    } else {
        fprintf(stderr,"NOTE: READING FROM STDIN\n");
        info->in_filename = strdup("stdin");
        info->in = stdin;
    }
    info->in_linenum = 1;

    //
    // parse each line in the file
    //
    while (read_line(info)) {
        if (info->string || info->word_cnt) {
            if (info->word_cnt) {
                if (!strcmp(info->words[0],"if")) {
                    get_if(info);
                    continue;
                }
                if (!strcmp(info->words[0],"else")) {
                    get_else(info);
                    continue;
                }
                if (!strcmp(info->words[0],"endif")) {
                    get_endif(info);
                    continue;
                }
                if (!strcmp(info->words[0],"define")) {
                    get_define(info);
                    continue;
                }
                if (!strcmp(info->words[0],"include")) {
                    get_include(info);
                    continue;
                }
            }
            if (info->enable) {
                info->current_func(info);
            }
        }
    }

    //
    // check for unmatched conditionals
    //
    is = info->if_stack;
    while(is) {
        fprintf(stderr,"Unmatched if at line %d",is->if_line);
        if (is->is_else) {
            fprintf(stderr," (else at %d)",is->else_line);
        }
        fprintf(stderr,"\n");

        is = is->next;
    }
}

void output_byte_str(MInfo *info, char *valstr, char *label, char *cmmt)
{
    if (info->byte_cnt & 1) {

        if (label && label[0]) {
#if 0
            fprintf(info->out,"#define lbl_%-15s\t(lbo_%s+1)\n",label,label);
#else
            char name[100],def[100];
            sprintf(name,"lbl_%s",label);
            sprintf(def,"(lbo_%s+1)",label);
            add_define(info, name, def);
#endif
            fprintf(info->out,"lbo_%s:\n",label);
        }

        if (info->last_byte_label) {
            fprintf(info->out,"lbl_%s:\n",info->last_byte_label);
            free(info->last_byte_label);
            info->last_byte_label = 0;
        }

        fprintf(info->out,"\tdb\t\t%s,%s",
            info->last_byte, valstr);
        free(info->last_byte);
        if (cmmt || info->last_byte_comment) {
            fprintf(info->out,"\t; ");
            if (info->last_byte_comment) {
                fprintf(info->out,"%s%s",
                    info->last_byte_comment,
                    cmmt?", ":"");
                free(info->last_byte_comment);
                info->last_byte_comment = 0;
            }
            if (cmmt) {
                fprintf(info->out,"%s",cmmt);
            }
        }
        fprintf(info->out,"\n");

    } else {
        info->last_byte = strdup(valstr);
        info->last_byte_comment = 0;
        info->last_byte_label = 0;
        if (cmmt && cmmt[0]) {
            info->last_byte_comment = strdup(cmmt);
        }
        if (label && label[0]) {
            info->last_byte_label = strdup(label);
        }
    }
    info->byte_cnt++;
}

void output_byte_val(MInfo *info, int val, char *label, char *cmmt)
{
    char valstr[20];
    sprintf(valstr,"0x%02x",val);
    output_byte_str(info, valstr, label, cmmt);
}

void output_char(MInfo *info, int val, char *label, char *cmmt)
{
    char valstr[20];
    if (my_isprint(val) && val != '\'') {
        sprintf(valstr,"'%c'",val&0xff);
    } else {
        sprintf(valstr,"0x%02x",val&0xff);
    }
    output_byte_str(info, valstr, label, cmmt);
}

void output_flush(MInfo *info)
{
    if (info->byte_cnt & 1) {
        output_byte_val(info, 0, 0, 0);
    }
}

#if 1
void output_mtext(MInfo *info, MText *mt)
{
    char valstr[100];
    char cmmt[100];

    MString *ms = mt->first_string;
    ASSERT(mt->cnt < 0x40);
    ASSERT(mt->cnt > 1);
    sprintf(cmmt,"text mode[0x%02x] (%s)",mt->mode.idx, mt->mode.name);
    output_byte_val(info,
                    5,
                    mt->mode.name,
                    cmmt);
    output_byte_val(info,
                    MTYPE_TEXT + mt->cnt - 1, 
                    0,
                    0);
    ASSERT(ms->name && ms->name[0]);
    sprintf(valstr, "HIGH lbl_%s", ms->name);
    output_byte_str(info,
                    valstr,
                    0,
                    quote_string(info, ms));
    sprintf(valstr, "LOW lbl_%s", ms->name);
    output_byte_str(info,
                    valstr,
                    0,
                    0);
    if (!strcmp(mt->next_mode, "POP")) {
        output_byte_val(
                    info, 
                    0,
                    0,
                    "POP");
    } else {
#if 1

        ASSERT(!mt->do_push);
        sprintf(valstr, "%s",
            mt->next_mode);
        output_byte_str(
                    info, 
                    valstr,
                    0,
                    0);
        //find_mode(info, mt->next_mode, 1);
#else

        sprintf(valstr, "%s%s",
            mt->next_mode,
            mt->do_push?"":"|0x80");
        output_byte_str(
                    info, 
                    valstr,
                    0,
                    0);
        //find_mode(info, mt->next_mode, 1);
#endif
    }
}

void output_mmenu(MInfo *info, MMenu *mm)
{
    char valstr[100];
    char cmmt[100];
    MMItem *mmi;
    MString *ms;


    ms = mm->first_string;
    ASSERT(mm->cnt < 0x40);
    ASSERT(mm->cnt > 0);
    sprintf(cmmt,"menu mode[0x%02x] (%s)",mm->mode.idx, mm->mode.name);
    output_byte_val(info,
                    mm->cnt + 4, 
                    mm->mode.name,
                    cmmt);
    output_byte_val(info,
                    MTYPE_MENU + mm->cnt, 
                    0,
                    0);
    ASSERT(ms->name && ms->name[0]);
    sprintf(valstr, "HIGH lbl_%s", ms->name);
    output_byte_str(info,
                    valstr,
                    0,
                    quote_string(info, ms));
    sprintf(valstr, "LOW lbl_%s", ms->name);
    output_byte_str(info,
                    valstr,
                    0,
                    0);
    for (mmi = mm->head; mmi; mmi = mmi->next) {
        if (!strcmp(mmi->mode, "POP")) {
            output_byte_val(
                        info, 
                        0,
                        0,
                        "POP");
        } else {
#if 1
            ASSERT(mmi->do_push);
            sprintf(valstr, "%s",
                mmi->mode);
            output_byte_str(
                        info, 
                        valstr,
                        0,
                        quote_string(info, mmi->string));
            //find_mode(info, mmi->mode, 1);
#else
            sprintf(valstr, "%s%s",
                mmi->mode,
                mmi->do_push?"":"|0x80");
            output_byte_str(
                        info, 
                        valstr,
                        0,
                        quote_string(info, mmi->string));
            //find_mode(info, mmi->mode, 1);
#endif
        }
    }
}

void output_mentry(MInfo *info, MEntry *me)
{
    char valstr[100];
    char cmmt[100];
    MString *ms = me->prompt_string;

    sprintf(cmmt,"entry mode[0x%02x] (%s)",me->mode.idx, me->mode.name);
    output_byte_val(info,
                    5,
                    me->mode.name,
                    cmmt);
    ASSERT(me->mask < 0x40);
    output_byte_val(info,
                    MTYPE_ENTRY | me->mask,
                    0,
                    0);
    ASSERT(ms->name && ms->name[0]);
    sprintf(valstr, "HIGH lbl_%s", ms->name);
    output_byte_str(info,
                    valstr,
                    0,
                    quote_string(info, ms));
    sprintf(valstr, "LOW lbl_%s", ms->name);
    output_byte_str(info,
                    valstr,
                    0,
                    0);
    output_byte_str(
                info, 
                me->enter_func,
                0,
                0);
    find_fptr(info, me->enter_func, 1);
    me = me->next;
}

void output_mfunc(MInfo *info, MFunc *mf)
{
    char valstr[100];
    char cmmt[100];

    int parm_cnt = 0;

    sprintf(valstr,"0x%02x|func_%s",MTYPE_FUNC, mf->fptr->func_name);

    if (mf->parm0 && mf->parm0[0]) parm_cnt = 1;
    if (mf->parm1 && mf->parm1[0]) parm_cnt = 2;
    if (mf->parm2 && mf->parm2[0]) parm_cnt = 3;
    
    sprintf(cmmt,"func mode[0x%02x] (%s)",mf->mode.idx, mf->mode.name);
    output_byte_val(info,
                    2 + parm_cnt,
                    mf->mode.name,
                    cmmt);
    output_byte_str(info,
                    valstr,
                    0,
                    0);
    if (parm_cnt > 0) {
        output_byte_str(info,
                        mf->parm0?mf->parm0:"0x00",
                        0,
                        0);
    }
    if (parm_cnt > 1) {
        output_byte_str(info,
                        mf->parm1?mf->parm1:"0x00",
                        0,
                        0);
    }
    if (parm_cnt > 2) {
        output_byte_str(info,
                        mf->parm2?mf->parm2:"0x00",
                        0,
                        0);
    }
}

#if 0
MText *get_dummy_mode(MInof *info)
{
    static int first = 1;
    static MText dm;

    if (first) {
        dm->first_string = find_string(info, "str_unknown_mode");
        dm->cnt          = 2;
        ASSERT(dm->first_string);
        dm->next_mode    = "POP";
        dm->next         = 0;
    }

    return &dm;
}
#endif

void output_modes(MInfo *info)
{
    MMode *mm = info->mode_order_head;
    MText *dm;

    output_flush(info);

    fprintf(info->out, "\n");
    fprintf(info->out, "\n");
    fprintf(info->out, "modes:\n");

    while(mm) {
        switch(mm->type) {
            case MTYPE_TEXT:
                output_mtext(info, (MText*)mm);
                break;
            case MTYPE_MENU:
                output_mmenu(info, (MMenu*)mm);
                break;
            case MTYPE_ENTRY:
                output_mentry(info, (MEntry*)mm);
                break;
            case MTYPE_FUNC:
                output_mfunc(info, (MFunc*)mm);
                break;
            case MTYPE_UNKNOWN:
#if 0
                dm = get_dummy_mode(info);
                memcpy(&dm->mode, mm, sizeof(MMode));
                dm->mode.next_mode = 0;
                dm->mode.next_mode_order = 0;
                output_mtext(info, dm);
#endif
                break;
            default:
                fprintf(stderr,"Cannot output mode '%s' - unknown mode type\n",
                    mm->name);
                break;
        }

        mm = mm->next_mode_order;
    }
    fprintf(stderr,"    Mode count:         %d\n",info->mode_cnt);
}

void output_mode_defs(MInfo *info)
{
    MMode *mm = info->mode_order_head;

    output_flush(info);

    fprintf(info->out, "\n");
    fprintf(info->out, ";\n");
    fprintf(info->out, "; Mode labels\n");
    fprintf(info->out, ";\n");

    while(mm) {
        fprintf(info->out, "%-19s\tEQU\t%d\n",
            mm->name,
            mm->idx);
        mm = mm->next_mode;
    }

    fprintf(info->out, "%-19s\tEQU\t%d\n",
        "mode_max",
        info->mode_cnt-1);
}

#else
void output_modes(MInfo *info)
{
    char valstr[100];
    char cmmt[100];

    MMenu *mm = info->menu_head;
    MText *mt = info->text_head;
    MEntry *me = info->entry_head;
    MFunc *mf = info->func_head;
    MUnknown *mu = info->unknown_head;

    output_flush(info);

    fprintf(info->out, "\n");
    fprintf(info->out, "\n");
    fprintf(info->out, "modes:\n");

    fprintf(info->out, "\n");
    fprintf(info->out, ";\n");
    fprintf(info->out, "; Text Modes\n");
    fprintf(info->out, ";\n");

    while(mt) {
        if (mt->cnt == 0) {
            fprintf(stderr,"Text mode %s has no menu lines!",mt->name);
        } else {
            MString *ms = mt->first_string;
            mt->mode_id = info->mode_cnt++;
            ASSERT(mt->cnt < 0x40);
            ASSERT(mt->cnt > 1);
            sprintf(cmmt,"mode[0x%02x] (%s)",mt->mode_id, mt->name);
            output_byte_val(info,
                            5,
                            mt->name,
                            cmmt);
            output_byte_val(info,
                            MTYPE_TEXT + mt->cnt - 1, 
                            0,
                            0);
            ASSERT(ms->name && ms->name[0]);
            sprintf(valstr, "HIGH lbl_%s", ms->name);
            output_byte_str(info,
                            valstr,
                            0,
                            quote_string(info, ms));
            sprintf(valstr, "LOW lbl_%s", ms->name);
            output_byte_str(info,
                            valstr,
                            0,
                            0);
            if (!strcmp(mt->next_mode, "POP")) {
                output_byte_val(
                            info, 
                            0,
                            0,
                            "POP");
            } else {
                output_byte_str(
                            info, 
                            mt->next_mode,
                            0,
                            0);
                find_mode(info, mt->next_mode, 1);
            }
        }
        mt = mt->next;
    }

    fprintf(info->out, "\n");
    fprintf(info->out, ";\n");
    fprintf(info->out, "; Menu Modes\n");
    fprintf(info->out, ";\n");

    while(mm) {
        if (mm->cnt == 0) {
            fprintf(stderr,"Menu %s has no menu items!",mm->name);
        } else {
            MMItem *mmi;
            MString *ms = mm->first_string;
            mm->mode_id = info->mode_cnt++;
            ASSERT(mm->cnt < 0x40);
            sprintf(cmmt,"mode[0x%02x] (%s)",mm->mode_id, mm->name);
            output_byte_val(info,
                            mm->cnt + 4, 
                            mm->name,
                            cmmt);
            output_byte_val(info,
                            MTYPE_MENU + mm->cnt, 
                            0,
                            0);
            ASSERT(ms->name && ms->name[0]);
            sprintf(valstr, "HIGH lbl_%s", ms->name);
            output_byte_str(info,
                            valstr,
                            0,
                            quote_string(info, ms));
            sprintf(valstr, "LOW lbl_%s", ms->name);
            output_byte_str(info,
                            valstr,
                            0,
                            0);
            for (mmi = mm->head; mmi; mmi = mmi->next) {
                if (!strcmp(mmi->mode, "POP")) {
                    output_byte_val(
                                info, 
                                0,
                                0,
                                "POP");
                } else {
                    output_byte_str(
                                info, 
                                mmi->mode,
                                0,
                                quote_string(info, mmi->string));
                    find_mode(info, mmi->mode, 1);
                }
            }
        }
        mm = mm->next;
    }

    fprintf(info->out, "\n");
    fprintf(info->out, ";\n");
    fprintf(info->out, "; Entry Modes\n");
    fprintf(info->out, ";\n");

    while(me) {
        MString *ms = me->prompt_string;
        me->mode_id = info->mode_cnt++;
        sprintf(cmmt,"mode[0x%02x] (%s)",me->mode_id, me->name);
        output_byte_val(info,
                        5,
                        me->name,
                        cmmt);
        ASSERT(me->mask < 0x40);
        output_byte_val(info,
                        MTYPE_ENTRY | me->mask,
                        0,
                        0);
        ASSERT(ms->name && ms->name[0]);
        sprintf(valstr, "HIGH lbl_%s", ms->name);
        output_byte_str(info,
                        valstr,
                        0,
                        quote_string(info, ms));
        sprintf(valstr, "LOW lbl_%s", ms->name);
        output_byte_str(info,
                        valstr,
                        0,
                        0);
        output_byte_str(
                    info, 
                    me->enter_func,
                    0,
                    0);
        find_fptr(info, me->enter_func, 1);
        me = me->next;
    }

    fprintf(info->out, "\n");
    fprintf(info->out, ";\n");
    fprintf(info->out, "; Function Modes\n");
    fprintf(info->out, ";\n");

    while(mf) {
        int parm_cnt;

        mf->mode_id = info->mode_cnt++;
        sprintf(valstr,"0x%02x|func_%s",MTYPE_FUNC, mf->fptr->func_name);

        parm_cnt=0;
        if (mf->parm0 && mf->parm0[0]) parm_cnt = 1;
        if (mf->parm1 && mf->parm1[0]) parm_cnt = 2;
        if (mf->parm2 && mf->parm2[0]) parm_cnt = 3;
        
        sprintf(cmmt,"mode[0x%02x] (%s)",mf->mode_id, mf->name);
        output_byte_val(info,
                        2 + parm_cnt,
                        mf->name,
                        cmmt);
        output_byte_str(info,
                        valstr,
                        0,
                        0);
        if (parm_cnt > 0) {
            output_byte_str(info,
                            mf->parm0?mf->parm0:"0x00",
                            0,
                            0);
        }
        if (parm_cnt > 1) {
            output_byte_str(info,
                            mf->parm1?mf->parm1:"0x00",
                            0,
                            0);
        }
        if (parm_cnt > 2) {
            output_byte_str(info,
                            mf->parm2?mf->parm2:"0x00",
                            0,
                            0);
        }
        mf = mf->next;
    }
    fprintf(stderr,"    Mode count:         %d\n",info->mode_cnt);
}

void output_mode_defs(MInfo *info)
{
    MMenu *mm = info->menu_head;
    MText *mt = info->text_head;
    MEntry *me = info->entry_head;
    MFunc *mf = info->func_head;
    MUnknown *mu = info->unknown_head;
    int cnt;

    output_flush(info);

    fprintf(info->out, "\n");
    fprintf(info->out, ";\n");
    fprintf(info->out, "; Mode labels\n");
    fprintf(info->out, ";\n");

    cnt = 0;
    while(mt) {
        ASSERT(cnt == mt->mode_id);
        fprintf(info->out, "%-15s\tEQU\t%d\n",
            mt->name,
            mt->mode_id);
        mt = mt->next;
        cnt++;
    }

    while(mm) {
        ASSERT(cnt == mm->mode_id);
        fprintf(info->out, "%-15s\tEQU\t%d\n",
            mm->name,
            mm->mode_id);
        mm = mm->next;
        cnt++;
    }

    while(me) {
        ASSERT(cnt == me->mode_id);
        fprintf(info->out, "%-15s\tEQU\t%d\n",
            me->name,
            me->mode_id);
        me = me->next;
        cnt++;
    }

    while(mf) {
        ASSERT(cnt == mf->mode_id);
        fprintf(info->out, "%-15s\tEQU\t%d\n",
            mf->name,
            mf->mode_id);
        mf = mf->next;
        cnt++;
    }

    fprintf(info->out, "%-15s\tEQU\t%d\n",
        "mode_max",
        cnt-1);

    while(mu) {
        fprintf(stderr,"WARNING: Unknown symbol: %s\n",mu->name);
        mu->mode_id = 0;
        fprintf(info->out, "%-15s\tEQU\t%d\n",
            mu->name,
            mu->mode_id);
        mu = mu->next;
    }
}
#endif

void output_codes(MInfo *info)
{
    char valstr[100];

    MCode *mc = info->code_head;

    output_flush(info);

    fprintf(info->out, "\n");
    fprintf(info->out, "\n");
    fprintf(info->out, ";\n");
    fprintf(info->out, "; Codes\n");
    fprintf(info->out, ";\n");
    fprintf(info->out, ";  EACH CODE LOOKS LIKE THIS\n");
    fprintf(info->out, ";    BYTE     DESCR\n");
    fprintf(info->out, ";     0       Length of code (in characters)\n");
    fprintf(info->out, ";     1-n     code characters\n");
    fprintf(info->out, ";     n+1     function parameter\n");
    fprintf(info->out, ";     n+2     function pointer\n");
    fprintf(info->out, ";\n");
    fprintf(info->out, ";  Entry following last code has len=0\n");
    fprintf(info->out, ";\n");
    fprintf(info->out, "codes:\n");

    while(mc) {
        char cmmt[100];
        int len = strlen(mc->code);
        int s_len;
        int i;
        ASSERT(len > 0 && len < 256);
        sprintf(cmmt,"CODE '%s' l=%d",mc->code, len);
        output_byte_val(info,
                        len,
                        0,
                        0);
        for (i=0; i<len; i++) {
            int c = toupper(mc->code[i]);
            if (!isalnum(c)) {
                warn(info, "Bad characters in code");
                fprintf(stderr,"CODE: '%s' char[%d] = %d = 0x%02x\n",
                    mc->code,i,c,c);
            }
            output_char(info,
                            c,
                            0,
                            0);
        }
        if (mc->parm0) {
            output_byte_str(info,
                            mc->parm0,
                            0,
                            0);
        } else {
            output_byte_val(info,
                            0,
                            0,
                            0);
        }
        sprintf(valstr,"func_%s",mc->fptr->func_name);
        output_byte_str(info,
                        valstr,
                        0,
                        0);
        mc = mc->next;
    }

    //
    // terminate with 0 length
    //
    output_byte_val(info,
                    0,
                    0,
                    0);

    output_flush(info);
}

void order_string(MInfo *info, MString *ms, int needname)
{
    static int string_order = 0;
    static int stblk_lbl_id = 0;
    char strdat_label[100];

    if (needname) {
        if (ms->name && ms->name[0] == 0) {
            free(ms->name);
            ms->name = 0;
        }
        if (!ms->name) {
            sprintf(strdat_label, "str_blk_%d", stblk_lbl_id);
            stblk_lbl_id++;
            ms->name = strdup(strdat_label);
        }
    }

    ms->idx = string_order++;
    ms->next_order = 0;

    *info->string_order_tail = ms;
    info->string_order_tail  = &ms->next_order;

    if (DB_show_strings) {
        printf("   order(%3d) %19s: \"%s\"\n",
            ms->idx,
            ms->name?ms->name:"???",
            ms->string);
    }
}

void order_strings(MInfo *info)
{
    MString *ms;

    //
    // put named strings into blocks
    //  (includes unnamed strings in modes containing named strings)
    //
    for (ms = info->string_head; ms; ) {
        int first = 1;
        int has_name = 0;
        MString *ms_end = ms->next;
        if (ms->name && ms->name[0]) has_name = 1;
        if (DB_show_strings) {
            printf("STRING GROUP: %d\n",ms->string_group);
            printf("   %30s: \"%s\"\n",
                ms->name?ms->name:"???",
                ms->string);
        }
        while(ms_end && ms_end->string_group == ms->string_group) {
            if (DB_show_strings) {
                printf("   %30s: \"%s\"\n",
                    ms_end->name?ms_end->name:"???",
                    ms_end->string);
            }
            if (ms_end->name && ms_end->name[0]) has_name = 1;
            ms_end = ms_end->next;
        }
        while(ms != ms_end) {
            if (has_name) {
                order_string(info, ms, first);
                first = 0;
            }
            ms = ms->next;
        }
    }

    //
    // put unnamed strings into blocks
    //
    for (ms = info->string_head; ms; ) {
        int first = 1;
        MString *ms_end = ms->next;
        if (ms->idx != -1) {
            ms = ms->next;
            continue;
        }
        ASSERT(ms->name == 0 || ms->name[0] == 0);
        while(ms_end && ms_end->string_group == ms->string_group) {
            ASSERT(ms_end->name == 0 || ms_end->name[0] == 0);
            ms_end = ms_end->next;
        }
        while(ms != ms_end) {
            order_string(info, ms, first);
            first = 0;
            ms = ms->next;
        }
    }
}

void order_mode(MInfo *info, MMode *mm)
{
    ASSERT(mm->next_mode_order == 0);
    ASSERT(mm->idx == -1);

    mm->idx = info->mode_cnt++;

    *info->mode_order_tail = mm;
    info->mode_order_tail  = &mm->next_mode_order;
}

void order_mode_unknown(MInfo *info, MMode *mm)
{
    ASSERT(mm->next_mode_order == 0);
    ASSERT(mm->idx == -1);

    mm->idx = 0;

    *info->mode_order_tail = mm;
    info->mode_order_tail  = &mm->next_mode_order;
}

void order_modes(MInfo *info)
{
    MMenu *mm = info->menu_head;
    MText *mt = info->text_head;
    MEntry *me = info->entry_head;
    MFunc *mf = info->func_head;
    MUnknown *mu;

    while(mt) {
        if (mt->cnt == 0) {
            fprintf(stderr,"Text mode %s has no menu lines!",mt->mode.name);
        } else {
            order_mode(info, &mt->mode);
            if (strcmp(mt->next_mode, "POP")) {
                find_mode(info, mt->next_mode, 1);
            }
        }
        mt = mt->next;
    }

    while(mm) {
        if (mm->cnt == 0) {
            fprintf(stderr,"Menu %s has no menu items!",mm->mode.name);
        } else {
            MMItem *mmi;
            order_mode(info, &mm->mode);
            for (mmi = mm->head; mmi; mmi = mmi->next) {
                if (strcmp(mmi->mode, "POP")) {
                    find_mode(info, mmi->mode, 1);
                }
            }
        }
        mm = mm->next;
    }

    while(me) {
        order_mode(info, &me->mode);
        find_fptr(info, me->enter_func, 1);
        me = me->next;
    }

    while(mf) {
        order_mode(info, &mf->mode);
        mf = mf->next;
    }

    mu = info->unknown_head;
    if (mu) {
#if 0
        info->current_string_group++;
        add_string(info, "str_unknown_mode", 
                "Undefined mode!", 15)
        add_string(info, "str_unknown_mode2", 
                " ", 1)
#endif
        while(mu) {
            fprintf(stderr,"WARNING: Unknown symbol: %s\n",mu->mode.name);
            order_mode_unknown(info, &mu->mode);
            mu = mu->next;
        }
    }

    //ASSERT(info->mode_cnt < 0x80);    // hi bit indicates push/pop
    ASSERT(info->mode_cnt < 0x100); // hi bit indicates push/pop
}


void output_strings(MInfo *info)
{
    char cmmt[1000];
    int cnt = 0;

    MString *ms;
    MStrBlock *sb;

    output_flush(info);

    fprintf(info->out, "\n");
    fprintf(info->out, "\n");
    fprintf(info->out, ";\n");
    fprintf(info->out, "; Strings\n");
    fprintf(info->out, ";\n");

    fprintf(stderr,"    String count:       %d\n",info->string_cnt);

    fprintf(info->out, "\n");
    fprintf(info->out, "\n");
    fprintf(info->out, ";\n");
    fprintf(info->out, "; string data\n");
    fprintf(info->out, ";\n");
    fprintf(info->out, "strings:\n");

    //
    // string data
    // each string is preceded by a 1 byte length.
    // length does NOT include the length byte
    //

    for (ms=info->string_order_head; ms; ms=ms->next_order) {
        char strdat_label[100];
        char *s = ms->string;
        int len = ms->len;

        ASSERT(cnt == ms->idx);
        output_byte_val(info,
                        len,
                        ms->name,
                        quote_string(info,ms));

        while(len > 0) {
            output_char(    info,
                            *s,
                            0,
                            0);
            s++;
            len--;
        }

        cnt++;
    }

    ASSERT(cnt == info->string_cnt);
    
    output_flush(info);
}

void output_string_defs(MInfo *info)
{
    MString *ms;
    int max = 0;

    output_flush(info);

    fprintf(info->out, "\n");
    fprintf(info->out, ";\n");
    fprintf(info->out, "; String labels\n");
    fprintf(info->out, ";\n");

    for (ms=info->string_order_head; ms; ms=ms->next_order) {
        if (ms->idx > 255) break;
        if (!ms->name || !ms->name[0]) continue;
        if (!strncmp(ms->name, "str_blk_", 8)) continue;
        fprintf(info->out, "%-15s\tEQU\t%d\t\t; %s\n",
            ms->name,
            ms->idx,
            quote_string(info, ms));
        max = ms->idx;
    }

    fprintf(info->out, "%-15s\tEQU\t%d\t\t; %s\n",
        "str_max",
        max,
        "largest valid string id in strblock 0");
}

void output_fptrs(MInfo *info)
{
    char cmmt[1000];

    MFPtr *mf = info->fptr_head;

    output_flush(info);

    fprintf(info->out, "\n");
    fprintf(info->out, "\n");
    fprintf(info->out, ";\n");
    fprintf(info->out, "; Function Table\n");
    fprintf(info->out, ";\n");
    /*fprintf(info->out, "  movf    PCLATH,f\n");*/
    fprintf(info->out, "    addwf   PCL\n");
    fprintf(info->out, "\n");
    fprintf(info->out, "func_table:\n");
    fprintf(info->out, "tfunc_nop:\n");
    fprintf(info->out, "    return\n");

    while(mf) {
        fprintf(info->out, "tfunc_%s:\n",mf->func_name);
        fprintf(info->out, "    bra     %s\n",mf->func_name);
        mf = mf->next;
    }
}

void output_fptr_defs(MInfo *info)
{
    char cmmt[1000];

    MFPtr *mf = info->fptr_head;
    MFPtr *mf_last = mf;

    output_flush(info);

    fprintf(info->out, "\n");
    fprintf(info->out, "\n");
    fprintf(info->out, "\n");
    fprintf(info->out, "func_%-18s EQU\t\t-func_table+tfunc_%s\n",
            "nop","nop");

    while(mf) {
        fprintf(info->out, "func_%-18s EQU\t\t-func_table+tfunc_%s\n",
            mf->func_name, mf->func_name);
        mf_last = mf;
        mf = mf->next;
    }

    if (mf_last) {
        fprintf(info->out, "func_%-18s EQU\t\tfunc_%s\n",
            "max",mf_last->func_name);
    } else {
        fprintf(info->out, "func_%-18s EQU\t\tfunc_nop\n",
            "max");
    }
    
}

void output_defs(MInfo *info)
{
    MDefine *md = info->def_head;

    while(md) {
        fprintf(info->out, "#define %s  %s\n",
            md->name, md->def);
        md = md->next;
    }
}

void output_scfuncs(MInfo *info)
{
    MSCFunc *scf = info->scfunc_head;

    if (scf) {
        ASSERT(scf->sc_val == SPECIAL_CHAR_MIN);
    }

    while(scf) {
        ASSERT(scf->sc_val <= SPECIAL_CHAR_MAX);
        fprintf(info->out, "\tbra\t\t%-20s; 0x%02x\n",
            scf->name,
            scf->sc_val);
        ASSERT(!scf->next || scf->next->sc_val == scf->sc_val + 1);
        scf = scf->next;
    }
}

void output_header(MInfo *info, char *filename, char *cmmt, char *cc)
{
    fprintf(info->out, "%s Justice Unlimited Gadget Microcode\n",cc);
    fprintf(info->out, "%s (C) 2004 Nathan (Acorn) Pooley \n",cc);
    fprintf(info->out, "%s\n",cc);
    if (filename && *filename) {
        fprintf(info->out, "%s %s\n",cc, filename);
        fprintf(info->out, "%s\n",cc);
    }
    if (cmmt && *cmmt) {
        fprintf(info->out, "%s@DO",cc);
        fprintf(info->out,     "C@ %s\n", cmmt);
        fprintf(info->out, "%s\n",cc);
    }
    fprintf(info->out, "\n");
    fprintf(info->out, "%s WARNING: DO NOT EDIT THIS FILE BY HAND\n",cc);
    fprintf(info->out, "%s This file is autogenerated by ju_convert.\n",cc);
    fprintf(info->out, "\n");
    fprintf(info->out, "\n");
}

void write_deps(MInfo *info)
{
    int i;
    MDep *dp;
    if (!info->dep_filename) {
        return;
    }
    info->out = fopen(info->dep_filename,"wt");
    if (!info->out) {
        warn(info, "Could not write dep file");
        return;
    }
    output_header(info, info->dep_filename, "Dependancy list", "#");

    for (i=0; i<1000; i++) {
        char *targ = 0;
        dp = info->dep_head;
        if (!dp) break;
        switch (i) {
            case 0: targ = info->string_filename; break;
            case 1: targ = info->func_filename; break;
            case 2: targ = info->schar_filename; break;
            case 3: targ = info->def_filename; break;
            case 4: targ = info->dep_filename; break;
            default: i=1000; break;
        }
        if (!targ) continue;

        fprintf(info->out, "%s:\t\\\n",targ);

        while(dp) {
            fprintf(info->out, "\t%s\t\\\n",dp->filename);
            dp = dp->next;
        }

        fprintf(info->out, "\n\n");
    }

#if 0
    //
    // add rule to ignore file if it goes away
    //
    dp = info->dep_head;
    while(dp) {
        fprintf(info->out, "%s:\t;\t\n\n");
    }
#endif

    fprintf(info->out, "\n\n");

    fclose(info->out);
    info->out = 0;
}


void close_out_file(MInfo *info)
{
    ASSERT(info->out);

    output_flush(info);
    fprintf(info->out, "\n");

    fclose(info->out);
    info->out = 0;
}

void open_out_file(MInfo *info, char **filename, char *desc)
{
    ASSERT(filename);
    if (!*filename) {
        fprintf(stderr,"Writing %s file to STDOUT\n",desc);
        info->out = stdout;
        *filename = strdup("stdout");
    } else {

        info->out = fopen(*filename,"wt");

        if (!info->out) {
            fprintf(stderr,"ERROR: Could not write to %s file '%s'\n",
                desc,*filename);
            exit(1);
        }
    }

    output_header(info, *filename, desc, ";");
}

void output(MInfo *info)
{
    info->last_byte = 0;
    info->byte_cnt = 0;

    order_strings(info);
    order_modes(info);


    /*
     * strings file (data)
     */
    open_out_file(info, &info->string_filename, "String and Mode data");
    ASSERT(info->out);

    output_modes(info);
    output_strings(info);
    output_codes(info);
    //output_mode_defs(info);
    //output_string_defs(info);

    output_flush(info);

    fprintf(info->out, "\n");
    fprintf(info->out, ";\n");
    fprintf(info->out, "; total data bytes:  %d  (%.3f K)\n",
                info->byte_cnt,
                (float)info->byte_cnt/1024.0f);

    close_out_file(info);


    /*
     * function table file
     */
    open_out_file(info, &info->func_filename, "Jump table");
    ASSERT(info->out);

    output_fptrs(info);
    output_flush(info);
    output_fptr_defs(info);

    close_out_file(info);


    /*
     * special character jump table file
     */
    open_out_file(info, &info->schar_filename, "special character Jump table");
    ASSERT(info->out);

    output_scfuncs(info);

    close_out_file(info);


    /*
     * definitions file
     */
    open_out_file(info, &info->def_filename, "Definitions");
    ASSERT(info->out);

    output_defs(info);
    fprintf(info->out, "\n");
    fprintf(info->out, "\n");

    fprintf(info->out, "#define strdat  (lbl_strdat+off_strdat)\n");

    //output_fptr_defs(info);
    output_mode_defs(info);
    output_string_defs(info);

    close_out_file(info);
}

void init_info(MInfo *info)
{
    memset(info, 0, sizeof(*info));

    info->current_func = func_default;
    info->string_tail = &info->string_head;
    info->string_order_tail = &info->string_order_head;
    info->mode_tail = &info->mode_head;
    info->mode_order_tail = &info->mode_order_head;
    info->menu_tail = &info->menu_head;
    info->text_tail = &info->text_head;
    info->func_tail = &info->func_head;
    info->entry_tail = &info->entry_head;
    info->unknown_tail = &info->unknown_head;
    info->fptr_tail = &info->fptr_head;
    info->def_tail = &info->def_head;
    info->scfunc_tail = &info->scfunc_head;
    info->code_tail = &info->code_head;
    info->dep_tail = &info->dep_head;
    info->if_stack = 0;
    info->infile_stack = 0;
    info->enable = 1;
    info->next_sc_val = SPECIAL_CHAR_MIN;
}

void usage(void)
{
    printf("USAGE: ju_convert [OPTIONS]\n");
    printf("OPTIONS:\n");
    printf("   -i <filename>  - input filename (default = STDIN)\n");
    printf("   -s <filename>  - output strings filname (default = STDOUT)\n");
    printf("   -f <filename>  - output funcs filname (default = STDOUT)\n");
    printf("   -d <filename>  - output defs filname (default = STDOUT)\n");
    printf("   -c <filename>  - output special-char filname (def = STDOUT)\n");
    printf("   -p <filename>  - output dependancy filname (default = none)\n");
}

void parse_args(MInfo *info, int argc, char *argv[])
{
    int i;
    for (i=1; i<argc; i++) {
        if (argv[i][0] == '-') {
            char **str_p = 0;
            if (!strcmp(argv[i],"-?")) {
                usage();
                exit(0);
            } else if (!strcmp(argv[i],"-i")) {
                str_p = &info->in_filename;
            } else if (!strcmp(argv[i],"-s")) {
                str_p = &info->string_filename;
            } else if (!strcmp(argv[i],"-f")) {
                str_p = &info->func_filename;
            } else if (!strcmp(argv[i],"-d")) {
                str_p = &info->def_filename;
            } else if (!strcmp(argv[i],"-c")) {
                str_p = &info->schar_filename;
            } else if (!strcmp(argv[i],"-p")) {
                str_p = &info->dep_filename;
            } else {
                fprintf(stderr,"Unknown argument '%s'\n",argv[i]);
                usage();
                exit(1);
            }

            if (str_p) {
                i++;
                if (i>=argc) {
                    fprintf(stderr,"ERROR: '%s' not followed by filename\n",
                        argv[i-1]);
                    usage();
                    exit(1);
                }
                if (*str_p) {
                    fprintf(stderr,"ERROR: '%s' argument specified twice\n",
                        argv[i-1]);
                    usage();
                    exit(1);
                }
                *str_p = strdup(argv[i]);
            }

        } else {
            fprintf(stderr,"Extra argument: '%s'\n",argv[i]);
            usage();
            exit(1);
        }
    }
}

int main(int argc, char *argv[])
{
    MInfo info;

    //
    // initialize Mode Info structure
    //
    init_info(&info);

    //
    // parse arguments
    //
    parse_args(&info, argc, argv);

    //
    // parse input file
    //
    input(&info);

    //
    // write output files
    //
    output(&info);

    //
    // write dependancies
    //
    write_deps(&info);

    return 0;
}

This file Copyright (C) 2004 by Nathan (Acorn) Pooley
Go to DRUID Development page
Go to DRUID page
Go to JU Gadgets page
Go to Justice Unlimited homepage
Go to Acorn's personal webpage
Contact Acorn
See comments from others
Post your own comments
File created by do_doc at Wed Aug 4 18:08:21 2004