/* SDB - token scanning routines */

#include "stdio.h"
#include "sdbio.h"

int dbv_token;                          /* current token */
int dbv_tvalue;                         /* integer token value */
char dbv_tstring[STRINGMAX+1];          /* string token value */
struct ifile *dbv_ifp;                  /* indirect file context */
struct macro *dbv_macros;               /* macro definitions */
int dbv_fold;                           /* case fold alpha comparisons */

static char *iprompt,*cprompt;          /* input prompts */
static char cmdline[LINEMAX+2],*lptr;   /* current line and pointer */
static int atbol;                       /* flag indicating at bol */
static int savech;                      /* lookahead character */
static int savetkn;                     /* lookahead token */
static char *keywords[] = {             /* keyword table */
    "ascending",
    "by",
    "char",
    "compress",
    "create",
    "define",
    "delete",
    "descending",
    "exit",
    "export",
    "extract",
    "from",
    "help",
    "insert",
    "import",
    "into",
    "num",
    "print",
    "select",
    "set",
    "show",
    "sort",
    "update",
    "using",
    "where",
    NULL
};
static int keytokens[] = {              /* token values for each keyword */
    ASCENDING,
    BY,
    CHAR,
    COMPRESS,
    CREATE,
    DEFINE,
    DELETE,
    DESCENDING,
    EXIT,
    EXPORT,
    EXTRACT,
    FROM,
    HELP,
    INSERT,
    IMPORT,
    INTO,
    NUM,
    PRINT,
    SELECT,
    SET,
    SHOW,
    SORT,
    UPDATE,
    USING,
    WHERE,
    NULL
};

/* db_sinit - initialize the scanner */
db_sinit()
{
    /* at beginning of line */
    atbol = TRUE;

    /* make the command line null */
    lptr = NULL;

    /* no lookahead yet */
    savech = EOS;
    savetkn = NULL;

    /* no indirect command files */
    dbv_ifp = NULL;

    /* no macros defined */
    dbv_macros = NULL;

    /* fold alpha comparisons */
    dbv_fold = TRUE;
}

/* db_prompt(ip,cp) - initialize prompt strings */
db_prompt(ip,cp)
  char *ip,*cp;
{
    /* save initial and continuation prompt strings */
    iprompt = ip;
    cprompt = cp;
}

