/* Copyright (c) 1982 Regents of the University of California */

static char sccsid[] = "@(#)scanner.c 1.8 8/5/83";

/*
 * Debugger scanner.
 */

#include "defs.h"
#include "scanner.h"
#include "main.h"
#include "keywords.h"
#include "tree.h"
#include "symbols.h"
#include "names.h"
#include "y.tab.h"

#ifndef public
typedef int Token;
#endif

public String initfile = ".dbxinit";

typedef enum { WHITE, ALPHA, NUM, OTHER } Charclass;

private Charclass class[256 + 1];
private Charclass *lexclass = class + 1;

#define isdigit(c) (lexclass[c] == NUM)
#define isalnum(c) (lexclass[c] == ALPHA or lexclass[c] == NUM)
#define ishexdigit(c) ( \
    isdigit(c) or (c >= 'a' and c <= 'f') or (c >= 'A' and c <= 'F') \
)

#define MAXLINESIZE 1024

private File in;
private Char linebuf[MAXLINESIZE];
private Char *curchar;

#define MAXINCLDEPTH 10

private struct {
    File savefile;
    Filename savefn;
    int savelineno;
} inclinfo[MAXINCLDEPTH];

private unsigned int curinclindex;

private Token getident();
private Token getnum();
private Token getstring();
private Boolean eofinput();
private Char charcon();
private Char charlookup();

private enterlexclass(class, s)
Charclass class;
String s;
{
    register char *p;

    for (p = s; *p != '\0'; p++) {
	lexclass[*p] = class;
    }
}

public scanner_init()
{
    register Integer i;

    for (i = 0; i < 257; i++) {
	class[i] = OTHER;
    }
    enterlexclass(WHITE, " \t");
    enterlexclass(ALPHA, "abcdefghijklmnopqrstuvwxyz");
    enterlexclass(ALPHA, "ABCDEFGHIJKLMNOPQRSTUVWXYZ_$");
    enterlexclass(NUM, "0123456789");
    in = stdin;
    errfilename = nil;
    errlineno = 0;
    curchar = linebuf;
    linebuf[0] = '\0';
}

/*
 * Read a single token.
 *
 * Input is line buffered.
 *
 * There are two "modes" of operation:  one as in a compiler,
 * and one for reading shell-like syntax.
 */

private Boolean shellmode;

public Token yylex()
{
    register int c;
    register char *p;
    register Token t;
    String line;

    p = curchar;
    if (*p == '\0') {
	do {
	    if (isterm(in)) {
		printf("(%s) ", cmdname);
		fflush(stdout);
	    }
	    line = fgets(linebuf, MAXLINESIZE, in);
	} while (line == nil and not eofinput());
	if (line == nil) {
	    c = EOF;
	} else {
	    p = linebuf;
	    while (lexclass[*p] == WHITE) {
		p++;
	    }
	    shellmode = false;
	}
    } else {
	while (lexclass[*p] == WHITE) {
	    p++;
	}
    }
    curchar = p;
    c = *p;
    if (lexclass[c] == ALPHA) {
	t = getident();
    } else if (lexclass[c] == NUM) {
	if (shellmode) {
	    t = getident();
	} else {
	    t = getnum();
	}
    } else {
	++curchar;
	switch (c) {
	    case '\n':
		t = '\n';
		if (errlineno != 0) {
		    errlineno++;
		}
		break;

	    case '"':
	    case '\'':
		t = getstring();
		break;

	    case '.':
		if (shellmode) {
		    --curchar;
		    t = getident();
		} else if (isdigit(*curchar)) {
		    --curchar;
		    t = getnum();
		} else {
		    t = '.';
		}
		break;

	    case '<':
		if (not shellmode and *curchar == '<') {
		    ++curchar;
		    t = LFORMER;
		} else {
		    t = '<';
		}
		break;

	    case '>':
		if (not shellmode and *curchar == '>') {
		    ++curchar;
		    t = RFORMER;
		} else {
		    t = '>';
		}
		break;

	    case '#':
		if (*curchar == '^') {
		    ++curchar;
		    t = ABSTRACTION;
		} else {
		    t = '#';
		}
		break;

	    case '-':
		if (shellmode) {
		    --curchar;
		    t = getident();
		} else if (*curchar == '>') {
		    ++curchar;
		    t = ARROW;
		} else {
		    t = '-';
		}
		break;

	    case EOF:
		t = 0;
		break;

	    default:
		if (shellmode and index("!&*()[]", c) == nil) {
		    --curchar;
		    t = getident();
		} else {
		    t = c;
		}
		break;
	}
    }
#   ifdef LEXDEBUG
	if (lexdebug) {
	    fprintf(stderr, "yylex returns ");
	    print_token(stderr, t);
	    fprintf(stderr, "\n");
	}
#   endif
    return t;
}

