#include "defs"
#include "string_defs"
#include "tokdefs"

# define BLANK	' '
# define MYQUOTE (2)
# define SEOF 0

/* card types */

# define STEOF 1
# define STINITIAL 2
# define STCONTINUE 3

/* lex states */

#define NEWSTMT	1
#define FIRSTTOKEN	2
#define OTHERTOKEN	3
#define RETEOS	4


LOCAL int stkey;
ftnint yystno;
flag intonly;
LOCAL long int stno;
LOCAL long int nxtstno;
LOCAL int parlev;
LOCAL int expcom;
LOCAL int expeql;
LOCAL char *nextch;
LOCAL char *lastch;
LOCAL char *nextcd 	= NULL;
LOCAL char *endcd;
LOCAL int prevlin;
LOCAL int thislin;
LOCAL int code;
LOCAL int lexstate	= NEWSTMT;
LOCAL char s[1390];
LOCAL char *send	= s+20*66;
LOCAL int nincl	= 0;

struct inclfile
	{
	struct inclfile *inclnext;
	FILEP inclfp;
	char *inclname;
	int incllno;
	char *incllinp;
	int incllen;
	int inclcode;
	ftnint inclstno;
	} ;

LOCAL struct inclfile *inclp	=  NULL;
LOCAL struct keylist { char *keyname; int keyval; } ;
LOCAL struct punctlist { char punchar; int punval; };
LOCAL struct fmtlist { char fmtchar; int fmtval; };
LOCAL struct dotlist { char *dotname; int dotval; };
LOCAL struct keylist *keystart[26], *keyend[26];




inilex(name)
char *name;
{
nincl = 0;
inclp = NULL;
doinclude(name);
lexstate = NEWSTMT;
return(NO);
}



/* throw away the rest of the current line */
flline()
{
lexstate = RETEOS;
}



char *lexline(n)
ftnint *n;
{
*n = (lastch - nextch) + 1;
return(nextch);
}





doinclude(name)
char *name;
{
FILEP fp;
struct inclfile *t;

if(inclp)
	{
	inclp->incllno = thislin;
	inclp->inclcode = code;
	inclp->inclstno = nxtstno;
	if(nextcd)
		inclp->incllinp = copyn(inclp->incllen = endcd-nextcd , nextcd);
	else
		inclp->incllinp = 0;
	}
nextcd = NULL;

if(++nincl >= MAXINCLUDES)
	error("includes nested too deep",0,0,FATAL);
if(name[0] == '\0')
	fp = stdin;
else
	fp = fopen(name, "r");
if( fp )
	{
	t = inclp;
	inclp = ALLOC(inclfile);
	inclp->inclnext = t;
	prevlin = thislin = 0;
	infname = inclp->inclname = name;
	infile = inclp->inclfp = fp;
	}
else
	{
	fprintf(diagfile, "Cannot open file %s", name);
	done(1);
	}
}




LOCAL popinclude()
{
struct inclfile *t;
register char *p;
register int k;

if(infile != stdin)
	clf(&infile);
free(infname);

--nincl;
t = inclp->inclnext;
free(inclp);
inclp = t;
if(inclp == NULL)
	return(NO);

infile = inclp->inclfp;
infname = inclp->inclname;
prevlin = thislin = inclp->incllno;
code = inclp->inclcode;
stno = nxtstno = inclp->inclstno;
if(inclp->incllinp)
	{
	endcd = nextcd = s;
	k = inclp->incllen;
	p = inclp->incllinp;
	while(--k >= 0)
		*endcd++ = *p++;
	free(inclp->incllinp);
	}
else
	nextcd = NULL;
return(YES);
}




yylex()
{
static int  tokno;

	switch(lexstate)
	{
case NEWSTMT :	/* need a new statement */
	if(getcds() == STEOF)
		return(SEOF);
	crunch();
	tokno = 0;
	lexstate = FIRSTTOKEN;
	yystno = stno;
	stno = nxtstno;
	toklen = 0;
	return(SLABEL);

first:
case FIRSTTOKEN :	/* first step on a statement */
	analyz();
	lexstate = OTHERTOKEN;
	tokno = 1;
	return(stkey);

case OTHERTOKEN :	/* return next token */
	if(nextch > lastch)
		goto reteos;
	++tokno;
	if((stkey==SLOGIF || stkey==SELSEIF) && parlev==0 && tokno>3) goto first;
	if(stkey==SASSIGN && tokno==3 && nextch<lastch &&
		nextch[0]=='t' && nextch[1]=='o')
			{
			nextch+=2;
			return(STO);
			}
	return(gettok());

reteos:
case RETEOS:
	lexstate = NEWSTMT;
	return(SEOS);
	}
error("impossible lexstate %d", lexstate,0,FATAL1);
/* NOTREACHED */
}