/* db_scan(fmt,args) - initiate line scan command parsing */
db_scan(fmt,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
{
    /* convert the command line and arguments */
    sprintf(cmdline,fmt,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10);

    /* start at the beginning of the command line */
    lptr = cmdline;
    iprompt = NULL;
    dbv_ifp = NULL;

    /* no lookahead yet */
    savech = EOS;
    savetkn = NULL;

    /* fold alpha comparisons */
    dbv_fold = TRUE;
}

/* db_flush - flush the current input line */
int db_flush()
{
    while (savech != '\n')
        if (savech > ' ')
            return (db_ferror(SYNTAX));
        else
            savech = getchx();

    savech = EOS;
    atbol = TRUE;
    return (TRUE);
}

/* db_gline - get a line from the current input */
char *db_gline(buf)
  char *buf;
{
    int ch,i;

    for (i = 0; (ch = getch()) != '\n' && ch != -1; )
        if (i < LINEMAX)
            buf[i++] = ch;
        else {
            printf("*** line too long ***\nRetype> ");
            i = 0;
        }
    buf[i] = EOS;

    return (buf);
}

/* db_ifile - setup an indirect command file */
int db_ifile(fname)
  char *fname;
{
    struct ifile *new_ifp;

    if ((new_ifp = malloc(sizeof(struct ifile))) == NULL)
        return (db_ferror(INSMEM));
    else if ((new_ifp->if_fp = fopen(fname,"r")) == NULL) {
        free(new_ifp);
        return (db_ferror(INDFNF));
    }
    new_ifp->if_mtext = NULL;
    new_ifp->if_savech = savech;
    new_ifp->if_lptr = lptr;
    new_ifp->if_next = dbv_ifp;
    dbv_ifp = new_ifp;

    /* return successfully */
    return (TRUE);
}

/* db_kill - kill indirect command file input */
db_kill()
{
    struct ifile *old_ifp;

    while ((old_ifp = dbv_ifp) != NULL) {
        dbv_ifp = old_ifp->if_next;
        if (old_ifp->if_fp != NULL)
            fclose(old_ifp->if_fp);
        savech = old_ifp->if_savech;
        lptr = old_ifp->if_lptr;
        free(old_ifp);
    }

    while (savech != '\n')
        savech = getchx();

    savech = EOS;
    savetkn = NULL;
    atbol = TRUE;
}

/* db_token - return the current input token */
int db_token()
{
    struct macro *mptr;
    struct ifile *new_ifp;

    /* find a token that's not a macro call */
    while (db_xtoken() == ID) {

        /* check for a macro call */
        for (mptr = dbv_macros; mptr != NULL; mptr = mptr->mc_next)
            if (db_scmp(dbv_tstring,mptr->mc_name) == 0) {
                if ((new_ifp = malloc(sizeof(struct ifile))) == NULL)
                    printf("*** error expanding macro: %s ***\n",dbv_tstring);
                else {
                    new_ifp->if_fp = NULL;
                    new_ifp->if_mtext = mptr->mc_mtext->mt_next;
                    new_ifp->if_lptr = lptr; lptr = mptr->mc_mtext->mt_text;
                    new_ifp->if_savech = savech; savech = EOS;
                    new_ifp->if_next = dbv_ifp;
                    dbv_ifp = new_ifp;
                }
                savetkn = NULL;
                break;
            }

        if (mptr == NULL)
            break;
    }

    return (dbv_token);
}

/* db_xtoken - return the current input token */
int db_xtoken()
{
    int ch;

    /* check for a saved token */
    if ((dbv_token = savetkn) != NULL)
        return (dbv_token);

    /* get the next non-blank character */
    ch = nextch();

    /* check type of character */
    if (isalpha(ch))                    /* identifier or keyword */
        get_id();
    else if (isdigit(ch))               /* number */
        get_number();
    else if (ch == '"')                 /* string */
        get_string();
    else if (get_rel())                 /* relational operator */
        ;
    else                                /* single character token */
        dbv_token = getch();

    /* save the lookahead token */
    savetkn = dbv_token;

    /* return the token */
    return (dbv_token);
}

/* db_ntoken - get next token (after skipping the current one) */
int db_ntoken()
{
    /* get the current token */
    db_token();

    /* make sure another is read on next call */
    savetkn = NULL;

    /* return the current token */
    return (dbv_token);
}

/* db_xntoken - get next token (after skipping the current one) */
int db_xntoken()
{
    /* get the current token */
    db_xtoken();

    /* make sure another is read on next call */
    savetkn = NULL;

    /* return the current token */
    return (dbv_token);
}

/* db_scmp - compare two strings */
int db_scmp(str1,str2)
  char *str1,*str2;
{
    if (dbv_fold)
        return (scmp(str1,str2));
    else
        return (strcmp(str1,str2));
}

/* db_sncmp - compare two strings with a maximum length */
int db_sncmp(str1,str2,len)
  char *str1,*str2; int len;
{
    if (dbv_fold)
        return (sncmp(str1,str2,len));
    else
        return (strncmp(str1,str2,len));
}

/* scmp - compare two strings with alpha case folding */
static int scmp(str1,str2)
  char *str1,*str2;
{
    int ch1,ch2;

    /* compare each character */
    while (*str1 && *str2) {

        /* fold the character from the first string */
        if (isupper(*str1))
            ch1 = tolower(*str1++);
        else
            ch1 = *str1++;

        /* fold the character from the second string */
        if (isupper(*str2))
            ch2 = tolower(*str2++);
        else
            ch2 = *str2++;

        /* compare the characters */
        if (ch1 != ch2)
            if (ch1 < ch2)
                return (-1);
            else
                return (1);
    }

    /* check for strings of different lengths */
    if (*str1 == *str2)
        return (0);
    else if (*str1 == 0)
        return (-1);
    else
        return (1);
}

/* sncmp - compare two strings with alpha case folding and a maximum length */
static int sncmp(str1,str2,len)
  char *str1,*str2; int len;
{
    int ch1,ch2;

    /* compare each character */
    while (*str1 && *str2 && len > 0) {

        /* fold the character from the first string */
        if (isupper(*str1))
            ch1 = tolower(*str1++);
        else
            ch1 = *str1++;

        /* fold the character from the second string */
        if (isupper(*str2))
            ch2 = tolower(*str2++);
        else
            ch2 = *str2++;

        /* compare the characters */
        if (ch1 != ch2)
            if (ch1 < ch2)
                return (-1);
            else
                return (1);

        /* decrement the string length */
        len--;
    }

    /* check for strings of different lengths */
    if (len == 0 || *str1 == *str2)
        return (0);
    else if (*str1 == 0)
        return (-1);
    else
        return (1);
}

/* get_id - get a keyword or a user identifier */
static get_id()
{
    int ch,nchars,i;

    /* input letters and digits */
    ch = nextch();
    nchars = 0;
    while (isalpha(ch) || isdigit(ch)) {
        if (nchars < KEYWORDMAX)
            dbv_tstring[nchars++] = ch;
        getch(); ch = thisch();
    }

    /* terminate the keyword */
    dbv_tstring[nchars] = EOS;

    /* assume its an identifier */
    dbv_token = ID;

    /* check for keywords */
    for (i = 0; keywords[i] != NULL; i++)
        if (db_scmp(dbv_tstring,keywords[i]) == 0)
            dbv_token = keytokens[i];
}

/* get_number - get a number */
static get_number()
{
    int ch,ndigits,nodot;

    /* read digits and at most one decimal point */
    ch = nextch();
    ndigits = 0; nodot = TRUE;
    while (isdigit(ch) || (nodot && ch == '.')) {
        if (ch == '.')
            nodot = FALSE;
        if (ndigits < NUMBERMAX)
            dbv_tstring[ndigits++] = ch;
        getch(); ch = thisch();
    }

    /* terminate the number */
    dbv_tstring[ndigits] = EOS;

    /* get the value of the number */
    sscanf(dbv_tstring,"%d",&dbv_tvalue);

    /* token is a number */
    dbv_token = NUMBER;
}

/* get_string - get a string */
static get_string()
{
    int ch,nchars;

    /* skip the opening quote */
    getch();

    /* read characters until a closing quote is found */
    ch = thisch();
    nchars = 0;
    while (ch && ch != '"') {
        if (nchars < STRINGMAX)
            dbv_tstring[nchars++] = ch;
        getch(); ch = thisch();
    }

    /* terminate the string */
    dbv_tstring[nchars] = EOS;

    /* skip the closing quote */
    getch();

    /* token is a string */
    dbv_token = STRING;
}

/* get_rel - get a relational operator */
static int get_rel()
{
    int ch;

    switch (ch = nextch()) {
    case '=':
            getch();
            dbv_token = EQL;
            return (TRUE);;
    case '<':
            getch(); ch = nextch();
            if (ch == '>') {
                getch();
                dbv_token = NEQ;
            }
            else if (ch == '=') {
                getch();
                dbv_token = LEQ;
            }
            else
                dbv_token = LSS;
            return (TRUE);;
    case '>':
            getch(); ch = nextch();
            if (ch == '=') {
                getch();
                dbv_token = GEQ;
            }
            else
                dbv_token = GTR;
            return (TRUE);;
    default:
            return (FALSE);
    }
}

/* getch - get the next character */
static int getch()
{
    char fname[STRINGMAX+1];
    int ch,i;

    /* return the lookahead character if there is one */
    if (savech != EOS) {
        ch = savech;
        savech = EOS;
        return (ch);
    }

    /* get a character */
    ch = getchx();

    /* skip spaces at the beginning of a command */
    if (atbol && iprompt != NULL)
        while (ch <= ' ')
            ch = getchx();

    /* use continuation prompt next time */
    iprompt = NULL;

    /* check for indirect command file */
    while (ch == '@') {
        for (i = 0; (savech = getchx()) > ' '; )
            if (i < STRINGMAX)
                fname[i++] = savech;
        fname[i] = 0;
        if (db_ifile(fname) != TRUE)
            printf("*** error opening command file: %s ***\n",fname);
        ch = getchx();
    }

    /* return the character */
    return (ch);
}

/* getchx - get the next character */
static int getchx()
{
    struct ifile *old_ifp;
    int ch;

    /* check for input from buffer */
    if (lptr != NULL) {
        while (*lptr == EOS)
            if (dbv_ifp != NULL)
                if (dbv_ifp->if_mtext == NULL) {
                    old_ifp = dbv_ifp;
                    ch = dbv_ifp->if_savech; savech = EOS;
                    lptr = dbv_ifp->if_lptr;
                    dbv_ifp = dbv_ifp->if_next;
                    free(old_ifp);
                    if (ch != EOS)
                        return (ch);
                    if (lptr == NULL)
                        break;
                }
                else {
                    lptr = dbv_ifp->if_mtext->mt_text;
                    dbv_ifp->if_mtext = dbv_ifp->if_mtext->mt_next;
                }
            else
                return (EOS);

        if (lptr != NULL)
            return (*lptr++);
    }

    /* print prompt if necessary */
    if (atbol && dbv_ifp == NULL)  {  /*dns*/
        if (iprompt != NULL)
            printf("%s",iprompt);
        else if (cprompt != NULL)
            printf("%s",cprompt);
#ifdef Lattice
        fflush(stdout); /*dns*/
#endif
        } /*dns*/

    if (dbv_ifp == NULL)
        if ((ch = getcx(stdin)) == '\n')
            atbol = TRUE;
        else
            atbol = FALSE;
    else {
        if ((ch = getcx(dbv_ifp->if_fp)) == -1) {
            old_ifp = dbv_ifp;
            ch = dbv_ifp->if_savech; savech = EOS;
            lptr = dbv_ifp->if_lptr;
            dbv_ifp = dbv_ifp->if_next;
            fclose(old_ifp->if_fp);
            free(old_ifp);
        }
    }

    /* return the character */
    return (ch);
}

/* thisch - get the current character */
static int thisch()
{
    /* get a lookahead character */
    if (savech == EOS)
        savech = getch();

    /* return lookahead character */
    return (savech);
}

/* nextch - get the next non-blank character */
static int nextch()
{
    int ch;

    /* skip blank characters */
    while ((ch = thisch()) <= ' ' && ch != EOS)
        getch();

    /* return the first non-blank */
    return (ch);
}