/*
 * Parser error handling.
 */

public yyerror(s)
String s;
{
    register Char *p, *tokenbegin, *tokenend;
    register Integer len;

    if (streq(s, "syntax error")) {
	beginerrmsg();
	tokenend = curchar - 1;
	tokenbegin = tokenend;
	while (lexclass[*tokenbegin] != WHITE and tokenbegin > &linebuf[0]) {
	    --tokenbegin;
	}
	len = tokenend - tokenbegin + 1;
	p = tokenbegin;
	if (p > &linebuf[0]) {
	    while (lexclass[*p] == WHITE and p > &linebuf[0]) {
		--p;
	    }
	}
	if (p == &linebuf[0]) {
	    fprintf(stderr, "unrecognized command \"%.*s\"", len, tokenbegin);
	} else {
	    fprintf(stderr, "syntax error");
	    if (len != 0) {
		fprintf(stderr, " on \"%.*s\"", len, tokenbegin);
	    }
	}
	enderrmsg();
    } else {
	error(s);
    }
}

/*
 * Eat the current line.
 */

public gobble()
{
    curchar = linebuf;
    linebuf[0] = '\0';
}

/*
 * Scan an identifier and check to see if it's a keyword.
 */

private Token getident()
{
    char buf[256];
    register Char *p, *q;
    register Token t;

    p = curchar;
    q = buf;
    if (shellmode) {
	do {
	    *q++ = *p++;
	} while (index(" \t\n!&<>*[]()", *p) == nil);
    } else {
	do {
	    *q++ = *p++;
	} while (isalnum(*p));
    }
    curchar = p;
    *q = '\0';
    yylval.y_name = identname(buf, false);
    if (not shellmode) {
	t = findkeyword(yylval.y_name);
	if (t == nil) {
	    t = NAME;
	}
    } else {
	t = NAME;
    }
    return t;
}

/*
 * Scan a number.
 */

private Token getnum()
{
    char buf[256];
    register Char *p, *q;
    register Token t;
    Integer base;

    p = curchar;
    q = buf;
    if (*p == '0') {
	if (*(p+1) == 'x') {
	    p += 2;
	    base = 16;
	} else {
	    base = 8;
	}
    } else {
	base = 10;
    }
    if (base == 16) {
	do {
	    *q++ = *p++;
	} while (ishexdigit(*p));
    } else {
	do {
	    *q++ = *p++;
	} while (isdigit(*p));
    }
    if (*p == '.') {
	do {
	    *q++ = *p++;
	} while (isdigit(*p));
	if (*p == 'e' or *p == 'E') {
	    p++;
	    if (*p == '+' or *p == '-' or isdigit(*p)) {
		*q++ = 'e';
		do {
		    *q++ = *p++;
		} while (isdigit(*p));
	    }
	}
	*q = '\0';
	yylval.y_real = atof(buf);
	t = REAL;
    } else {
	*q = '\0';
	switch (base) {
	    case 10:
		yylval.y_int = atol(buf);
		break;

	    case 8:
		yylval.y_int = octal(buf);
		break;

	    case 16:
		yylval.y_int = hex(buf);
		break;

	    default:
		badcaseval(base);
	}
	t = INT;
    }
    curchar = p;
    return t;
}

/*
 * Convert a string of octal digits to an integer.
 */

private int octal(s)
String s;
{
    register Char *p;
    register Integer n;

    n = 0;
    for (p = s; *p != '\0'; p++) {
	n = 8*n + (*p - '0');
    }
    return n;
}

/*
 * Convert a string of hexadecimal digits to an integer.
 */

private int hex(s)
String s;
{
    register Char *p;
    register Integer n;

    n = 0;
    for (p = s; *p != '\0'; p++) {
	n *= 16;
	if (*p >= 'a' and *p <= 'f') {
	    n += (*p - 'a' + 10);
	} else if (*p >= 'A' and *p <= 'F') {
	    n += (*p - 'A' + 10);
	} else {
	    n += (*p - '0');
	}
    }
    return n;
}

