/*
    logicexp.c

    Convert PSpice LOGICEXP logic expressions into XSPICE gates.
    Extract typical timing delay estimates from PINDLY statements and
    insert buffers and tristates with these delays.

    Reference: PSpice A/D Reference Guide version 16.6
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "ngspice/memory.h"
#include "ngspice/macros.h"
#include "ngspice/bool.h"
#include "ngspice/ngspice.h"
#include "ngspice/stringskip.h"
#include "ngspice/stringutil.h"
#include "ngspice/dstring.h"
#include "ngspice/logicexp.h"
#include "ngspice/udevices.h"

/* Turn off/on debug tracing */
#define PRINT_ALL    FALSE
//#define PRINT_ALL    TRUE

/* Start of btree symbol table */
#define SYM_INPUT       1
#define SYM_OUTPUT      2
#define SYM_TMODEL      4
#define SYM_KEY_WORD    8
#define SYM_ID          16
#define SYM_GATE_OP     32
#define SYM_INVERTER    64
#define SYM_OTHER       128

typedef struct sym_entry *SYM_TAB;
struct sym_entry {
    char *name;
    char *alias;
    int attribute;
    int ref_count; // for inverters
    SYM_TAB left;
    SYM_TAB right;
};

static SYM_TAB new_sym_entry(char *name, int attr)
{
    SYM_TAB newp;
    newp = TMALLOC(struct sym_entry, 1);
    newp->left = NULL;
    newp->right = NULL;
    newp->name = TMALLOC(char, strlen(name) + 1);
    strcpy(newp->name, name);
    newp->alias = NULL;
    newp->attribute = attr;
    newp->ref_count = 0;
    return newp;
}

static SYM_TAB insert_sym_tab(char *name, SYM_TAB t, int attr)
{
    int cmp;
    if (t == NULL) {
        t = new_sym_entry(name, attr);
        return t;
    }
    cmp = strcmp(name, t->name);
    if (cmp < 0) {
        t->left = insert_sym_tab(name, t->left, attr);
    } else if (cmp > 0) {
        t->right = insert_sym_tab(name, t->right, attr);
    } else {
        printf("NOTE insert_sym_tab %s already there\n", name);
    }
    return t;
}

static SYM_TAB member_sym_tab(char *name, SYM_TAB t)
{
    int cmp;
    while (t != NULL) {
        cmp = strcmp(name, t->name);
        if (cmp == 0) {
            return t;
        } else if (cmp < 0) {
            t = t->left;
        } else {
            t = t->right;
        }
    }
    return NULL;
}

static SYM_TAB add_sym_tab_entry(char *name, int attr, SYM_TAB *stab)
{
    SYM_TAB entry = NULL;
    entry = member_sym_tab(name, *stab);
    if (!entry) {
        *stab = insert_sym_tab(name, *stab, attr);
        entry = member_sym_tab(name, *stab);
    }
    return entry;
}

static void alias_sym_tab(char *alias, SYM_TAB t)
{
    if (t == NULL) { return; }
    if (t->alias)
        tfree(t->alias);
    t->alias = TMALLOC(char, strlen(alias) + 1);
    strcpy(t->alias, alias);
}

static void delete_sym_tab(SYM_TAB t)
{
    if (t == NULL) { return; }
    delete_sym_tab(t->left);
    delete_sym_tab(t->right);
    if (t->name)
        tfree(t->name);
    if (t->alias)
        tfree(t->alias);
    tfree(t);
}

static void print_sym_tab(SYM_TAB t, BOOL with_addr)
{
    if (t == NULL) { return; }
    print_sym_tab(t->left, with_addr);
    if (with_addr)
        printf("%p --> \n", (void *)t);
    printf("\"%s\"    %d  ref_count=%d", t->name, t->attribute, t->ref_count);
    if (t->alias)
        printf("  alias = \"%s\"", t->alias);
    printf("\n");
    print_sym_tab(t->right, with_addr);
}
/* End of btree symbol table */

/* Start of lexical scanner */
#define LEX_ID       256
#define LEX_OTHER    257
#define LEX_BUF_SZ   512
#define LEX_INIT_SZ  128

typedef struct lexer *LEXER;
struct lexer {
    char *lexer_buf;
    char *lexer_line;
    int lexer_pos;
    int lexer_back;
    SYM_TAB lexer_sym_tab;
    size_t lexer_blen;
};

static LEXER parse_lexer = NULL;

static LEXER new_lexer(char *line)
{
    LEXER lx;
    lx = TMALLOC(struct lexer, 1);
    lx->lexer_line = TMALLOC(char, (strlen(line) + 1));
    strcpy(lx->lexer_line, line);
    lx->lexer_pos = lx->lexer_back = 0;
    lx->lexer_blen = LEX_INIT_SZ;
    lx->lexer_buf = TMALLOC(char, lx->lexer_blen);
    (void) memset(lx->lexer_buf, 0, lx->lexer_blen);
    lx->lexer_sym_tab = NULL;
    return lx;
}

static void delete_lexer(LEXER lx)
{
    if (!lx)
        return;
    if (lx->lexer_buf)
        tfree(lx->lexer_buf);
    if (lx->lexer_line)
        tfree(lx->lexer_line);
    if (lx->lexer_sym_tab)
        delete_sym_tab(lx->lexer_sym_tab);
    tfree(lx);
}

static void lex_init(char *line)
{
    parse_lexer = new_lexer(line);
    return;
}

static int lexer_set_start(char *s, LEXER lx)
{
    char *pos;
    if (!lx)
        return -1;
    pos = strstr(lx->lexer_line, s);
    if (!pos)
        return -1;
    lx->lexer_pos = (int) (pos - &lx->lexer_line[0]);
    lx->lexer_back = lx->lexer_pos;
    return lx->lexer_pos;
}

static int lex_set_start(char *s)
{
    return lexer_set_start(s, parse_lexer);
}

static int lexer_getchar(LEXER lx)
{
    int item = 0;
    item = lx->lexer_line[lx->lexer_pos];
    lx->lexer_back = lx->lexer_pos;
    if (item != 0)
        lx->lexer_pos++;
    return item;
}

static void lexer_putback(LEXER lx)
{
    if (lx->lexer_back >= 0)
        lx->lexer_pos = lx->lexer_back;
}

static int lex_punct(int c)
{
    switch (c) {
    case ',':
    case '{':
    case '}':
    case '(':
    case ')':
    case ':':
    case '.':
        return c;
    default:
        break;
    }
    return 0;
}

static int lex_oper(int c)
{
    switch (c) {
    case '~':
    case '&':
    case '^':
    case '|':
    case '=':
        return c;
    default:
        break;
    }
    return 0;
}

static char *lex_gate_name(int c, BOOL not)
{
    /* returns an XSPICE gate model name */
    static char buf[32];
    switch (c) {
    case '~':
        if (not)
            sprintf(buf, "d__inverter__1");
        else
            sprintf(buf, "d__buffer__1");
        break;
    case '&':
        if (not)
            sprintf(buf, "d__nand__1");
        else
            sprintf(buf, "d__and__1");
        break;
    case '^':
        if (not)
            sprintf(buf, "d__xnor__1");
        else
            sprintf(buf, "d__xor__1");
        break;
    case '|':
        if (not)
            sprintf(buf, "d__nor__1");
        else
            sprintf(buf, "d__or__1");
        break;
    default:
        sprintf(buf, "UNKNOWN");
        break;
    }
    return buf;
}

static char *tmodel_gate_name(int c, BOOL not)
{
    /* Returns an XSPICE model name for the case where
       logicexp does not have a corresponding pindly
       but does have a UGATE timing model (not d0_gate).
    */
    static char buf[32];
    switch (c) {
    case '&':
        if (not)
            sprintf(buf, "dxspice_dly_nand");
        else
            sprintf(buf, "dxspice_dly_and");
        break;
    case '|':
        if (not)
            sprintf(buf, "dxspice_dly_nor");
        else
            sprintf(buf, "dxspice_dly_or");
        break;
    case '^':
        if (not)
            sprintf(buf, "dxspice_dly_xnor");
        else
            sprintf(buf, "dxspice_dly_xor");
        break;
    default:
        return NULL;
    }
    return buf;
}

static int lex_gate_op(int c)
{
    switch (c) {
    case '&':
    case '^':
    case '|':
        return c;
    default:
        break;
    }
    return 0;
}

static int lex_ident(int c)
{
    /* Pspice and MicroCap are vague about what defines an identifier */
    if (isalnum(c) || c == '_' || c == '/' || c == '-')
        return c;
    else
        return 0;
}