LOCAL getcds()
{
register char *p, *q;

top:
	if(nextcd == NULL)
		{
		code = getcd( nextcd = s );
		stno = nxtstno;
		prevlin = thislin;
		}
	if(code == STEOF)
		if( popinclude() )
			goto top;
		else
			return(STEOF);

	if(code == STCONTINUE)
		{
		lineno = thislin;
		error("illegal continuation card ignored",0,0,ERR);
		nextcd = NULL;
		goto top;
		}

	if(nextcd > s)
		{
		q = nextcd;
		p = s;
		while(q < endcd)
			*p++ = *q++;
		endcd = p;
		}
	for(nextcd = endcd ;
		nextcd+66<=send && (code = getcd(nextcd))==STCONTINUE ;
		nextcd = endcd )
			;
	nextch = s;
	lastch = nextcd - 1;
	if(nextcd >= send)
		nextcd = NULL;
	lineno = prevlin;
	prevlin = thislin;
	return(STINITIAL);
}

LOCAL getcd(b)
register char *b;
{
register int c;
register char *p, *bend;
int speclin;
static char a[6];
static char *aend	= a+6;

top:
	endcd = b;
	bend = b+66;
	speclin = NO;

	if( (c = getc(infile)) == '&')
		{
		a[0] = BLANK;
		a[5] = 'x';
		speclin = YES;
		bend = send;
		}
	else if(c=='c' || c=='C' || c=='*')
		{
		while( (c = getc(infile)) != '\n')
			if(c == EOF)
				return(STEOF);
		++thislin;
		goto top;
		}

	else if(c != EOF)
		{
		/* a tab in columns 1-6 skips to column 7 */
		ungetc(c, infile);
		for(p=a; p<aend && (c=getc(infile)) != '\n' && c!=EOF; )
			if(c == '\t')
				{
				while(p < aend)
					*p++ = BLANK;
				speclin = YES;
				bend = send;
				}
			else
				*p++ = c;
		}
	if(c == EOF)
		return(STEOF);
	if(c == '\n')
		{
		while(p < aend)
			*p++ = BLANK;
		if( ! speclin )
			while(endcd < bend)
				*endcd++ = BLANK;
		}
	else	{	/* read body of line */
		while( endcd<bend && (c=getc(infile)) != '\n' && c!=EOF )
			*endcd++ = c;
		if(c == EOF)
			return(STEOF);
		if(c != '\n')
			{
			while( (c=getc(infile)) != '\n')
				if(c == EOF)
					return(STEOF);
			}

		if( ! speclin )
			while(endcd < bend)
				*endcd++ = BLANK;
		}
	++thislin;
	if( !isspace(a[5]) && a[5]!='0')
		return(STCONTINUE);
	for(p=a; p<aend; ++p)
		if( !isspace(*p) ) goto initline;
	for(p = b ; p<endcd ; ++p)
		if( !isspace(*p) ) goto initline;
	goto top;

initline:
	nxtstno = 0;
	for(p = a ; p<a+5 ; ++p)
		if( !isspace(*p) )
			if(isdigit(*p))
				nxtstno = 10*nxtstno + (*p - '0');
			else	{
				lineno = thislin;
				error("nondigit in statement number field",0,0,ERR);
				nxtstno = 0;
				break;
				}
	return(STINITIAL);
}

