/************************************************************************/
/*                                                                      */
/* Copyright (c) 1988 -                                                 */
/*     The Rector and Visitors of the University of Virginia            */
/*                                                                      */
/* This code may not be distributed further without permission          */
/*      from the University of Virginia.                                */
/* This code is distributed WITHOUT ANY WARRANTY.  No claims are        */
/*      made as to whether it serves any particular purpose or even     */
/*      works at all.                                                   */
/*                                                                      */
/************************************************************************/

/*
 * This file contains the code for lexical analysis.  All functions
 * in this file follow this convention:  Upon return, the current
 * input character "c" contains the character immediately following
 * the last thing recognized.
 */

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "types.h"
#include "scan.h"
#include "nextline.h"
#include "ident.h"
#include "error.h"

#define NOINPUT -2

static int c = NOINPUT;  /* current input character */

char word_in[MAXLINE];
char word_lc_in[MAXLINE];

/*

nextch() -- see macro in nextline.h

*/

/*
 * sy_get() is the lexical analyzer.  It reads input text characters
 * until it recognizes a symbol and returns the integral value
 * of the symbol.  All input comes from nextch(), a macro defined
 * in nextline.h
 */

int
sy_get()
{
	register int ret = 0;
	static int flip = -1;

	src_prevsy = src_cursy;
	src_cursy += (flip = (-flip));

	while (ret == 0) {
		src_cursy->s_column = column();
	
		switch(c) {
	
		case NOINPUT:		/* First time thru; prime the pump */
			c = nextline();
			break;
	
		case EOF:
			ret = SY_ENDFILE;
			break;
	
		case '\n':
		case ' ' :
		case '\t':
		case '\f':
			while (isspace(c = nextch()))
				;
			break;
	
		case ':':
			if ((c = nextch()) == '=') {
				c = nextch();
				ret = SY_ASSIGN;
			}
			else
				ret = SY_COLON;
			break;
	
		case '>':
			if ((c = nextch()) == '=') {
				c = nextch();
				ret = SY_GE;
			}
			else
				ret = SY_GT;
			break;
	
		case '<':
			if ((c = nextch()) == '=') {
				c = nextch();
				ret = SY_LE;
			}
			else if (c == '>') {
				c = nextch();
				ret = SY_NE;
			}
			else
				ret = SY_LT;
			break;
		
		case '.':
			if (isdigit(lookahead())) {  /* illegal real constant  */
				ret = sy_number();       /* scan the sucker anyway */
				break;
			}

			if ((c = nextch()) == '.') {
				c = nextch();
				ret = SY_DOTDOT;
			}
			else if (c == ')') {
				c = nextch();
				ret = SY_RBRAC;  /* '.)' and ']' equivalent */
			}
			else
				ret = SY_DOT;
			break;
	
		case '{':
			c = nextch();
			sy_comment();
			break;

		case '}':
			strcpy(err_text, "illegal comment nesting");
			err_msg(E_FATAL, E_CURCHAR);
			c = nextch();
			break;
	
		case '(':
			if ((c = nextch()) == '*') {
				c = nextch();
				sy_comment();
				break;
			}
			if (c == '.') {  /* '(.' and '[' equivalent */
				c = nextch();
				ret = SY_LBRAC;
			}
			else
				ret = SY_LPAREN;
			break;
	
		case ')':
			c = nextch();
			ret = SY_RPAREN;
			break;
	
		case '\'':
			c = nextch();
			sy_quotedstr();
			ret = SY_STRING;
			break;
	
		case '+':
			c = nextch();
			ret = SY_PLUS;
			break;
	
		case '-':
			c = nextch();
			ret = SY_MINUS;
			break;
	
		case '[':
			c = nextch();
			ret = SY_LBRAC;
			break;
	
		case ']':
			c = nextch();
			ret = SY_RBRAC;
			break;
		
		case ';':
			c = nextch();
			ret = SY_SEMI;
			break;
	
		case '=':
			c = nextch();
			ret = SY_EQUAL;
			break;
	
		case ',':
			c = nextch();
			ret = SY_COMMA;
			break;
	
		case '*':
			if ((c = nextch()) == ')') {
				strcpy(err_text, "illegal comment nesting");
				err_msg(E_FATAL, E_CURCHAR);
				c = nextch();
				break;
			}
			ret = SY_STAR;
			break;
	
		case '/':
			c = nextch();
			ret = SY_SLASH;
			break;
	
		case '^':
		case '@':
			c = nextch();
			ret = SY_UPARROW;
			break;

		case '0': case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
			ret = sy_number();
			break;
	
		default:
			if (isalpha(c))
				ret = sy_identifier();
			else {
				char badchar[20];
				sprintf(badchar, isprint(c) ? "'%c'" : "(octal %o)", c);
				sprintf (err_text,
					"syntax error:  illegal character %s",
					badchar);
				err_msg(E_FATAL, E_CURCHAR);
				c = nextch();
				break;
			}
			break;
		}
	}
	src_cursy->s_line = src_curline;
	return (ret);
}