static int lexer_scan(LEXER lx)
{
    int c;
    while (1) {
        lx->lexer_buf[0] = '\0';
        c = lexer_getchar(lx);
        if (c == '\0')
            return 0;
        else if (isspace(c))
            continue;
        else if (lex_punct(c))
            return c;
        else if (lex_oper(c))
            return c;
        else if (lex_ident(c)) {
            size_t i = 0;
            while (lex_ident(c)) {
                if (i >= lx->lexer_blen) {
                    lx->lexer_blen *= 2;
                    lx->lexer_buf =
                        TREALLOC(char, lx->lexer_buf, lx->lexer_blen);
                }
                lx->lexer_buf[i] = (char) c;
                i++;
                c = lexer_getchar(lx);
            }
            if (i >= lx->lexer_blen) {
                lx->lexer_blen *= 2;
                lx->lexer_buf =
                    TREALLOC(char, lx->lexer_buf, lx->lexer_blen);
            }
            lx->lexer_buf[i] = '\0';
            if (c != '\0')
                lexer_putback(lx);
            return LEX_ID;
        } else {
            lx->lexer_buf[0] = (char) c;
            lx->lexer_buf[1] = '\0';
            return LEX_OTHER;
        }
    }
}

static int lex_scan(void)
{
    return lexer_scan(parse_lexer);
}

static BOOL lex_all_digits(char *str)
{
    size_t i, slen;
    if (!str) { return FALSE; }
    slen = strlen(str);
    if (slen < 1) { return FALSE; }
    for (i = 0; i < slen; i++) {
        if (!isdigit(str[i])) { return FALSE; }
    }
    return TRUE;
}
/* End of lexical scanner */

/* Start parse table */
typedef struct table_line *TLINE;
struct table_line {
    char *line;
    TLINE next;
};

typedef struct parse_table *PTABLE;
struct parse_table {
    TLINE first;
    TLINE last;
    unsigned int entry_count;
};

static PTABLE parse_tab = NULL;
static PTABLE gen_tab = NULL;

static PTABLE new_parse_table(void)
{
    PTABLE pt;
    pt = TMALLOC(struct parse_table, 1);
    pt->first = pt->last = NULL;
    pt->entry_count = 0;
    return pt;
}

static void delete_parse_table(PTABLE pt)
{
    TLINE t, next;
    if (!pt)
        return;
    next = pt->first;
    while (next) {
        t = next;
        tfree(t->line);
        next = t->next;
        tfree(t);
    }
    tfree(pt);
}

static void delete_parse_gen_tables(void)
{
    delete_parse_table(parse_tab);
    delete_parse_table(gen_tab);
    parse_tab = gen_tab = NULL;
}

static void init_parse_tables(void)
{
    parse_tab = new_parse_table();
    gen_tab = new_parse_table();
}

static TLINE ptab_new_line(char *line)
{
    TLINE t = NULL;
    t = TMALLOC(struct table_line, 1);
    t->next = NULL;
    t->line = TMALLOC(char, (strlen(line) + 1));
    strcpy(t->line, line);
    return t;
}

static TLINE add_common(char *line, BOOL ignore_blank)
{
    if (!line)
        return NULL;
    if (ignore_blank) {
        if (line[0] == '\0') {
            return NULL;
        } else if (line[0] == '\n' && strlen(line) < 2) {
            return NULL;
        }
    }
    return ptab_new_line(line);
}

static TLINE add_to_parse_table(PTABLE pt, char *line, BOOL ignore_blank)
{
    TLINE t;
    if (!pt)
        return NULL;
    t = add_common(line, ignore_blank);
    if (!t)
        return NULL;
    t->next = NULL;
    if (!pt->first) {
        pt->first = pt->last = t;
    } else {
        pt->last->next = t;
        pt->last = t;
    }
    pt->entry_count++;
    return t;
}

static TLINE ptab_add_line(char *line, BOOL ignore_blank)
{
    TLINE t;
    t = add_to_parse_table(parse_tab, line, ignore_blank);
    return t;
}

static TLINE gen_tab_add_line(char *line, BOOL ignore_blank)
{
    TLINE t;
    t = add_to_parse_table(gen_tab, line, ignore_blank);
    return t;
}

static char *get_temp_from_line(char *line, BOOL begin)
{
    /* First occurrence of "tmp" on the line */
    /* If begin is TRUE then "tmp" must be at the start of line */
    static char lbuf[64];
    char *p, *q;
    int j = 0;
    p = strstr(line, "tmp");
    if (!p)
        return NULL;
    if (begin && p != line)
        return NULL;
    for (q = p, j = 0; isalnum(q[j]) || q[j] == '_'; j++) {
        if (j >= 63)
            return NULL;
        lbuf[j] = q[j];
    }
    lbuf[j] = '\0';
    return lbuf;
}

static char *find_temp_begin(char *line)
{
    return get_temp_from_line(line, TRUE);
}

static char *find_temp_anywhere(char *line)
{
    return get_temp_from_line(line, FALSE);
}

static int get_temp_depth(char *line)
{
    char buf[64];
    char *p, *endp;
    int depth;
    p = find_temp_anywhere(line);
    if (p) {
        strcpy(buf, p);
        p = strstr(buf + strlen("tmp"), "__");
        if (p) {
            p = p + 2;
            depth = (int) strtol(p, &endp, 10);
            return depth;
        }
    }
    return -1;
}

static TLINE tab_find(PTABLE pt, char *str, BOOL start_of_line)
{
    TLINE t;
    size_t len;

    if (!pt)
        return NULL;
    t = pt->first;
    len = strlen(str);
    while (t) {
        if (start_of_line) {
            if (strncmp(t->line, str, len) == 0)
                return t;
        } else {
            if (strstr(t->line, str))
                return t;
        }
        t = t->next;
    }
    return NULL;
}

static void ptable_print(PTABLE pt)
{
    TLINE t;
    if (!pt)
        return;
    t = pt->first;
    printf("entry_count %u\n", pt->entry_count);
    while (t) {
        printf("%s\n", t->line);
        t = t->next;
    }
}
/* End parse table */

/* Start of logicexp parser */
static char *get_inst_name(void);
static char *get_inverter_output_name(char *input);
static void aerror(char *s);
static BOOL amatch(int t);
static BOOL bexpr(void);
static BOOL bfactor(void);
static BOOL bparse(char *line, BOOL new_lexer);

static int lookahead = 0;
static int adepth = 0;
static int max_adepth = 0;
static DSTRING d_curr_line;
static int number_of_instances = 0;
static BOOL use_tmodel_delays = FALSE;

static void cleanup_parser(void)
{
    delete_lexer(parse_lexer);
    parse_lexer = NULL;
    delete_parse_gen_tables();
}

static char *get_inst_name(void)
{
    static char name[64];
    static int number = 0;
    number++;
    (void) sprintf(name, "a_%d", number);
    number_of_instances++;
    return name;
}

static char *get_inverter_output_name(char *input)
{
    static char buf[LEX_BUF_SZ];
    LEXER lx = parse_lexer;
    // FIX ME keep this name in the symbol table to ensure uniqueness
    (void) sprintf(buf, "inv_out__%s", input);
    if (member_sym_tab(buf, lx->lexer_sym_tab))
        fprintf(stderr, "ERROR %s is already in use\n", buf);
    return buf;
}

static char *get_inv_tail(char *str)
{
    static char lbuf[64];
    char *p = NULL, *q = NULL;
    int j = 0;
    size_t slen = strlen("inv_out__");

    p = strstr(str, "inv_out__");
    if (!p)
        return NULL;
    for (q = p + slen, j = 0; q[j] != '\0' && !isspace(q[j]); j++) {
        if (j >= 63)
            return NULL;
        lbuf[j] = q[j];
    }
    lbuf[j] = '\0';
    return lbuf;
}

static void gen_models(void)
{
    DS_CREATE(model, 64);

    ds_clear(&model);
    ds_cat_printf(&model,
    ".model d_inv_zero_delay d_inverter(rise_delay=1.0e-12 fall_delay=1.0e-12)");
    u_add_instance(ds_get_buf(&model));

    ds_clear(&model);
    ds_cat_printf(&model,
    ".model d__inverter__1 d_inverter(rise_delay=1.0e-12 fall_delay=1.0e-12)");
    u_add_instance(ds_get_buf(&model));

    ds_clear(&model);
    ds_cat_printf(&model,
    ".model d__buffer__1 d_buffer(rise_delay=1.0e-12 fall_delay=1.0e-12)");
    u_add_instance(ds_get_buf(&model));

    ds_clear(&model);
    ds_cat_printf(&model,
    ".model d__nand__1 d_nand(rise_delay=1.0e-12 fall_delay=1.0e-12)");
    u_add_instance(ds_get_buf(&model));

    ds_clear(&model);
    ds_cat_printf(&model,
    ".model d__and__1 d_and(rise_delay=1.0e-12 fall_delay=1.0e-12)");
    u_add_instance(ds_get_buf(&model));

    ds_clear(&model);
    ds_cat_printf(&model,
    ".model d__xnor__1 d_xnor(rise_delay=1.0e-12 fall_delay=1.0e-12)");
    u_add_instance(ds_get_buf(&model));

    ds_clear(&model);
    ds_cat_printf(&model,
    ".model d__xor__1 d_xor(rise_delay=1.0e-12 fall_delay=1.0e-12)");
    u_add_instance(ds_get_buf(&model));

    ds_clear(&model);
    ds_cat_printf(&model,
    ".model d__nor__1 d_nor(rise_delay=1.0e-12 fall_delay=1.0e-12)");
    u_add_instance(ds_get_buf(&model));

    ds_clear(&model);
    ds_cat_printf(&model,
    ".model d__or__1 d_or(rise_delay=1.0e-12 fall_delay=1.0e-12)");
    u_add_instance(ds_get_buf(&model));

    ds_free(&model);
}

