/* SDB - token scanning routines */

#include <stdio.h>
#include <ctype.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)
    	if (iprompt != NULL)
    	    printf("%s",iprompt);
    	else if (cprompt != NULL)
            printf("%s",cprompt);

    if (dbv_ifp == NULL)
    	if ((ch = getc(stdin)) == '\n')
    	    atbol = TRUE;
    	else
    	    atbol = FALSE;
    else {
    	if ((ch = getc(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);
}