LOCAL crunch()
{
register char *i, *j, *j0, *j1, *prvstr;
int ten, nh, quote;

/* i is the next input character to be looked at
j is the next output character */
parlev = 0;
expcom = 0;	/* exposed ','s */
expeql = 0;	/* exposed equal signs */
j = s;
prvstr = s;
for(i=s ; i<=lastch ; ++i)
	{
	if(isspace(*i) )
		continue;
	if(*i=='\'' ||  *i=='"')
		{
		quote = *i;
		*j = MYQUOTE; /* special marker */
		for(;;)
			{
			if(++i > lastch)
				{
				error("unbalanced quotes; closing quote supplied",0,0,ERR);
				break;
				}
			if(*i == quote)
				if(i<lastch && i[1]==quote) ++i;
				else break;
			else if(*i=='\\' && i<lastch)
				switch(*++i)
					{
					case 't':
						*i = '\t'; break;
					case 'b':
						*i = '\b'; break;
					case 'n':
						*i = '\n'; break;
					case 'f':
						*i = '\f'; break;
					case '0':
						*i = '\0'; break;
					default:
						break;
					}
			*++j = *i;
			}
		j[1] = MYQUOTE;
		j += 2;
		prvstr = j;
		}
	else if( (*i=='h' || *i=='H')  && j>prvstr)	/* test for Hollerith strings */
		{
		if( ! isdigit(j[-1])) goto copychar;
		nh = j[-1] - '0';
		ten = 10;
		j1 = prvstr - 1;
		if (j1<j-5) j1=j-5;
		for(j0=j-2 ; j0>j1; -- j0)
			{
			if( ! isdigit(*j0 ) ) break;
			nh += ten * (*j0-'0');
			ten*=10;
			}
		if(j0 <= j1) goto copychar;
/* a hollerith must be preceded by a punctuation mark.
   '*' is possible only as repetition factor in a data statement
   not, in particular, in character*2h
*/

		if( !(*j0=='*'&&s[0]=='d') && *j0!='/' && *j0!='(' &&
			*j0!=',' && *j0!='=' && *j0!='.')
				goto copychar;
		if(i+nh > lastch)
			{
			error("%dH too big", nh,0,ERR1);
			nh = lastch - i;
			}
		j0[1] = MYQUOTE; /* special marker */
		j = j0 + 1;
		while(nh-- > 0)
			{
			if(*++i == '\\')
				switch(*++i)
					{
					case 't':
						*i = '\t'; break;
					case 'b':
						*i = '\b'; break;
					case 'n':
						*i = '\n'; break;
					case 'f':
						*i = '\f'; break;
					case '0':
						*i = '\0'; break;
					default:
						break;
					}
			*++j = *i;
			}
		j[1] = MYQUOTE;
		j+=2;
		prvstr = j;
		}
	else	{
		if(*i == '(') ++parlev;
		else if(*i == ')') --parlev;
		else if(parlev == 0)
			if(*i == '=') expeql = 1;
			else if(*i == ',') expcom = 1;
copychar:		/*not a string or space -- copy, shifting case if necessary */
		if(shiftcase && isupper(*i))
			*j++ = tolower(*i);
		else	*j++ = *i;
		}
	}
lastch = j - 1;
nextch = s;
}

LOCAL analyz()
{
register char *i;

	if(parlev != 0)
		{
		error("unbalanced parentheses, statement skipped",0,0,ERR);
		stkey = SUNKNOWN;
		return;
		}
	if(nextch+2<=lastch && nextch[0]=='i' && nextch[1]=='f' && nextch[2]=='(')
		{
/* assignment or if statement -- look at character after balancing paren */
		parlev = 1;
		for(i=nextch+3 ; i<=lastch; ++i)
			if(*i == (MYQUOTE))
				{
				while(*++i != MYQUOTE)
					;
				}
			else if(*i == '(')
				++parlev;
			else if(*i == ')')
				{
				if(--parlev == 0)
					break;
				}
		if(i >= lastch)
			stkey = SLOGIF;
		else if(i[1] == '=')
			stkey = SLET;
		else if( isdigit(i[1]) )
			stkey = SARITHIF;
		else	stkey = SLOGIF;
		if(stkey != SLET)
			nextch += 2;
		}
	else if(expeql) /* may be an assignment */
		{
		if(expcom && nextch<lastch &&
			nextch[0]=='d' && nextch[1]=='o')
				{
				stkey = SDO;
				nextch += 2;
				}
		else	stkey = SLET;
		}
/* otherwise search for keyword */
	else	{
		stkey = getkwd();
		if(stkey==SGOTO && lastch>=nextch)
			if(nextch[0]=='(')
				stkey = SCOMPGOTO;
			else if(isalpha(nextch[0]))
				stkey = SASGOTO;
		}
	parlev = 0;
}



LOCAL getkwd()
{
register char *i, *j;
register struct keylist *pk, *pend;
int k;

if(! isalpha(nextch[0]) )
	return(SUNKNOWN);
k = nextch[0] - 'a';
if(pk = keystart[k])
	for(pend = keyend[k] ; pk<=pend ; ++pk )
		{
		i = pk->keyname;
		j = nextch;
		while(*++i==*++j && *i!='\0')
			;
		if(*i=='\0' && j<=lastch+1)
			{
			nextch = j;
			return(pk->keyval);
			}
		}
return(SUNKNOWN);
}