static void aerror(char *s)
{
    LEXER lx = parse_lexer;
    printf("%s [%s]\n", s, lx->lexer_line + lx->lexer_pos);
    fflush(stdout);
    cleanup_parser();
}

char *get_temp_name(void)
{
    static char name[64];
    static int number = 0;
    number++;
    (void) sprintf(name, "tmp%d", number);
    return name;
}

static BOOL amatch(int t)
{
    if (lookahead == t) {
        lookahead = lex_scan();
    } else {
        printf("expect = %d lookahead = %d lexer_buf \"%s\"\n",
            t, lookahead, parse_lexer->lexer_buf);
        aerror("amatch: syntax error");
        return FALSE;
    }
    return TRUE;
}

#define AMATCH_BFACTOR(n) \
{ \
    if (!amatch((n))) { \
        return FALSE; \
    } \
}

static BOOL bfactor(void)
{
    /* factor is : ['~'] rest
       where rest is: input_name_id | '(' expr ')' | error
       [] means optional
    */
    BOOL is_not = FALSE;
    SYM_TAB entry = NULL;
    LEXER lx = parse_lexer;

    adepth++;

    if (lookahead == '~') {
        is_not = TRUE;
        lookahead = lex_scan();
    }

    if (lookahead == LEX_ID) {
        entry = add_sym_tab_entry(lx->lexer_buf, SYM_ID, &lx->lexer_sym_tab);
        if (is_not) {
            ds_cat_printf(&d_curr_line, "%s ",
                get_inverter_output_name(lx->lexer_buf));
            entry->attribute |= SYM_INVERTER;
            entry->ref_count++;
        } else {
            ds_cat_printf(&d_curr_line, "%s ", lx->lexer_buf);
        }

        lookahead = lex_scan();

    } else if (lookahead == '(') {
        DS_CREATE(tmpnam, 64);

        ds_clear(&tmpnam);
        if (adepth > max_adepth)
            max_adepth = adepth;

        ds_cat_str(&tmpnam, get_temp_name());
        (void) ptab_add_line(ds_get_buf(&d_curr_line), TRUE);
        ds_clear(&d_curr_line);
        ds_cat_printf(&d_curr_line, "%s__%d <- ", ds_get_buf(&tmpnam), adepth);

        if (is_not) {
            ds_cat_printf(&d_curr_line, "~ %c", lookahead);
        } else {
            ds_cat_printf(&d_curr_line, "%c", lookahead);
        }
        (void) ptab_add_line(ds_get_buf(&d_curr_line), TRUE);
        ds_clear(&d_curr_line);

        lookahead = lex_scan();
        if (!bexpr()) {
            cleanup_parser();
            return FALSE;
        }

        (void) ptab_add_line(ds_get_buf(&d_curr_line), TRUE);
        ds_clear(&d_curr_line);

        ds_cat_printf(&d_curr_line, "%c -> %s__%d", lookahead,
            ds_get_buf(&tmpnam), adepth);
        (void) ptab_add_line(ds_get_buf(&d_curr_line), TRUE);
        ds_clear(&d_curr_line);

        ds_free(&tmpnam);
        AMATCH_BFACTOR(')');

    } else {
        aerror("bfactor: syntax error");
        return FALSE;
    }
    adepth--;
    return TRUE;
}

static BOOL bexpr(void)
{
    /* expr is: factor { gate_op factor }+
       where {}+ means 0 or more times.
    */
    if (!bfactor()) {
        cleanup_parser();
        return FALSE;
    }

    while (lex_gate_op(lookahead)) {
        ds_cat_printf(&d_curr_line, "%c ", lookahead);

        lookahead = lex_scan();
        if (!bfactor()) {
            cleanup_parser();
            return FALSE;
        }
    }
    return TRUE;
}

#define AMATCH_BSTMT(n) \
{ \
    if (!amatch((n))) { \
        ds_free(&tname); ds_free(&assign); \
        return FALSE; \
    } \
}

static BOOL bstmt(void)
{
    /* A stmt is: output_name_id = '{' expr '}' */
    BOOL verbose = PRINT_ALL;
    int end_pos = 0, start_pos = 0;
    SYM_TAB entry = NULL;
    DS_CREATE(tname, 64);
    DS_CREATE(assign, LEX_BUF_SZ);

    if (lookahead == LEX_ID) {
        entry = add_sym_tab_entry(parse_lexer->lexer_buf, SYM_ID,
            &parse_lexer->lexer_sym_tab);
    } else {
        aerror("bstmt: syntax error");
        return FALSE;
    }

    adepth++;
    if (adepth > max_adepth)
        max_adepth = adepth;

    if (verbose) {
        start_pos = parse_lexer->lexer_pos;
        printf("* %s", parse_lexer->lexer_buf);
    }

    AMATCH_BSTMT(LEX_ID);
    AMATCH_BSTMT('=');

    ds_clear(&assign);
    ds_cat_printf(&assign, "%s =", entry->name);
    (void) ptab_add_line(ds_get_buf(&assign), TRUE);

    AMATCH_BSTMT('{');

    ds_clear(&tname);
    ds_cat_str(&tname, get_temp_name());
    ds_cat_printf(&d_curr_line, "%s__%d <- (", ds_get_buf(&tname), adepth);
    (void) ptab_add_line(ds_get_buf(&d_curr_line), TRUE);
    ds_clear(&d_curr_line);

    if (!bexpr()) {
        cleanup_parser();
        ds_free(&assign);
        ds_free(&tname);
        return FALSE;
    }

    if (ds_get_length(&d_curr_line) > 0) {
        (void) ptab_add_line(ds_get_buf(&d_curr_line), TRUE);
    }
    ds_clear(&d_curr_line);
    ds_cat_printf(&d_curr_line, ") -> %s__%d", ds_get_buf(&tname), adepth);
    (void) ptab_add_line(ds_get_buf(&d_curr_line), TRUE);
    ds_clear(&d_curr_line);

    if (verbose) {
        DS_CREATE(stmt_str, 128);
        end_pos = parse_lexer->lexer_pos;
        ds_cat_mem(&stmt_str, &parse_lexer->lexer_line[start_pos],
            (size_t) (end_pos - start_pos));
        printf("%s\n", ds_get_buf(&stmt_str));
        ds_free(&stmt_str);
    }

    AMATCH_BSTMT('}');

    ds_free(&assign);
    ds_free(&tname);
    adepth--;
    return TRUE;
}