/*
 * Scan a string.
 */

private Token getstring()
{
    char buf[256];
    register Char *p, *q;
    Boolean endofstring;

    p = curchar;
    q = buf;
    endofstring = false;
    while (not endofstring) {
	if (*p == '\n' or *p == '\0') {
	    error("non-terminated string");
	    endofstring = true;
	} else if (*p == '"' or *p == '\'') {
	    if (*(p+1) != *p) {
		endofstring = true;
	    } else {
		*q++ = *p;
	    }
	} else {
	    curchar = p;
	    *q++ = charcon(p);
	    p = curchar;
	}
	p++;
    }
    curchar = p;
    *q = '\0';
    yylval.y_string = strdup(buf);
    return STRING;
}

/*
 * Process a character constant.
 * Watch out for backslashes.
 */

private Char charcon(p)
char *p;
{
    char c, buf[10], *q;

    if (*p == '\\') {
	++p;
	if (*p != '\\') {
	    q = buf;
	    do {
		*q++ = *p++;
	    } while (*p != '\\' and *p != '\'' and *p != '\n' and *p != '\0');
	    *q = '\0';
	    if (isdigit(buf[0])) {
		c = (Char) octal(buf);
	    } else {
		c = charlookup(buf);
	    }
	    curchar = p - 1;
	} else {
	    c = '\\';
	}
    } else {
	c = *p;
    }
    return c;
}

/*
 * Do a lookup for a ASCII character name.
 */

private String ascii[] = {
    "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL",
    "BS",  "HT",  "NL",  "VT",  "NP",  "CR",  "SO",  "SI",
    "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB",
    "CAN", "EM",  "SUB", "ESC", "FS",  "GS",  "RS",  "US",
    "SP", nil
};

private char charlookup(s)
String s;
{
    register int i;

    for (i = 0; ascii[i] != NULL; i++) {
	if (streq(s, ascii[i])) {
	    return i;
	}
    }
    if (streq(s, "DEL")) {
	return 0177;
    }
    error("unknown ascii name \"%s\"", s);
    return '?';
}

/*
 * Input file management routines.
 */

public setinput(filename)
Filename filename;
{
    File f;

    f = fopen(filename, "r");
    if (f == nil) {
	error("can't open %s", filename);
    } else {
	if (curinclindex >= MAXINCLDEPTH) {
	    error("unreasonable input nesting on \"%s\"", filename);
	}
	inclinfo[curinclindex].savefile = in;
	inclinfo[curinclindex].savefn = errfilename;
	inclinfo[curinclindex].savelineno = errlineno;
	curinclindex++;
	in = f;
	errfilename = filename;
	errlineno = 1;
    }
}

private Boolean eofinput()
{
    register Boolean b;

    if (curinclindex == 0) {
	if (isterm(in)) {
	    putchar('\n');
	    clearerr(in);
	    b = false;
	} else {
	    b = true;
	}
    } else {
	fclose(in);
	--curinclindex;
	in = inclinfo[curinclindex].savefile;
	errfilename = inclinfo[curinclindex].savefn;
	errlineno = inclinfo[curinclindex].savelineno;
	b = false;
    }
    return b;
}

/*
 * Pop the current input.  Return whether successful.
 */

public Boolean popinput()
{
    Boolean b;

    if (curinclindex == 0) {
	b = false;
    } else {
	b = (Boolean) (not eofinput());
    }
    return b;
}

/*
 * Return whether we are currently reading from standard input.
 */

public Boolean isstdin()
{
    return (Boolean) (in == stdin);
}

/*
 * Send the current line to the shell.
 */

public shellline()
{
    register char *p;

    p = curchar;
    while (*p != '\0' and (*p == '\n' or lexclass[*p] == WHITE)) {
	++p;
    }
    shell(p);
    if (*p == '\0' and isterm(in)) {
	putchar('\n');
    }
    erecover();
}

/*
 * Read the rest of the current line in "shell mode".
 */

public beginshellmode()
{
    shellmode = true;
}

/*
 * Print out a token for debugging.
 */

public print_token(f, t)
File f;
Token t;
{
    if (t == '\n') {
	fprintf(f, "char '\\n'");
    } else if (t == EOF) {
	fprintf(f, "EOF");
    } else if (t < 256) {
	fprintf(f, "char '%c'", t);
    } else {
	fprintf(f, "\"%s\"", keywdstring(t));
    }
}