initkey()
{
extern struct keylist keys[];
register struct keylist *p;
register int i,j;

for(i = 0 ; i<26 ; ++i)
	keystart[i] = NULL;

for(p = keys ; p->keyname ; ++p)
	{
	j = p->keyname[0] - 'a';
	if(keystart[j] == NULL)
		keystart[j] = p;
	keyend[j] = p;
	}
}

LOCAL gettok()
{
int havdot, havexp, havdbl;
int radix;
extern struct punctlist puncts[];
struct punctlist *pp;
extern struct fmtlist fmts[];
extern struct dotlist dots[];
struct dotlist *pd;

char *i, *j, *n1, *p;

	if(*nextch == (MYQUOTE))
		{
		++nextch;
		p = token;
		while(*nextch != MYQUOTE)
			*p++ = *nextch++;
		++nextch;
		toklen = p - token;
		*p = '\0';
		return (SHOLLERITH);
		}
/*
	if(stkey == SFORMAT)
		{
		for(pf = fmts; pf->fmtchar; ++pf)
			{
			if(*nextch == pf->fmtchar)
				{
				++nextch;
				if(pf->fmtval == SLPAR)
					++parlev;
				else if(pf->fmtval == SRPAR)
					--parlev;
				return(pf->fmtval);
				}
			}
		if( isdigit(*nextch) )
			{
			p = token;
			*p++ = *nextch++;
			while(nextch<=lastch && isdigit(*nextch) )
				*p++ = *nextch++;
			toklen = p - token;
			*p = '\0';
			if(nextch<=lastch && *nextch=='p')
				{
				++nextch;
				return(SSCALE);
				}
			else	return(SICON);
			}
		if( isalpha(*nextch) )
			{
			p = token;
			*p++ = *nextch++;
			while(nextch<=lastch &&
				(*nextch=='.' || isdigit(*nextch) || isalpha(*nextch) ))
					*p++ = *nextch++;
			toklen = p - token;
			*p = '\0';
			return(SFIELD);
			}
		goto badchar;
		}
/* Not a format statement */

if(needkwd)
	{
	needkwd = 0;
	return( getkwd() );
	}

	for(pp=puncts; pp->punchar; ++pp)
		if(*nextch == pp->punchar)
			{
			if( (*nextch=='*' || *nextch=='/') &&
				nextch<lastch && nextch[1]==nextch[0])
					{
					if(*nextch == '*')
						yylval = SPOWER;
					else	yylval = SCONCAT;
					nextch+=2;
					}
			else	{yylval=pp->punval;
					if(yylval==SLPAR)
						++parlev;
					else if(yylval==SRPAR)
						--parlev;
					++nextch;
				}
			return(yylval);
			}
	if(*nextch == '.')
		if(nextch >= lastch) goto badchar;
		else if(isdigit(nextch[1])) goto numconst;
		else	{
			for(pd=dots ; (j=pd->dotname) ; ++pd)
				{
				for(i=nextch+1 ; i<=lastch ; ++i)
					if(*i != *j) break;
					else if(*i != '.') ++j;
					else	{
						nextch = i+1;
						return(pd->dotval);
						}
				}
			goto badchar;
			}
	if( isalpha(*nextch) )
		{
		p = token;
		*p++ = *nextch++;
		while(nextch<=lastch)
			if( isalpha(*nextch) || isdigit(*nextch) )
				*p++ = *nextch++;
			else break;
		toklen = p - token;
		*p = '\0';
		if(inioctl && nextch<=lastch && *nextch=='=')
			{
			++nextch;
			return(SNAMEEQ);
			}
		if(toklen>=8 && eqn(8, token, "function") &&
			nextch<lastch && *nextch=='(')
				{
				nextch -= (toklen - 8);
				return(SFUNCTION);
				}
		if(toklen > VL)
			{
			error("name %s too long, truncated to %d", token, VL,ERR2);
			toklen = VL;
			token[6] = '\0';
			}
		if(toklen==1 && *nextch==MYQUOTE)
			{
			switch(token[0])
				{
				case 'z':  case 'Z':
				case 'x':  case 'X':
					radix = 16; break;
				case 'o':  case 'O':
					radix = 8; break;
				case 'b':  case 'B':
					radix = 2; break;
				default:
					error("bad bit identifier",0,0,ERR);
					return(SNAME);
				}
			++nextch;
			for(p = token ; *nextch!=MYQUOTE ; )
				if( hextoi(*p++ = *nextch++) >= radix)
					{
					error("invalid binary character",0,0,ERR);
					break;
					}
			++nextch;
			toklen = p - token;
			return( radix==16 ? SHEXCON : (radix==8 ? SOCTCON : SBITCON) );
			}
		return(SNAME);
		}
	if( ! isdigit(*nextch) ) goto badchar;
numconst:
	havdot = NO;
	havexp = NO;
	havdbl = NO;
	for(n1 = nextch ; nextch<=lastch ; ++nextch)
		{
		if(*nextch == '.')
			if(havdot) break;
			else if(nextch+2<=lastch && isalpha(nextch[1])
				&& isalpha(nextch[2]))
					break;
			else	havdot = YES;
		else if(!intonly && (*nextch=='d' || *nextch=='e'))
			{
			p = nextch;
			havexp = YES;
			if(*nextch == 'd')
				havdbl = YES;
			if(nextch<lastch)
				if(nextch[1]=='+' || nextch[1]=='-')
					++nextch;
			if( ! isdigit(*++nextch) )
				{
				nextch = p;
				havdbl = havexp = NO;
				break;
				}
			for(++nextch ;
				nextch<=lastch && isdigit(*nextch);
				++nextch);
			break;
			}
		else if( ! isdigit(*nextch) )
			break;
		}
	p = token;
	i = n1;
	while(i < nextch)
		*p++ = *i++;
	toklen = p - token;
	*p = '\0';
	if(havdbl) return(SDCON);
	if(havdot || havexp) return(SRCON);
	return(SICON);
badchar:
	s[0] = *nextch++;
	return(SUNKNOWN);
}