static PTABLE optimize_gen_tab(PTABLE pt)
{
    /* This function compacts the gen_tab, returning a new PTABLE.
       Aliases are transformed and removed as described below.
       Usually, optimize_gen_tab is called a second time on the
       PTABLE created by the first call. The algorithm here will
       only transform one level of aliases.
    */
    TLINE t = NULL;
    LEXER lxr = NULL;
    int val, idnum = 0, tok_count = 0;
    SYM_TAB entry = NULL, alias_tab = NULL;
    BOOL found_tilde = FALSE, starts_with_temp = FALSE;
    BOOL prit = PRINT_ALL;
    PTABLE new_gen = NULL;
    DS_CREATE(scratch, LEX_BUF_SZ);
    DS_CREATE(alias, 64);
    DS_CREATE(non_tmp_name, 64);
    DS_CREATE(tmp_name, 64);

    if (!pt || !pt->first) {
        ds_free(&scratch);
        ds_free(&alias);
        ds_free(&non_tmp_name);
        ds_free(&tmp_name);
        return NULL;
    }
    t = pt->first;
    lxr = new_lexer(t->line);
    /* Look for tmp... = another_name
         t1 = name1 (alias for t1)
         t2 = name2 (alias for t2)
         t3 = t1 op t2
       during second pass transform
         ignore t1, t2
         t3 = name1 op name2
    */
    while (t) {
        idnum = 0;
        val = lexer_scan(lxr);
        ds_clear(&alias);
        entry = NULL;
        found_tilde = FALSE;
        if (find_temp_begin(t->line))
            starts_with_temp = TRUE;
        else
            starts_with_temp = FALSE;
        tok_count = 0;
        while (val != '\0') {
            tok_count++;
            if (val == LEX_ID) {
                idnum++;
                if (idnum == 1) {
                    entry = add_sym_tab_entry(lxr->lexer_buf, SYM_ID,
                        &alias_tab);
                } else if (idnum == 2) {
                    ds_cat_str(&alias, lxr->lexer_buf);
                }
            } else if (val == '~') {
                found_tilde = TRUE;
                if (tok_count != 3) {
                    goto quick_return;
                }
            } else if (val == '=') {
                if (tok_count != 2) {
                    goto quick_return;
                }
            }
            val = lexer_scan(lxr);
        }
        if (starts_with_temp && !found_tilde && idnum == 2)
            alias_sym_tab(ds_get_buf(&alias), entry);
        t = t->next;
        if (t) {
            delete_lexer(lxr);
            lxr = new_lexer(t->line);
        }
    }
    if (prit) {
        printf("alias_tab:\n");
        print_sym_tab(alias_tab, FALSE);
    }
    delete_lexer(lxr);


    /* Second pass, replace names by their aliases.
       Perform transformation as mentioned above.
       Transform:
         t1 = t2 op t3 {op t4 ...} (t* can also be name*, not just tmps)
         lhs = t1 (lhs of original x = { expr } statement)
       into:
         ignore lhs = t1
         lhs = t2 op t3 {op t4...}
       NOTE that lhs_= t1 should be the last entry in gen_tab.
       lhs = t1 (from stmt lhs = { expr }) is the top-most level
       in the parse tree, and is encountered last in the evaluation order.
    */
    new_gen = new_parse_table();
    ds_clear(&scratch);
    t = pt->first;
    lxr = new_lexer(t->line);
    while (t) { // while (t) second pass
        BOOL skip = FALSE;

        val = lexer_scan(lxr);
        idnum = 0;
        entry = NULL;
        if (find_temp_begin(t->line))
            starts_with_temp = TRUE;
        else
            starts_with_temp = FALSE;
        tok_count = 0;
        ds_clear(&scratch);
        ds_clear(&non_tmp_name);
        ds_clear(&tmp_name);
        while (val != '\0' && !skip) {
            tok_count++;
            if (val == LEX_ID) {
                idnum++;
                entry = member_sym_tab(lxr->lexer_buf, alias_tab);
                if (entry && entry->alias) {
                    if (idnum > 1) {
                        ds_cat_printf(&scratch, "%s ", entry->alias);
                    } else if (idnum == 1) {
                        if (starts_with_temp) {
                            skip = TRUE;
                        }
                    }
                } else {
                    ds_cat_printf(&scratch, "%s ", lxr->lexer_buf);
                    if (tok_count == 1) {
                        ds_clear(&non_tmp_name);
                        if (!find_temp_begin(lxr->lexer_buf))
                            ds_cat_str(&non_tmp_name, lxr->lexer_buf);
                    } else if (tok_count == 3) {
                        if (ds_get_length(&non_tmp_name) > 0) {
                            char *str1 = NULL;
                            str1 = find_temp_begin(lxr->lexer_buf);
                            if (str1) {
                                ds_clear(&tmp_name);
                                ds_cat_str(&tmp_name, lxr->lexer_buf);
                            }
                        }
                    }
                }

                if (idnum > 2) {
                    ds_clear(&non_tmp_name);
                    ds_clear(&tmp_name);
                }
            } else {
                if (val == LEX_OTHER) {
                    delete_parse_table(new_gen);
                    new_gen = NULL;
                    goto quick_return;
                }
                ds_cat_printf(&scratch, "%c ", val);
            }
            val = lexer_scan(lxr);
        }
        t = t->next;
        if (t) {
            delete_lexer(lxr);
            lxr = new_lexer(t->line);
        }
        if (!skip) {
            TLINE tnamel = NULL;
            char *p = NULL;
            DS_CREATE(d_buf, 128);
            BOOL ignore_lhs = FALSE;

            ds_clear(&d_buf);
            if (ds_get_length(&tmp_name) > 0)
                tnamel = tab_find(new_gen, ds_get_buf(&tmp_name), TRUE);
            if (ds_get_length(&non_tmp_name) > 0 && tnamel) {
                ignore_lhs = TRUE;

                ds_clear(&d_buf);
                p = strstr(tnamel->line, " = ");
                if (p) {
                    ds_cat_str(&d_buf, ds_get_buf(&non_tmp_name));
                    ds_cat_str(&d_buf, p);
                    tfree(tnamel->line);
                    tnamel->line = TMALLOC(char, ds_get_length(&d_buf) + 1);
                    strcpy(tnamel->line, ds_get_buf(&d_buf));
                }
            }
            if (!ignore_lhs) {
                (void) add_to_parse_table(new_gen,
                    ds_get_buf(&scratch), TRUE);
            }
            ds_free(&d_buf);
        }
    } // end of while (t) second pass

quick_return:
    if (new_gen && new_gen->entry_count == 0) {
        delete_parse_table(new_gen);
        new_gen = NULL;
    }
    ds_free(&alias);
    ds_free(&scratch);
    ds_free(&non_tmp_name);
    ds_free(&tmp_name);
    delete_lexer(lxr);
    delete_sym_tab(alias_tab);

    return new_gen;
}

static BOOL gen_gates(PTABLE gate_tab, SYM_TAB parser_symbols)
{
    /* gen_gates is called with PTABLE gate_tab being the final
       PTABLE produced by optimize_gen_tab(,..) calls.
       If gate tab is the orignal uncompacted gen_tab, then extra
       redundant intermediate gates will be created.
    */
    TLINE t;
    LEXER lxr = NULL;
    int val, tok_count = 0, gate_op = 0, idnum = 0, in_count = 0;
    BOOL found_tilde = FALSE;
    BOOL prit = PRINT_ALL;
    DS_CREATE(out_name, 64);
    DS_CREATE(in_names, 64);
    DS_CREATE(gate_name, 64);
    DS_CREATE(instance, 128);

    if (!gate_tab || !gate_tab->first) {
        ds_free(&out_name);
        ds_free(&in_names);
        ds_free(&gate_name);
        ds_free(&instance);
        return FALSE;
    }
    t = gate_tab->first;
    lxr = new_lexer(t->line);
    while (t) { // while t loop
        ds_clear(&out_name);
        ds_clear(&in_names);
        ds_clear(&gate_name);
        ds_clear(&instance);
        idnum = 0;
        val = lexer_scan(lxr);
        found_tilde = FALSE;
        tok_count = 0;
        gate_op = 0;
        in_count = 0;
        while (val != '\0') {  // while val loop
            tok_count++;
            if (val == LEX_ID) {
                idnum++;
                if (idnum == 1) { //output name
                    ds_cat_str(&out_name, lxr->lexer_buf);
                } else { // input name
                    char *tail = NULL;
                    in_count++;
                    tail = get_inv_tail(lxr->lexer_buf);
                    if (tail && strlen(tail) > 0) {
                        ds_cat_printf(&in_names, " ~%s", tail);
                        if (prit) {
                            printf(
                            "change input name \"%s\" tail \"~%s\"\n",
                            lxr->lexer_buf, tail);
                        }
                    } else {
                        ds_cat_printf(&in_names, " %s", lxr->lexer_buf);
                    }
                }
            } else if (val == '~') {
                found_tilde = TRUE;
                if (tok_count != 3) goto gen_error;
            } else if (val == '=') {
                if (tok_count != 2) goto gen_error;
            } else if (lex_gate_op(val)) {
                if (gate_op != 0) {
                   if (val != gate_op) goto gen_error;
                }
                gate_op = val;
            } else {
                goto gen_error;
            }
            val = lexer_scan(lxr);
        }  // end while val loop

        if (in_count == 1) { // buffer or inverter
            if (gate_op != 0) goto gen_error;
            ds_cat_str(&gate_name, lex_gate_name('~', found_tilde));
        } else if (in_count >= 2) { // AND, OR. XOR and inverses
            if (gate_op == 0) goto gen_error;
            if (use_tmodel_delays) {
                /* This is the case when logicexp has a UGATE
                   timing model (not d0_gate) and no pindly.
                */
                SYM_TAB entry = NULL;
                char *nm1 = 0;
                entry = member_sym_tab(ds_get_buf(&out_name), parser_symbols);
                if (entry && (entry->attribute & SYM_OUTPUT)) {
                    nm1 = tmodel_gate_name(gate_op, found_tilde);
                    if (nm1) {
                        ds_cat_str(&gate_name, nm1);
                    }
                }
                if (!nm1) {
                    nm1 = lex_gate_name(gate_op, found_tilde);
                    ds_cat_str(&gate_name, nm1);
                }
            } else {
                ds_cat_str(&gate_name, lex_gate_name(gate_op, found_tilde));
            }
        } else {
            goto gen_error;
        }
        ds_cat_printf(&instance, "%s ", get_inst_name());
        if (in_count == 1) {
            /* If the input name is inv_out_<tail> use the <tail>
               and instantiate an inverter to avoid an extra buffer.
            */
            char *tail = NULL;
            SYM_TAB ent;
            tail = get_inv_tail(ds_get_buf(&in_names));
            if (tail && strlen(tail) > 0) {
                ds_clear(&gate_name);
                ds_cat_str(&gate_name, lex_gate_name('~', TRUE));
                ds_cat_printf(&instance, "%s %s ", tail,
                    ds_get_buf(&out_name));
                ent = member_sym_tab(tail, parser_symbols);
                if (!ent) {
                    goto gen_error;
                }
                if ((ent->attribute & SYM_INVERTER) == 0) {
                    goto gen_error;
                }
                ent->ref_count--;
            } else {
                ds_cat_printf(&instance, "%s %s ", ds_get_buf(&in_names),
                    ds_get_buf(&out_name));
            }

        } else {
            ds_cat_printf(&instance, "[%s ] %s ", ds_get_buf(&in_names),
                ds_get_buf(&out_name));
        }
        ds_cat_printf(&instance, "%s", ds_get_buf(&gate_name));
        t = t->next;
        if (t) {
            delete_lexer(lxr);
            lxr = new_lexer(t->line);
        }
        if (ds_get_length(&instance) > 0) {
            u_add_instance(ds_get_buf(&instance));
        }
    } // end while t loop

    delete_lexer(lxr);
    ds_free(&out_name);
    ds_free(&in_names);
    ds_free(&gate_name);
    ds_free(&instance);
    return TRUE;

gen_error:
    delete_lexer(lxr);
    ds_free(&out_name);
    ds_free(&in_names);
    ds_free(&gate_name);
    ds_free(&instance);
    return FALSE;
}