/*
 * sy_identifier() recognizes a Pascal identifier.
 */

sy_identifier() {
	register int rc = c;
	register char *p = word_in, *l = word_lc_in;

	do {
		*p++ = rc;
		*l++ = isupper(rc) ? tolower(rc) : rc;
	} while (isalnum(rc = nextch()));
	*p = '\0';
	*l = '\0';
	ident_in = id_get(word_lc_in, SY_IDENT);
	c = rc;
	return(ident_in->id_symbol);
}

/*
 * sy_comment() skips over comments
 */

sy_comment()
{
	register int rc = c;
	src_comment = 1;
	for (;;) {
		switch(rc) {

		case '*':
			if ((rc = nextch()) != ')')
				continue;
			/* fall through */

		case '}':
			c = nextch();
			src_comment = 0;
			return;

		case EOF:
			sprintf(err_text, err_eof, "comment");
			err_msg(E_WARNING, E_CURLINE);
			return;
		}
		rc = nextch();
	}
}

/*
 * sy_quotedstr() recognizes a string in single quotes.
 * newline and EOF not allowed in string.
 */

sy_quotedstr() {
	register int rc = c;
	register char *p = word_in;
	for (;;) {
		switch(rc) {

		case '\'':
			if ((rc = nextch()) == '\'') {
				*p++ = '\'';
				rc = nextch();
			}
			else {
				if (p == word_in) {
					strcpy(err_text, "syntax error:  empty quoted string");
					err_msg(E_FATAL, E_CURCHAR);
				}
				*p = '\0';
				c = rc;
				return;
			}
			break;

		case EOF:
		case '\n':
			if (rc == EOF)
				sprintf(err_text, err_eof, "quoted string");
			else
				strcpy(err_text,
					"syntax error:  end of line inside quoted string");
			err_msg(E_FATAL, E_CURLINE);
			*p = '\0';
			c = rc;
			return;

		default:
			*p++ = rc;
			rc = nextch();
			break;
		}
	}
}

/*
 * sy_number() recognizes an integer or real constant.
 */

sy_number() {
	register char *p = word_in;
	register int rc = c, ret = SY_REAL;
	int errflag = 0;

	while (isdigit(rc)) {
		*p++ = rc;
		rc = nextch();
	}

	if (rc != '.' && rc != 'e' && rc != 'E') {   /* must be integer */
		ret = SY_DIGSEQ;
		goto out;
	}

	/*
	 * if the next character is '.', we may have a real or we may
	 * have the token ".." or token ".)"
	 */
	if (rc == '.') {
		if (lookahead() == '.' || lookahead() == ')') {
			ret = SY_DIGSEQ;
			goto out;
		}
		*p++ = '.';
		while (isdigit(rc = nextch()))
			*p++ = rc;
		if (*(p-1) == '.')   /* no digits after '.' */
			errflag++;
	}

	if (rc != 'e' && rc != 'E') {
		goto out;
	}
	*p++ = 'e';

	if ((rc = nextch()) == '+' || rc == '-') {
		*p++ = rc;
		rc = nextch();
	}
	while (isdigit(rc)) {
		*p++ = rc;
		rc = nextch();
	}

out:
	*p = '\0';
	if (errflag || *word_in == '.' || !isdigit(*(p-1))) {
		strcpy(err_text, "syntax error:  illegal real constant");
		err_msg(E_FATAL, E_CURSYMBOL);
		strcpy(word_in, "1.0");
	}
	else if (isalpha(rc)) {
		strcpy(err_text,
			"syntax error:  number immediately followed by letter");
		err_msg(E_FATAL, E_CURCHAR);
	}
	c = rc;
	return(ret);
}