/* KEYWORD AND SPECIAL CHARACTER TABLES
*/

struct punctlist puncts[ ] =
	{
	'(', SLPAR,
	')', SRPAR,
	'=', SEQUALS,
	',', SCOMMA,
	'+', SPLUS,
	'-', SMINUS,
	'*', SSTAR,
	'/', SSLASH,
	'$', SCURRENCY,
	':', SCOLON,
	0, 0 } ;

/*
LOCAL struct fmtlist  fmts[ ] =
	{
	'(', SLPAR,
	')', SRPAR,
	'/', SSLASH,
	',', SCOMMA,
	'-', SMINUS,
	':', SCOLON,
	0, 0 } ;
*/

LOCAL struct dotlist  dots[ ] =
	{
	"and.", SAND, 
	"or.", SOR, 
	"not.", SNOT, 
	"true.", STRUE, 
	"false.", SFALSE, 
	"eq.", SEQ, 
	"ne.", SNE, 
	"lt.", SLT, 
	"le.", SLE, 
	"gt.", SGT, 
	"ge.", SGE, 
	"neqv.", SNEQV, 
	"eqv.", SEQV, 
	0, 0 } ;

LOCAL struct keylist  keys[ ] =
	{
	"assign",  SASSIGN,
	"automatic",  SAUTOMATIC,
	"backspace",  SBACKSPACE,
	"blockdata",  SBLOCK,
	"call",  SCALL,
	"character",  SCHARACTER,
	"close",  SCLOSE,
	"common",  SCOMMON,
	"complex",  SCOMPLEX,
	"continue",  SCONTINUE,
	"data",  SDATA,
	"dimension",  SDIMENSION,
	"doubleprecision",  SDOUBLE,
	"doublecomplex", SDCOMPLEX,
	"elseif",  SELSEIF,
	"else",  SELSE,
	"endfile",  SENDFILE,
	"endif",  SENDIF,
	"end",  SEND,
	"entry",  SENTRY,
	"equivalence",  SEQUIV,
	"external",  SEXTERNAL,
	"format",  SFORMAT,
	"function",  SFUNCTION,
	"goto",  SGOTO,
	"implicit",  SIMPLICIT,
	"include",  SINCLUDE,
	"inquire",  SINQUIRE,
	"intrinsic",  SINTRINSIC,
	"integer",  SINTEGER,
	"logical",  SLOGICAL,
	"open",  SOPEN,
	"parameter",  SPARAM,
	"pause",  SPAUSE,
	"print",  SPRINT,
	"program",  SPROGRAM,
	"punch",  SPUNCH,
	"read",  SREAD,
	"real",  SREAL,
	"return",  SRETURN,
	"rewind",  SREWIND,
	"save",  SSAVE,
	"static",  SSTATIC,
	"stop",  SSTOP,
	"subroutine",  SSUBROUTINE,
	"then",  STHEN,
	"undefined", SUNDEFINED,
	"write",  SWRITE,
	"decode",  SDECODE,
	"encode",  SENCODE,
	0, 0 };