/*
    gen_tab lines format:
        name1 = [~] name2 [op name3 {op namei}+]
    [] means optional, {}+ means zero or more times.
    op is gate type (&, |, ^), ~ means invert output.
    name1 is the gate output, and name2,... are inputs.
    & is AND, | is OR, ^ is XOR.
    ~ & is NAND, ~ | is NOR, ~ ^ is XNOR.
    In any given line, all the op values are the same, and don't change.
    AND and OR can have >= 2 inputs, XOR can have only 2 inputs.
    If there is only a single input, then the gate is BUF or INV(~).
*/
static void bevaluate(TLINE t, int deep)
{
    /* TLINE t is the entry in the parse_tab and deep is the call depth
       where the parse_tab is transformed into the gen_tab. The deeper
       calls are evaluated first, bottom-up, as determined by beval_order.
       The tokens in the parse_tab are reassembled into gen_tab lines
       as described above.
    */
    char *s;
    int down = 0;
    DS_CREATE(this, 64);
    DS_CREATE(other, 64);
    DS_CREATE(new_line, LEX_BUF_SZ);

    s = find_temp_begin(t->line);
    if (!s)
        return;
    ds_clear(&other);
    ds_clear(&new_line);
    ds_clear(&this);
    ds_cat_str(&this, s);
    if (strstr(t->line + ds_get_length(&this), " ~ ")) {
        ds_cat_printf(&new_line, "%s =  ~ ", ds_get_buf(&this));
    } else {
        if (deep == 1) {
            ds_cat_printf(&new_line, "%s ", parse_tab->first->line);
        } else {
            ds_cat_printf(&new_line, "%s = ", ds_get_buf(&this));
        }
    }
    t = t->next;
    while (t) {
        s = find_temp_anywhere(t->line);
        if (s) {
            if (eq(ds_get_buf(&this), s)) {
                break;
            } else {
                if (down == 0) {
                    s = find_temp_begin(t->line);
                    ds_clear(&other);
                    ds_cat_str(&other, s);
                    down = 1;
                    ds_cat_printf(&new_line, " %s", ds_get_buf(&other));
                } else if (down == 1) {
                    s = find_temp_anywhere(t->line);
                    if (eq(ds_get_buf(&other), s)) {
                        down = 0;
                        ds_clear(&other);
                    }
                }
            }
        } else if (down == 0) {
            s = find_temp_anywhere(t->line);
            if (!s) {
                ds_cat_printf(&new_line, " %s", t->line);
            }
        }
        t = t->next;
    }
    (void) gen_tab_add_line(ds_get_buf(&new_line), TRUE);
    ds_free(&this);
    ds_free(&other);
    ds_free(&new_line);
    return;
}

static void beval_order(void)
{
    /* The parser is top-down recursive descent. The depth is used
       so that the parsed data is evaluated bottom-up. Then the
       tmp.. regions can be evaluated before they are referenced.
    */
    int i, depth;
    TLINE t;
    size_t slen;

    if (!parse_tab || !parse_tab->first)
        return;
    slen = strlen("tmp");
    for (i = max_adepth; i > 0; i--) {
        t = parse_tab->first;
        while (t) {
            char *q;
            int cmp = 0;
            cmp = strncmp(t->line, "tmp", slen);
            if (cmp == 0 && ((q = strstr(t->line, " <- ")) != NULL)) {
                depth = get_temp_depth(t->line);
                if (depth >= 0) {
                    if (i == depth) {
                        bevaluate(t, i);
                    }
                }
            }
            t = t->next;
        }
    }
    return;
}

static BOOL bparse(char *line, BOOL new_lexer)
{
    int stmt_num = 0;
    BOOL ret_val = TRUE, prit = PRINT_ALL;
    PTABLE opt_tab1 = NULL, opt_tab2 = NULL;
    DS_CREATE(stmt, LEX_BUF_SZ);
    char *seed_buf;

    seed_buf = TMALLOC(char, LEX_BUF_SZ);
    (void) memcpy(seed_buf, "seed", strlen("seed"));

    ds_init(&d_curr_line, seed_buf, strlen("seed"),
        LEX_BUF_SZ, ds_buf_type_heap);
    ds_clear(&d_curr_line);

    if (new_lexer)
        lex_init(line);
    if (!parse_lexer) return FALSE;
    lookahead = lex_set_start("logic:");
    lookahead = lex_scan(); // "logic"
    lookahead = lex_scan(); // ':'
    lookahead = lex_scan();
    while (lookahead != '\0') { // while lookahead loop
        unsigned int last_count = 0, curr_count = 0;
        init_parse_tables();
        adepth = max_adepth = 0;
        stmt_num++;
        ds_clear(&stmt);
        ds_cat_str(&stmt, parse_lexer->lexer_buf);
        if (!bstmt()) {
            cleanup_parser();
            ret_val= FALSE;
            break;
        }

        if (prit) {
            printf("START parse_tab\n");
            ptable_print(parse_tab);
            printf("END parse_tab\n");
        }

        beval_order();

        /* generate gates only when optimizations are successful */
        if (prit) {
            printf("gen_tab ");
            ptable_print(gen_tab);
        }
        last_count = gen_tab->entry_count;
        if (last_count == 1) {
            ret_val = gen_gates(gen_tab, parse_lexer->lexer_sym_tab);
            if (!ret_val) {
                fprintf(stderr, "ERROR generating gates for logicexp\n");
            }
        } else if (last_count > 1) {
            opt_tab1 = optimize_gen_tab(gen_tab);
            if (prit) {
                printf("opt_tab1 ");
                ptable_print(opt_tab1);
            }
            if (opt_tab1) {
                curr_count = opt_tab1->entry_count;
                opt_tab2 = opt_tab1;
                while (curr_count > 1 && curr_count < last_count) {
                    last_count = curr_count;
                    opt_tab2 = optimize_gen_tab(opt_tab1);
                    if (prit) {
                        printf("opt_tab2 ");
                        ptable_print(opt_tab2);
                    }
                    delete_parse_table(opt_tab1);
                    if (!opt_tab2) {
                        ret_val = FALSE;
                        break;
                    }
                    opt_tab1 = opt_tab2;
                    curr_count = opt_tab2->entry_count;
                }
                if (opt_tab2) {
                    ret_val = gen_gates(opt_tab2, parse_lexer->lexer_sym_tab);
                    if (!ret_val) {
                        fprintf(stderr,
                            "ERROR generating gates for logicexp\n");
                    }
                    delete_parse_table(opt_tab2);
                }
            } else {
                ret_val = FALSE;
            }
        } else {
            ret_val = FALSE;
        }
        delete_parse_gen_tables();
        if (!ret_val) {
            break;
        }
    } // end while lookahead loop

    ds_free(&d_curr_line);
    if (ret_val)
        gen_models();
    ds_free(&stmt);
    cleanup_parser();
    return ret_val;
}
/* End of logicexp parser */

/* Start of f_logicexp which is called from udevices.c 
   See the PSpice reference which describes the LOGICEXP statement syntax.

   NOTE: Combinational gates are generated and usually have zero delays.
   In XSPICE, the shortest delays are 1.0e-12 secs, not actually zero.

   Timing delays for LOGICEXP come from an associated PINDLY instance
   when the timing model is d0_gate. Otherwise the timing model is used
   for the delay estimates (see f_logicexp).

   The PINDLY statements generate buffers and tristate buffers
   which drive the primary outputs from the LOGICEXP outputs.
   These buffers and tristates have estimated typical delays.
*/
static LEXER current_lexer = NULL;

static BOOL expect_token(
    int tok, int expected_tok, char *expected_str, BOOL msg, int loc)
{
    if (tok != expected_tok) {
        if (msg) {
            fprintf(stderr,
                "ERROR expect_token failed tok %d expected_tok %d loc %d\n",
                tok, expected_tok, loc);
        }
        return FALSE;
    }
    if (tok == LEX_ID) {
        if (expected_str) {
            LEXER lx = current_lexer;
            if (eq(expected_str, lx->lexer_buf))
                return TRUE;
            else {
                if (msg) {
                    fprintf(stderr,
                    "ERROR expect_token failed lexer_buf %s expected_str %s loc %d\n",
                        lx->lexer_buf, expected_str, loc);
                }
                return FALSE;
            }
        } else { // Any LEX_ID string matches
            return TRUE;
        }
    }
    return TRUE;
}

BOOL f_logicexp(char *line)
{
    int t, num_ins = 0, num_outs = 0, i;
    char *endp;
    BOOL ret_val = TRUE;

    lex_init(line);
    current_lexer = parse_lexer;
    (void) add_sym_tab_entry("logic", SYM_KEY_WORD,
        &parse_lexer->lexer_sym_tab);
    t = lex_scan(); // U*
    if (!expect_token(t, LEX_ID, NULL, TRUE, 1)) goto error_return;
    /* logicexp ( int , int ) */
    t = lex_scan();
    if (!expect_token(t, LEX_ID, "logicexp", TRUE, 2)) goto error_return;
    t = lex_scan();
    if (!expect_token(t, '(', NULL, TRUE, 3)) goto error_return;
    t = lex_scan();
    if (!expect_token(t, LEX_ID, NULL, TRUE, 4)) goto error_return;
    if (lex_all_digits(parse_lexer->lexer_buf)) {
        num_ins = (int) strtol(parse_lexer->lexer_buf, &endp, 10);
    } else {
        fprintf(stderr, "ERROR logicexp input count is not an integer\n");
        goto error_return;
    }
    t = lex_scan();
    if (!expect_token(t, ',', NULL, TRUE, 5)) goto error_return;
    t = lex_scan();
    if (!expect_token(t, LEX_ID, NULL, TRUE, 6)) goto error_return;
    if (lex_all_digits(parse_lexer->lexer_buf)) {
        num_outs = (int) strtol(parse_lexer->lexer_buf, &endp, 10);
    } else {
        fprintf(stderr, "ERROR logicexp output count is not an integer\n");
        goto error_return;
    }
    t = lex_scan();
    if (!expect_token(t, ')', NULL, TRUE, 7)) goto error_return;
    t = lex_scan(); // pwr
    if (!expect_token(t, LEX_ID, NULL, TRUE, 8)) goto error_return;
    t = lex_scan(); // gnd
    if (!expect_token(t, LEX_ID, NULL, TRUE, 9)) goto error_return;
    /* num_ins input ids */
    for (i = 0; i < num_ins; i++) {
        t = lex_scan();
        if (!expect_token(t, LEX_ID, NULL, TRUE, 10)) goto error_return;
        (void) add_sym_tab_entry(parse_lexer->lexer_buf,
            SYM_INPUT, &parse_lexer->lexer_sym_tab);
        u_remember_pin(parse_lexer->lexer_buf, 1);
    }
    /* num_outs output ids */
    for (i = 0; i < num_outs; i++) {
        t = lex_scan();
        if (!expect_token(t, LEX_ID, NULL, TRUE, 11)) goto error_return;
        (void) add_sym_tab_entry(parse_lexer->lexer_buf,
            SYM_OUTPUT, &parse_lexer->lexer_sym_tab);
        u_remember_pin(parse_lexer->lexer_buf, 2);
    }
    /* timing model */
    t = lex_scan();
    if (!expect_token(t, LEX_ID, NULL, TRUE, 12)) goto error_return;
    if (!eq(parse_lexer->lexer_buf, "d0_gate")) {
        u_add_logicexp_model(parse_lexer->lexer_buf,
            "d_and", "dxspice_dly_and");
        u_add_logicexp_model(parse_lexer->lexer_buf,
            "d_nand", "dxspice_dly_nand");
        u_add_logicexp_model(parse_lexer->lexer_buf,
            "d_or", "dxspice_dly_or");
        u_add_logicexp_model(parse_lexer->lexer_buf,
            "d_nor", "dxspice_dly_nor");
        u_add_logicexp_model(parse_lexer->lexer_buf,
            "d_xor", "dxspice_dly_xor");
        u_add_logicexp_model(parse_lexer->lexer_buf,
            "d_xnor", "dxspice_dly_xnor");
        use_tmodel_delays = TRUE;
    } else {
        use_tmodel_delays = FALSE;
    }
    (void) add_sym_tab_entry(parse_lexer->lexer_buf,
        SYM_TMODEL, &parse_lexer->lexer_sym_tab);
    ret_val = bparse(line, FALSE);

    current_lexer = NULL;
    if (!ret_val) {
        fprintf(stderr, "ERROR parsing logicexp\n");
        fprintf(stderr, "ERROR in \"%s\"\n", line);
        cleanup_parser();
    }
    return ret_val;

error_return:
    delete_lexer(parse_lexer);
    current_lexer = NULL;
    return FALSE;
}

/* Start of f_pindly which is called from udevices.c 
   See the PSpice reference which describes the PINDLY statement syntax.

   NOTE that only two sections, PINDLY: and TRISTATE:, are considered.
   Typical delays are estimated from the DELAY(...) functions.
   XSPICE does not have the variety of delays that PSpice supports.
   Output buffers and tristate buffers are generated.
*/
/* C++ with templates would generalize the different TABLEs */
typedef struct pindly_line *PLINE;
struct pindly_line {
    char *in_name;
    char *out_name;
    char *ena_name;
    char *delays;
    PLINE next;
};

typedef struct pindly_table *PINTABLE;
struct pindly_table {
    PLINE first;
    PLINE last;
    int num_entries;
};

static PINTABLE new_pindly_table(void)
{
    PINTABLE pint;
    pint = TMALLOC(struct pindly_table, 1);
    pint->first = pint->last = NULL;
    pint->num_entries = 0;
    return pint;
}

static int num_pindly_entries(PINTABLE pint)
{
    if (!pint)
        return 0;
    else
        return pint->num_entries;
}

static PLINE new_pindly_line(void)
{
    PLINE p = NULL;
    p = TMALLOC(struct pindly_line, 1);
    p->in_name = p->out_name = p->ena_name = p->delays = NULL;
    return p;
}

static PLINE add_new_pindly_line(PINTABLE pint)
{
    PLINE p;
    p = new_pindly_line();
    p->next = NULL;
    if (!pint->first) {
        pint->first = pint->last = p;
    } else {
        pint->last->next = p;
        pint->last = p;
    }
    pint->num_entries++;
    return p;
}

static PLINE set_in_name(char *s, PLINE p)
{
    if (p->in_name) tfree(p->in_name);
    p->in_name = TMALLOC(char, (strlen(s) + 1));
    strcpy(p->in_name, s);
    return p;
}

static PLINE set_out_name(char *s, PLINE p)
{
    if (p->out_name) tfree(p->out_name);
    p->out_name = TMALLOC(char, (strlen(s) + 1));
    strcpy(p->out_name, s);
    return p;
}

static PLINE set_ena_name(char *s, PLINE p)
{
    if (p->ena_name) tfree(p->ena_name);
    p->ena_name = TMALLOC(char, (strlen(s) + 1));
    strcpy(p->ena_name, s);
    return p;
}

static PLINE set_delays(char *s, PLINE p)
{
    if (p->delays) tfree(p->delays);
    p->delays = TMALLOC(char, (strlen(s) + 1));
    strcpy(p->delays, s);
    return p;
}

static void delete_pindly_table(PINTABLE pint)
{
    PLINE p, next;
    if (!pint)
        return;
    next = pint->first;
    while (next) {
        p = next;
        if (p->in_name) tfree(p->in_name);
        if (p->out_name) tfree(p->out_name);
        if (p->ena_name) tfree(p->ena_name);
        if (p->delays) tfree(p->delays);
        next = p->next;
        tfree(p);
    }
    tfree(pint);
}

static void print_pindly_table(PINTABLE pint)
{
    PLINE p, next;
    if (!pint)
        return;
    printf("num_entries %d\n", pint->num_entries);
    next = pint->first;
    while (next) {
        p = next;
        printf("in_name \"%s\"", p->in_name);
        printf(" out_name \"%s\"", p->out_name);
        printf(" ena_name \"%s\"", p->ena_name);
        printf(" delays \"%s\"\n", p->delays);
        next = p->next;
    }
}

static PLINE nth_pindly_entry(PINTABLE pint, int n)
{
    /* Entries are from 0 to num_entries - 1 */
    PLINE p, next;
    int count = 0;
    if (n < 0) return NULL;
    if (n > pint->num_entries - 1) return NULL;
    next = pint->first;
    while (next) {
        p = next;
        if (count == n) return p;
        count++;
        next = p->next;
    }
    return NULL;
}

static PLINE find_pindly_out_name(PINTABLE pint, char *name)
{
    PLINE p, next;
    if (!pint) return NULL;
    next = pint->first;
    while (next) {
        p = next;
        if (eq(p->out_name, name)) return p;
        next = p->next;
    }
    return NULL;
}

static PINTABLE pindly_tab = NULL;

static void init_pindly_tab(void)
{
    pindly_tab = new_pindly_table();
}

static void cleanup_pindly_tab(void)
{
    delete_pindly_table(pindly_tab);
    pindly_tab = NULL;
}

static void gen_pindly_buffers(void)
{
    DS_CREATE(dbuf, 128);
    PLINE pline = NULL;
    BOOL prit = PRINT_ALL;

    if (prit) { print_pindly_table(pindly_tab); }
    pline = pindly_tab->first;
    while (pline) {
        char *iname = NULL;
        ds_clear(&dbuf);
        iname = get_inst_name();
        if (pline->ena_name && strlen(pline->ena_name) > 0) {
            ds_cat_printf(&dbuf, "%s %s %s %s d_tristate_buf_%s", iname,
                pline->in_name, pline->ena_name, pline->out_name, iname);
        } else {
            ds_cat_printf(&dbuf, "%s %s %s d_pindly_buf_%s", iname,
                pline->in_name, pline->out_name, iname);
        }
        u_add_instance(ds_get_buf(&dbuf));
        ds_clear(&dbuf);
        if (pline->ena_name && strlen(pline->ena_name) > 0) {
            ds_cat_printf(&dbuf, ".model d_tristate_buf_%s d_tristate%s",
                iname, pline->delays);
        } else {
            ds_cat_printf(&dbuf, ".model d_pindly_buf_%s d_buffer%s",
                iname, pline->delays);
        }
        u_add_instance(ds_get_buf(&dbuf));
        pline = pline->next;
    }
    ds_free(&dbuf);
}

static char *get_typ_estimate(char *min, char *typ, char *max)
{
    char *tmpmax = NULL, *tmpmin = NULL;
    float valmin, valmax, average;
    char *units1, *units2;
    static char tbuf[128];

    if (typ && strlen(typ) > 0 && typ[0] != '-') {
        strcpy(tbuf, typ);
        return tbuf;
    }
    if (max && strlen(max) > 0 && max[0] != '-') {
        tmpmax = max;
    }
    if (min && strlen(min) > 0 && min[0] != '-') {
        tmpmin = min;
    }
    if (tmpmin && tmpmax) {
        if (strlen(tmpmin) > 0 && strlen(tmpmax) > 0) {
            valmin = strtof(tmpmin, &units1);
            valmax = strtof(tmpmax, &units2);
            average = (valmin + valmax) / (float)2.0;
            sprintf(tbuf, "%.2f%s", average, units2);
            if (!eq(units1, units2)) {
                printf("WARNING units do not match\n");
            }
            return tbuf;
        }
    } else if (tmpmax && strlen(tmpmax) > 0) {
        strcpy(tbuf, tmpmax);
        return tbuf;
    } else if (tmpmin && strlen(tmpmin) > 0) {
        strcpy(tbuf, tmpmin);
        return tbuf;
    } else {
        return NULL;
    }
    return NULL;
}

static char *typical_estimate(char *delay_str)
{
    /* Input string (t1,t2,t2) */
    int which = 0;
    size_t i, slen;
    char *s;
    DS_CREATE(dmin, 32);
    DS_CREATE(dtyp, 32);
    DS_CREATE(dmax, 32);

    ds_clear(&dmin);
    ds_clear(&dtyp);
    ds_clear(&dmax);
    slen = strlen(delay_str) - 1;
    for (i = 1; i < slen; i++) {
        if (delay_str[i] == ',') {
            which++;
            continue;
        }
        switch (which) {
        case 0:
            ds_cat_char(&dmin, delay_str[i]);
            break;
        case 1:
            ds_cat_char(&dtyp, delay_str[i]);
            break;
        case 2:
            ds_cat_char(&dmax, delay_str[i]);
            break;
        default:
            break;
        }
    }
    s = get_typ_estimate(ds_get_buf(&dmin), ds_get_buf(&dtyp),
        ds_get_buf(&dmax));
    ds_free(&dmin);
    ds_free(&dtyp);
    ds_free(&dmax);
    return s;
}

static BOOL extract_delay(
    LEXER lx, int val, PLINE *pline_arr, int idx, BOOL tri)
{
    /* NOTE: The delays are specified in a DELAY(t1,t2,t3) function.
       Beware if the format of t1, t2, t3 changes!
       Expect t1, t2, t3:
          -1 or x.y[time_unit] or w[time_unit]
       where the time_unit is ns, ps, etc. and the same for t1, t2, t3;
       x.y represents a decimal number; w is an integer.
       Either numbers can have more that one digit.
    */
    BOOL in_delay = FALSE, ret_val = TRUE;
    int i;
    BOOL prit = PRINT_ALL;
    float typ_max_val = 0.0, typ_val = 0.0;
    char *units;
    DS_CREATE(dly, 64);
    DS_CREATE(dtyp_max_str, 16);

    if (val != '=') {
        ds_free(&dly);
        ds_free(&dtyp_max_str);
        return FALSE;
    }
    val = lexer_scan(lx);
    if (val != '{') {
        ds_free(&dly);
        ds_free(&dtyp_max_str);
        return FALSE;
    }
    val = lexer_scan(lx);
    while (val != '}') {
        if (val == LEX_ID) {
            if (eq(lx->lexer_buf, "delay")) {
                in_delay = TRUE;
                ds_clear(&dly);
            } else {
                if (in_delay) {
                    ds_cat_printf(&dly, "%s", lx->lexer_buf);
                }
            }
        } else {
            if (in_delay) {
                DS_CREATE(delay_string, 64);
                ds_cat_printf(&dly, "%c", val);
                if (val == ')') {
                    char *tmps;
                    in_delay = FALSE;
                    tmps = typical_estimate(ds_get_buf(&dly));
                    if (!tmps) {
                        ret_val = FALSE;
                        break;
                    }
                    if (prit) {
                        printf("%s\n", ds_get_buf(&dly));
                        printf("estimate \"%s\"\n", tmps);
                    }
                    typ_val = strtof(tmps, &units);
                    if (typ_val > typ_max_val) {
                        ds_clear(&delay_string);
                        ds_clear(&dtyp_max_str);
                        ds_cat_str(&dtyp_max_str, tmps);
                        typ_max_val = typ_val;
                        if (ds_get_length(&dtyp_max_str) > 0) {
                            if (tri) {
                                ds_cat_printf(&delay_string,
                                    "(delay=%s)",
                                    ds_get_buf(&dtyp_max_str));
                            } else {
                                ds_cat_printf(&delay_string,
                                    "(rise_delay=%s fall_delay=%s)",
                                    ds_get_buf(&dtyp_max_str),
                                    ds_get_buf(&dtyp_max_str));
                            }
                        } else {
                            printf("WARNING pindly DELAY not found\n");
                            if (tri) {
                                ds_cat_printf(&delay_string,
                                    "(delay=10ns)");
                            } else {
                                ds_cat_printf(&delay_string,
                                    "(rise_delay=10ns fall_delay=10ns)");
                            }
                        }
                        for (i = 0; i < idx; i++) {
                            (void) set_delays(
                                ds_get_buf(&delay_string),
                                pline_arr[i]);
                        }
                    }
                }
                ds_free(&delay_string);
            } // end if in_delay
        }
        val = lexer_scan(lx);
    } // end while != '}'
    ds_free(&dly);
    ds_free(&dtyp_max_str);
    return ret_val;
}

static BOOL new_gen_output_models(LEXER lx)
{
    int val, arrlen = 0, idx = 0, i;
    BOOL in_pindly = FALSE, in_tristate = FALSE;
    BOOL prit = PRINT_ALL;
    DS_CREATE(enable_name, 64);
    DS_CREATE(last_enable, 64);
    PLINE pline = NULL;
    PLINE *pline_arr = NULL;

    arrlen = num_pindly_entries(pindly_tab);
    if (arrlen <= 0) {
        ds_free(&enable_name);
        ds_free(&last_enable);
        return FALSE;
    }
    pline_arr = TMALLOC(PLINE, arrlen);
    ds_clear(&last_enable);
    val = lexer_scan(lx);
    while (val != 0) { // Outer while loop
        if (val == LEX_ID) {
            if (eq(lx->lexer_buf, "pindly")) {
                in_pindly = TRUE;
                in_tristate = FALSE;
                val = lexer_scan(lx);
                if (val != ':') {
                    goto err_return;
                }
            } else if (eq(lx->lexer_buf, "tristate")) {
                in_pindly = FALSE;
                in_tristate = TRUE;
                val = lexer_scan(lx);
                if (val != ':') {
                    goto err_return;
                }
            } else if (eq(lx->lexer_buf, "setup_hold")
                || eq(lx->lexer_buf, "width")
                || eq(lx->lexer_buf, "freq")
                || eq(lx->lexer_buf, "general")) {
                in_pindly = FALSE;
                in_tristate = FALSE;
            }
        }
        if (in_pindly && val == LEX_ID) { // start in_pindly and LEX_ID
            while (val == LEX_ID) {
                if (prit) { printf("pindly out \"%s\"\n", lx->lexer_buf); }
                pline = find_pindly_out_name(pindly_tab, lx->lexer_buf);
                if (pline) {
                    pline_arr[idx++] = pline;
                } else {
                    goto err_return;
                }
                val = lexer_scan(lx);
            }
            if (!extract_delay(lx, val, pline_arr, idx, FALSE)) goto err_return;
            for (i = 0; i < arrlen; i++) {
                pline_arr[i] = NULL;
            }
            idx = 0; // end in_pindly and LEX_ID
        } else if (in_tristate && val == LEX_ID) {
        // start in_tristate and LEX_ID
            if (eq(lx->lexer_buf, "enable")) {
                val = lexer_scan(lx);
                if (val == LEX_ID && (eq(lx->lexer_buf, "hi")
                        || eq(lx->lexer_buf, "lo"))) {
                    BOOL invert = FALSE;
                    if (eq(lx->lexer_buf, "lo"))
                        invert = TRUE;
                    if (prit) { printf("tristate enable %s ", lx->lexer_buf); }
                    val = lexer_scan(lx);
                    if (val != '=') {
                        goto err_return;
                    }
                    val = lexer_scan(lx);
                    if (val != LEX_ID) {
                        goto err_return;
                    }
                    if (prit) { printf("ena \"%s\"\n", lx->lexer_buf); }
                    ds_clear(&enable_name);
                    if (invert)
                        ds_cat_char(&enable_name, '~');
                    ds_cat_str(&enable_name, lx->lexer_buf);
                } else {
                    goto err_return;
                }
                ds_clear(&last_enable);
                ds_cat_ds(&last_enable, &enable_name);
                val = lexer_scan(lx);
                if (val != LEX_ID) {
                    goto err_return;
                }
            } else if (ds_get_length(&last_enable) > 0) {
                ds_clear(&enable_name);
                ds_cat_ds(&enable_name, &last_enable);
            } else {
                goto err_return;
            }
            while (val == LEX_ID) {
                if (prit) { printf("tristate out \"%s\"\n", lx->lexer_buf); }
                pline = find_pindly_out_name(pindly_tab, lx->lexer_buf);
                if (pline) {
                    pline_arr[idx++] = pline;
                    (void) set_ena_name(ds_get_buf(&enable_name), pline);
                    u_remember_pin(lx->lexer_buf, 3);
                } else {
                    goto err_return;
                }
                val = lexer_scan(lx);
            }
            if (!extract_delay(lx, val, pline_arr, idx, TRUE)) goto err_return;
            for (i = 0; i < arrlen; i++) {
                pline_arr[i] = NULL;
            }
            idx = 0; // end of in_tristate and LEX_ID
        }
        val = lexer_scan(lx);
    } // end of outer while loop
    ds_free(&enable_name);
    ds_free(&last_enable);
    tfree(pline_arr);
    return TRUE;

err_return:
    ds_free(&enable_name);
    ds_free(&last_enable);
    tfree(pline_arr);
    return FALSE;
}

BOOL f_pindly(char *line)
{
    int t, num_ios = 0, num_ena = 0, num_refs = 0, i;
    char *endp;
    LEXER lxr;
    PLINE pline = NULL;

    init_pindly_tab();

    lxr = new_lexer(line);
    current_lexer = lxr;
    t = lexer_scan(lxr); // U*
    if (!expect_token(t, LEX_ID, NULL, TRUE, 50)) goto error_return;

    /* pindly ( int , int, int ) */
    t = lexer_scan(lxr);
    if (!expect_token(t, LEX_ID, "pindly", TRUE, 51)) goto error_return;

    t = lexer_scan(lxr);
    if (!expect_token(t, '(', NULL, TRUE, 52)) goto error_return;

    t = lexer_scan(lxr);
    if (!expect_token(t, LEX_ID, NULL, TRUE, 53)) goto error_return;
    if (lex_all_digits(lxr->lexer_buf)) {
        num_ios = (int) strtol(lxr->lexer_buf, &endp, 10);
    } else {
        fprintf(stderr, "ERROR pindly io count is not an integer\n");
        goto error_return;
    }

    t = lexer_scan(lxr);
    if (!expect_token(t, ',', NULL, TRUE, 54)) goto error_return;

    t = lexer_scan(lxr);
    if (!expect_token(t, LEX_ID, NULL, TRUE, 55)) goto error_return;
    if (lex_all_digits(lxr->lexer_buf)) {
        num_ena = (int) strtol(lxr->lexer_buf, &endp, 10);
    } else {
        fprintf(stderr, "ERROR pindly enable count is not an integer\n");
        goto error_return;
    }

    t = lexer_scan(lxr);
    if (!expect_token(t, ',', NULL, TRUE, 56)) goto error_return;

    t = lexer_scan(lxr);
    if (!expect_token(t, LEX_ID, NULL, TRUE, 57)) goto error_return;
    if (lex_all_digits(lxr->lexer_buf)) {
        num_refs = (int) strtol(lxr->lexer_buf, &endp, 10);
    } else {
        fprintf(stderr, "ERROR pindly refs count is not an integer\n");
        goto error_return;
    }

    t = lexer_scan(lxr);
    if (!expect_token(t, ')', NULL, TRUE, 58)) goto error_return;

    t = lexer_scan(lxr); // pwr
    if (!expect_token(t, LEX_ID, NULL, TRUE, 59)) goto error_return;
    t = lexer_scan(lxr); // gnd
    if (!expect_token(t, LEX_ID, NULL, TRUE, 60)) goto error_return;

    /* num_ios input ids */
    for (i = 0; i < num_ios; i++) {
        t = lexer_scan(lxr);
        if (!expect_token(t, LEX_ID, NULL, TRUE, 61)) goto error_return;
        pline = add_new_pindly_line(pindly_tab);
        (void) set_in_name(lxr->lexer_buf, pline);
        u_remember_pin(lxr->lexer_buf, 1);
    }

    /* num_ena enable nodes which are ignored */
    /* num_refs reference nodes which are ignored */
    for (i = 0; i < num_ena + num_refs; i++) {
        t = lexer_scan(lxr);
        if (!expect_token(t, LEX_ID, NULL, TRUE, 62)) goto error_return;
        if (i < num_ena) {
            u_remember_pin(lxr->lexer_buf, 1);
        }
    }
    /* num_ios output ids */
    pline = NULL;
    for (i = 0; i < num_ios; i++) {
        t = lexer_scan(lxr);
        if (!expect_token(t, LEX_ID, NULL, TRUE, 63)) goto error_return;
        if (i == 0)
            pline = nth_pindly_entry(pindly_tab, i);
        else
            pline = pline->next;
        (void) set_out_name(lxr->lexer_buf, pline);
        u_remember_pin(lxr->lexer_buf, 2);
    }

    if (!new_gen_output_models(lxr)) {
        fprintf(stderr, "ERROR generating models for pindly\n");
        fprintf(stderr, "ERROR in \"%s\"\n", line);
        goto error_return;;
    }
    gen_pindly_buffers();
    delete_lexer(lxr);
    cleanup_pindly_tab();
    current_lexer = NULL;
    return TRUE;

error_return:
    delete_lexer(lxr);
    cleanup_pindly_tab();
    current_lexer = NULL;
    return FALSE;
}

