%{
/*
Copyright(C) 1990, Marcus J. Ranum, All Rights Reserved.
This software may be freely used, modified, and redistributed,
as long as this copyright message is left intact, and this
software is not used to develop any commercial product, or used
in any product that is provided on a pay-for-use basis.
*/



/*
EXPECTED CONFLICTS::conflicts: 1 shift/reduce on the if/else. This is OK.

	grammar for ubermud compiler. there are lots of cases where 'cute'
tricks are played to minimize the number of instructions that get written
into a program - this is unfortunate, but necessary, since every byte that
can be saved out of an object will decrease the size of the universe.
frequently used ops, like EVAL, ASGN, BLTIN, and ELEVAL use this technique
as much as possible. it makes the code a bit more convoluted, but it
should prove worthwhile.

	WARNING: severely icky-poo looking code ahead. The debug statments
are a ROYAL pain in the butt, but you will come to love them if you ever
need to debug this guy. If you make ANY changes, keep the debug statements
up-to-date, or you will be killed in a horrible way.
*/


/*
#define	COMPILEDEBUG
*/

#include	<stdio.h>
#include	<ctype.h>

#include	"ubermud.h"
#include	"externs.h"


/*
scratch buffers in which to compile and assemble input.
this is done in a somewhat roundabout manner, to maintain a static area
for compilation.
*/


static	char	*currfunc;	/* kludge - used to return function name */
static	long	currenum;	/* kludge - used to return function elem # */
static	int	currfuncop;	/* used to return assign op of funcs */
static	int	stringnum;
static	int	stringoff;
static	char	stringbuf[STRBUFSIZ];
static	int	instbuf[INSBUFSIZ];

static	Prog	bprg = { 0, 0, instbuf, stringbuf, 0, INSBUFSIZ, 0 };

/* ya caught me! a GLOBAL! */
Prog	*yaccprog = &bprg;

static	int	infunct = 0;

/*
shortened name for the current instruction address - I suppose some
day someone might want to assemble 2 programs at once, by re-pointing
yaccprog, so we deref this through a pointer.
*/
#define	ASMPC	(yaccprog->p_pc)

/* compiler state exception flag */
static	int	compiler_state = COMPILE_OK;

static	char	*infuncmsg = "operation only permitted within function";
%}


%token	NUM STR IDENT
%token	OUTOFSPACE BLTIN
%token	IF IN FOR FORARG ELSE FUNC RETURN VNULL

%right	ASGN
%left	REF
%left	OR 
%left	AND
%left	GT GTE LT LTE EQ NE
%left	ADD SUB
%left	MUL DIV
%right	UNARY
%left	NOT

%%
program:
	/* nothing */
	| program statement
	{
		(void)assemble(yaccprog,OP_STOP);

		/*
		sizes here are used only for error control 
		when running the assembled static program.
		they get fixed during resetparser(), which
		should get called in the main loop.
		*/
		yaccprog->p_siz = ASMPC;
		yaccprog->s_siz = stringoff;

		return(compiler_state);
	}
	| program funcdef
	{
		/*
		this instruction will only be reached if an
		explicit return; or return(expr) is not reached
		first. it ensures that something (a non-value)
		will be left on the stack for posterity
		*/
#ifdef	COMPILEDEBUG
		printf("OP_RETF@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_RETF);

		/*
		sizes here are used to calculate how much space
		is allocated if this prog gets copied into a
		static function. this is very important, as it
		ensures minimal space wastage. these values must
		get reset in resetparser(), during the main loop.
		*/
		yaccprog->p_siz = ASMPC;
		yaccprog->p_siz = ASMPC;
		yaccprog->s_siz = stringoff;
		infunct = 0;

		if(compiler_state == COMPILE_OK)
			return(COMPILE_FUNC);
		return(compiler_state);
	}
	;



asgn:
	'$' IDENT ASGN expr
	{
		$$ = $4;
#ifdef	COMPILEDEBUG
		printf("OP_ASGN@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_ASGN);
#ifdef	COMPILEDEBUG
		printf("%s@%d\n",&stringbuf[$2],ASMPC);
#endif
		(void)assemble(yaccprog,$2);
	}
	|
	MUL element ASGN expr %prec UNARY
	{
		/* *#44.bar = expr; */
		$$ = $2;
#ifdef	COMPILEDEBUG
		printf("OP_ECASGN@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_ECASGN);
	}
	|
	element ASGN expr
	{
		/* #44.bar = expr; */
#ifdef	COMPILEDEBUG
		printf("OP_EASGN@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_EASGN);
	}
	;



statement:
	expr ';'
	{
#ifdef	COMPILEDEBUG
		printf("OP_POP@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_POP);
	}
	| RETURN ';'
	{
		if(!infunct) {
			yyerror(infuncmsg);
			compiler_state = COMPILE_BAD;
		} else {
#ifdef	COMPILEDEBUG
			printf("OP_RETNV@%d\n",ASMPC);
#endif
			$$ = assemble(yaccprog,OP_RETNV);
		}
	}
	| RETURN expr ';'
	{
		$$ = $2;
		if(!infunct) {
			yyerror(infuncmsg);
			compiler_state = COMPILE_BAD;
		} else {
#ifdef	COMPILEDEBUG
			printf("OP_RETV@%d\n",ASMPC);
#endif
			(void)assemble(yaccprog,OP_RETV);
		}
	}
	| ';'
	| ifheader condition statement stop
	{
		yaccprog->p_mem[$1 + 1] = $3;	/* if part */
		yaccprog->p_mem[$1 + 2] = 0;	/* else part (none) */
		yaccprog->p_mem[$1 + 3] = $4;	/* continuation */
#ifdef	COMPILEDEBUG
printf("IF@%d backpatch, ifpart:%d@%d, else:%d@%d, cont:%d@%d\n",
		$1, yaccprog->p_mem[$1+1],$1+1,yaccprog->p_mem[$1+2],$1+2,
		yaccprog->p_mem[$1+3],$1+3);
#endif
	}
	| ifheader condition statement stop ELSE statement stop
	{
		yaccprog->p_mem[$1 + 1] = $3;	/* if part */
		yaccprog->p_mem[$1 + 2] = $6;	/* else part */
		yaccprog->p_mem[$1 + 3] = $7;	/* continuation */
#ifdef	COMPILEDEBUG
printf("IF@%d backpatch, ifpart:%d@%d, else:%d@%d, cont:%d@%d\n",
		$1, yaccprog->p_mem[$1+1],$1+1,yaccprog->p_mem[$1+2],$1+2,
		yaccprog->p_mem[$1+3],$1+3);
#endif
	}
	| forheader '$' IDENT IN condition statement stop
	{
		yaccprog->p_mem[$1 + 1] = $3;	/* iterator sym name */
		yaccprog->p_mem[$1 + 2] = $6;	/* do part */
		yaccprog->p_mem[$1 + 3] = $7;	/* continuation */
#ifdef	COMPILEDEBUG
printf("FOR@%d backpatch, sym:%s@%d, do:%d@%d, cont:%d@%d\n",
		$1, &stringbuf[$3],$1+1,yaccprog->p_mem[$1+2],$1+2,
		yaccprog->p_mem[$1+3],$1+3);
#endif
	}
	| forargheader '$' IDENT statement stop
	{
		yaccprog->p_mem[$1 + 1] = $3;	/* iterator sym name */
		yaccprog->p_mem[$1 + 2] = $5;	/* continuation */
#ifdef	COMPILEDEBUG
printf("FORARG@%d backpatch, iter:%s@%d, cont:%d@%d\n",
		$1, &stringbuf[$3],$1+1,yaccprog->p_mem[$1+2],$1+2);
#endif
	}
	| '{' statements '}'
	{
		$$ = $2;
	}
	;



condition:
	'(' expr ')'
	{
		$$ = $2;
#ifdef	COMPILEDEBUG
		printf("OP_STOP@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_STOP);
	}
	;



stop:
	/* nothing */
	{
#ifdef	COMPILEDEBUG
		printf("OP_STOP@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_STOP);
		$$ = ASMPC;
	}
	;



statements:
	/* nothing */
	{
		$$ = ASMPC;
	}
	| statements statement
	;



refnum:
	'@'
	{
#ifdef	COMPILEDEBUG
		printf("OP_ROOTPUSH@%d\n",ASMPC);
#endif
		$$ = assemble(yaccprog,OP_ROOTPUSH);
	}
	| '#' NUM
	{
#ifdef	COMPILEDEBUG
		printf("OP_RPUSH@%d\n",ASMPC);
#endif
		$$ = assemble(yaccprog,OP_RPUSH);
#ifdef	COMPILEDEBUG
		printf("%d@%d\n",$2,ASMPC);
#endif
		(void)assemble(yaccprog,$2);
	}
	| '#' IDENT
	{
		if(!strcmp(&stringbuf[$2],"self")) {
#ifdef	COMPILEDEBUG
			printf("OP_SELFPUSH@%d\n",ASMPC);
#endif
			$$ = assemble(yaccprog,OP_SELFPUSH);
		} else
		if(!strcmp(&stringbuf[$2],"caller")) {
#ifdef	COMPILEDEBUG
			printf("OP_CALLPUSH@%d\n",ASMPC);
#endif
			$$ = assemble(yaccprog,OP_CALLPUSH);
		} else
		if(!strcmp(&stringbuf[$2],"actor")) {
#ifdef	COMPILEDEBUG
			printf("OP_ACTPUSH@%d\n",ASMPC);
#endif
			$$ = assemble(yaccprog,OP_ACTPUSH);
		} else {
			yyerror("error. must specify self|actor|caller\n");
			compiler_state = COMPILE_BAD;
		}
	}
	;



element_head:
	refnum
	| '$' IDENT
	{
#ifdef	COMPILEDEBUG
		printf("OP_EVAL@%d\n",ASMPC);
#endif
		$$ = assemble(yaccprog,OP_EVAL);
#ifdef	COMPILEDEBUG
		printf("%s@%d\n",&stringbuf[$2],ASMPC);
#endif
		(void)assemble(yaccprog,$2);
	}
	| '$' NUM
	{
		if(!infunct) {
			yyerror(infuncmsg);
			compiler_state = COMPILE_BAD;
		} else {
#ifdef	COMPILEDEBUG
			printf("OP_PPUSH@%d\n",ASMPC);
#endif
			$$ = assemble(yaccprog,OP_PPUSH);
#ifdef	COMPILEDEBUG
			printf("%d@%d\n",$2,ASMPC);
#endif
			(void)assemble(yaccprog,$2);
		}
	}
	;



element:
	element_head '.' IDENT
	{
#ifdef	COMPILEDEBUG
		printf("OP_ELPUSH@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_ELPUSH);
#ifdef	COMPILEDEBUG
		printf("%s@%d\n",&stringbuf[$3],ASMPC);
#endif
		(void)assemble(yaccprog,$3);
	}
	| element_head '.' '(' expr ')'
	{
#ifdef	COMPILEDEBUG
		printf("OP_STRVAR@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_STRVAR);
	}
	| element '.' IDENT
	{
#ifdef	COMPILEDEBUG
		printf("OP_EEVAL@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_EEVAL);
#ifdef	COMPILEDEBUG
		printf("OP_ELPUSH@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_ELPUSH);
#ifdef	COMPILEDEBUG
		printf("%s@%d\n",&stringbuf[$3],ASMPC);
#endif
		(void)assemble(yaccprog,$3);
	}
	| element '.' '(' push_eval_kludge expr ')'
	{
#ifdef	COMPILEDEBUG
		printf("OP_STRVAR@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_STRVAR);
	}
	;



push_eval_kludge:
	/*
	nothing - what this does is ensures that the element is
	pushed AND evaluated BEFORE the expression is pushed and
	evaluated. this is somewhat gross, but then the whole
	string->variable hack is somewhat gross.
	*/
	{
#ifdef	COMPILEDEBUG
		printf("OP_EEVAL@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_EEVAL);
	}
	;



evaluated_element:
	element
	{
#ifdef	COMPILEDEBUG
		printf("OP_EEVAL@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_EEVAL);
	}
	| '$' IDENT
	{
#ifdef	COMPILEDEBUG
		printf("OP_EVAL@%d\n",ASMPC);
#endif
		$$ = assemble(yaccprog,OP_EVAL);	/* replace w/ contents */
#ifdef	COMPILEDEBUG
		printf("%s@%d\n",&stringbuf[$2],ASMPC);
#endif
		(void)assemble(yaccprog,$2);	/* variable name */
	}
	| '$' NUM
	{
		if(!infunct) {
			yyerror(infuncmsg);
			compiler_state = COMPILE_BAD;
		} else {
#ifdef	COMPILEDEBUG
			printf("OP_PPUSH@%d\n",ASMPC);
#endif
			$$ = assemble(yaccprog,OP_PPUSH);
#ifdef	COMPILEDEBUG
			printf("%d@%d\n",$2,ASMPC);
#endif
			(void)assemble(yaccprog,$2);
		}
	}
	;


expr:
	NUM
	{
#ifdef	COMPILEDEBUG
		printf("OP_NPUSH@%d\n",ASMPC);
#endif
		$$ = assemble(yaccprog,OP_NPUSH);
#ifdef	COMPILEDEBUG
		printf("%d@%d\n",$1,ASMPC);
#endif
		(void)assemble(yaccprog,$1);
	}
	| STR
	{
#ifdef	COMPILEDEBUG
		printf("OP_SPUSH@%d\n",ASMPC);
#endif
		$$ = assemble(yaccprog,OP_SPUSH);
#ifdef	COMPILEDEBUG
		printf("%s@%d\n",&stringbuf[$1],ASMPC);
#endif
		(void)assemble(yaccprog,$1);
	}
	| VNULL
	{
#ifdef	COMPILEDEBUG
		printf("OP_NULPUSH@%d\n",ASMPC);
#endif
		$$ = assemble(yaccprog,OP_NULPUSH);
	}
	| '$' '#'
	{
		if(!infunct) {
			yyerror(infuncmsg);
			compiler_state = COMPILE_BAD;
		} else
#ifdef	COMPILEDEBUG
			printf("OP_CPUSH@%d\n",ASMPC);
#endif
			$$ = assemble(yaccprog,OP_CPUSH);
	}
	| refnum
	| evaluated_element
	| asgn
	| BLTIN beginargs '(' arglist ')'
	{
		$$ = $2;
#ifdef	COMPILEDEBUG
		printf("OP_BLTIN@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_BLTIN);
#ifdef	COMPILEDEBUG
		printf("bltin #%d@%d\n",$1,ASMPC);
#endif
		(void)assemble(yaccprog,$1);
#ifdef	COMPILEDEBUG
		printf("%d args@%d\n",$4,ASMPC);
#endif
		(void)assemble(yaccprog,$4);
	}
	| evaluated_element '(' arglist ')'
	{
#ifdef	COMPILEDEBUG
		printf("OP_CALL@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_CALL);
#ifdef	COMPILEDEBUG
		printf("%d args@%d\n",$3,ASMPC);
#endif
		(void)assemble(yaccprog,$3);
	}
	| '(' expr ')'
	{
		$$ = $2;
	}
	| expr ADD expr
	{
#ifdef	COMPILEDEBUG
		printf("OP_ADD@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_ADD);
	}
	| expr SUB expr
	{
#ifdef	COMPILEDEBUG
		printf("OP_SUB@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_SUB);
	}
	| expr MUL expr
	{
#ifdef	COMPILEDEBUG
		printf("OP_MUL@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_MUL);
	}
	| expr DIV expr
	{
#ifdef	COMPILEDEBUG
		printf("OP_DIV@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_DIV);
	}
	| SUB expr %prec UNARY
	{
		$$ = $2;
#ifdef	COMPILEDEBUG
		printf("OP_NEG@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_NEG);	/* negate top of stack */
	}
	| REF element %prec UNARY
	{
		$$ = $2;
#ifdef	COMPILEDEBUG
		printf("OP_REF@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_REF);
	}
	| expr EQ expr
	{
#ifdef	COMPILEDEBUG
		printf("OP_EQ@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_EQ);
	}
	| expr NE expr
	{
#ifdef	COMPILEDEBUG
		printf("OP_NE@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_NE);
	}
	| expr LT expr
	{
#ifdef	COMPILEDEBUG
		printf("OP_LT@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_LT);
	}
	| expr LTE expr
	{
#ifdef	COMPILEDEBUG
		printf("OP_LTE@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_LTE);
	}
	| expr GT expr
	{
#ifdef	COMPILEDEBUG
		printf("OP_GT@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_GT);
	}
	| expr GTE expr
	{
#ifdef	COMPILEDEBUG
		printf("OP_GTE@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_GTE);
	}
	| expr AND expr
	{
#ifdef	COMPILEDEBUG
		printf("OP_AND@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_AND);
	}
	| expr OR expr
	{
#ifdef	COMPILEDEBUG
		printf("OP_OR@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_OR);
	}
	| NOT expr %prec UNARY
	{
		$$ = $2;
#ifdef	COMPILEDEBUG
		printf("OP_NOT@%d\n",ASMPC);
#endif
		(void)assemble(yaccprog,OP_NOT);	/* invert top of stack */
	}
	;



forheader:
	FOR
	{
#ifdef	COMPILEDEBUG
		printf("OP_FOR (header only)@%d\n",ASMPC);
#endif
		$$ = assemble(yaccprog,OP_FOR);
		(void)assemble(yaccprog,OP_STOP);
		(void)assemble(yaccprog,OP_STOP);
		(void)assemble(yaccprog,OP_STOP);
	}
	;



forargheader:
	FORARG
	{
#ifdef	COMPILEDEBUG
		printf("OP_FORARG (header only)@%d\n",ASMPC);
#endif
		$$ = assemble(yaccprog,OP_FORARG);
		(void)assemble(yaccprog,OP_STOP);
		(void)assemble(yaccprog,OP_STOP);
	}
	;



ifheader:
	IF
	{
#ifdef	COMPILEDEBUG
		printf("OP_IF (header only)@%d\n",ASMPC);
#endif
		$$ = assemble(yaccprog,OP_IF);
		(void)assemble(yaccprog,OP_STOP);
		(void)assemble(yaccprog,OP_STOP);
		(void)assemble(yaccprog,OP_STOP);
	}
	;



beginargs:
	/* nothing */
	{
		$$ = ASMPC;
	}
	;



funcdef:
	funcheader '@' '.' IDENT statement
	{
		/*
		this is gross, but the best way to handle it.
		store a pointer to the name of the currently
		defined function, to allow easy linking. this
		COULD be passed back in the compiled program,
		but then it would have to be stripped out.
		so, do it like this. Sue me.
		*/
		currfunc = &stringbuf[$4];
		currenum = (long)0;
	}
	| funcheader '#' NUM '.' IDENT statement
	{
		currfunc = &stringbuf[$5];
		currenum = (long)$3;
		currfuncop = OP_EASGN;
	}
	| funcheader MUL '#' NUM '.' IDENT statement
	{
		currfunc = &stringbuf[$6];
		currenum = (long)$4;
		currfuncop = OP_ECASGN;
	}
	;



arglist:
	/* nothing */
	{
		$$ = 0;
	}
	| expr
	{
		$$ = 1;
	}
	| arglist ',' expr
	{
		$$ = $1 + 1;
	}
	;



funcheader:
	FUNC 
	{
		infunct = 1;
	}
	;

%%
FILE	*yyin	= { stdin };

int	yylineno = 1;


void
setyyinput(f)
FILE	*f;
{
	yyin = f;
}


putinstringbuf(b)
char	*b;
{
	register char	*op = &stringbuf[stringoff];
	int		ooff = stringoff;

	while(*b && stringoff < sizeof(stringbuf)) {
		if(isprint(*b)) {
			*op++ = *b;
			stringoff++;
			if(stringoff >= sizeof(stringbuf))
				return(-1);
		}
		b++;
	}
	 stringoff++;
	*op = '\0';
	return(ooff);
}


assemble(p,op)
Prog	*p;
int	op;
{
	if(p->p_pc >= p->p_siz) {
		yyerror("program too big\n");
		compiler_state = COMPILE_OOE;
		return(0);
	}
	p->p_mem[p->p_pc] = op;
	p->p_pc++;
	return(p->p_pc - 1);
}



/*
return the element # of the currently defined function
*/
long
funcnum()
{
	return(currenum);
}



/*
return the assignment op type of the currently defined function
*/
funcop()
{
	return(currfuncop);
}




/*
return the name of the currently defined function
*/
char	*
funcname()
{
	return(currfunc);
}



/*
reset the string buffer pointers and so on. wipe the static memory
prior to a parser run.
*/
void
resetparser()
{
	infunct = 0;
	stringnum = 0;
	stringoff = 0;

	/* kludgy - reset the max size of the compiler's program buffer */
	ASMPC = 0;
	yaccprog->p_siz = INSBUFSIZ;
	yaccprog->s_siz = STRBUFSIZ;
	currfunc = 0;
	compiler_state = COMPILE_OK;
}



/*
search the string buffer for an existing copy of string. in case it is
already there, return the offset (cut down on duplication). if not,
return -1
*/
static	int
searchbuf(s)
char	*s;
{
	register	char	*p = stringbuf;
	register	char	*kp;
	int		strt;
	int		srch = 0;

	while(srch < stringoff) {
		kp = s;
		strt = srch;

		/* match as match can */
		while(*p == *kp && *kp != '\0' && srch < stringoff - 1) {
			p++;
			kp++;
			srch++;
		}

		/* is it a match ? */
		if(*p == '\0' && *kp == '\0' && srch < stringoff - 1) {
#ifdef	ASMDEBUG
			printf("string matched at %d\n",strt);
#endif
			return(strt);
		}

		/* it is not a match, skip to next NULL, and continue */
		while(*p != '\0') {
			p++;
			srch++;
		}

		/* and skip the NULL */
		srch++;
		if(srch < stringoff - 1)
			p++;
	}
	return(-1);
}



static	int
lookup(s,bval)
char	*s;
int	*bval;
{
	int	start	= 0;
	int	ret;

	static	struct	kwordz{
		char	*kw;
		int	rval;
		int	bltin;		/* # of builtin if builtin */
	} keyz[] = {
	/* MUST BE IN LEXICAL SORT ORDER !!!!!! */
	"NULL",			VNULL,		-1,
	"atoi",			BLTIN,		0,
	"atoobj",		BLTIN,		1,
	"catfile",		BLTIN,		2,
	"chmod",		BLTIN,		3,
	"chown",		BLTIN,		4,
	"disconnect",		BLTIN,		5,
	"echo",			BLTIN,		6,
	"echoto",		BLTIN,		7,
	"else",			ELSE,		-1,
	"errno",		BLTIN,		8,
	"error",		BLTIN,		9,
	"foreach",		FOR,		-1,
	"foreacharg",		FORARG,		-1,
	"func",			FUNC,		-1,
	"geteuid",		BLTIN,		10,
	"getuid",		BLTIN,		11,
	"if",			IF,		-1,
	"in",			IN,		-1,
	"islist",		BLTIN,		12,
	"isnum",		BLTIN,		13,
	"isobj",		BLTIN,		14,
	"isstr",		BLTIN,		15,
	"listadd",		BLTIN,		16,
	"listappend",		BLTIN,		17,
	"listcount",		BLTIN,		18,
	"listdrop",		BLTIN,		19,
	"listelem",		BLTIN,		20,
	"listmerge",		BLTIN,		21,
	"listnew",		BLTIN,		22,
	"listprepend",		BLTIN,		16,	/* alias add/prepend */
	"listsearch",		BLTIN,		23,
	"listsetelem",		BLTIN,		24,
	"log",			BLTIN,		25,
	"match",		BLTIN,		26,
	"objectdestroy",	BLTIN,		27,
	"objectelements",	BLTIN,		28,
	"objectnew",		BLTIN,		29,
	"objectowner",		BLTIN,		30,
	"random",		BLTIN,		31,
	"regcmp",		BLTIN,		32,
	"regexp",		BLTIN,		33,
	"return",		RETURN,		-1,
	"setruid",		BLTIN,		34,
	"setuid",		BLTIN,		35,
	"shutdown",		BLTIN,		36,
	"str",			BLTIN,		37,
	"strlen",		BLTIN,		38,
	"strtime",		BLTIN,		39,
	"time",			BLTIN,		40,
	0,0,0
	};

	int	end	= (sizeof(keyz)/sizeof(struct kwordz)) - 2;
	int	p	= end/2;

	*bval = -1;
	while(start <= end) {
		ret = strcmp(s,keyz[p].kw);
		if(ret == 0) {
			*bval = keyz[p].bltin;
			return(keyz[p].rval);
		}
		if(ret > 0)
			start = p + 1;
		else
			end = p - 1;

		p = start + ((end - start)/2);
	}
	return(-1);
}



yylex()
{
	char	in[BUFSIZ];
	char	*p = in;
	int	c;

	/* handle whitespace */
	while(isspace(c = fgetc(yyin)))
		if(c == '\n')
			yylineno++;
	
	/* handle EOF */
	if(c == EOF) {
		return(0);
	}

	/* save current char - it is valuable */
	*p++ = c;

	/* handle NUM */
	if(isdigit(c)) {
		int	num;

		num = c - '0';
		while(isdigit(c = fgetc(yyin)))
			num = (num * 10) + (c - '0');

		(void)ungetc(c,yyin);
		if(c == '\n')
			yylineno--;

		yylval = num;
		return(NUM);
	}

	/* handle keywords or idents/builtins */
	if(isalpha(c) || c == '_') {
		int	cnt = 0;
		int	rv;
		int	bltin;

		while((c = fgetc(yyin)) != EOF && (isalnum(c) || c == '_')) {
			if(++cnt + 1 >= MAXIDENTLEN)
				yyerror("identifier too long");
			*p++ = c;
		}

		(void)ungetc(c,yyin);

		*p = '\0';

		if((rv = lookup(in,&bltin)) != -1) {
			if(rv == BLTIN)
				yylval = bltin;
			return(rv);
		}
	
		/* if the string is already in the buffer, return that */
		if(stringoff > 0 && (yylval = searchbuf(in)) != -1) {
			return(IDENT);
		}

		if(stringoff + cnt > sizeof(stringbuf)) {
			yyerror("out of string space!");
			compiler_state = COMPILE_OOS;
			return(OUTOFSPACE);
		}
		bcopy(in,&stringbuf[stringoff],cnt + 2);
		yylval = stringoff;
		stringoff += cnt + 2;
		stringbuf[stringoff - 1] = '\0';
		return(IDENT);
	}

	/* handle quoted strings */
	if(c == '"' || c == '\'') {
		int	cnt = 0;
		int	quot = c;

		/* strip start quote by resetting ptr */
		p = in;

		/* match quoted strings */
		while((c = fgetc(yyin)) != EOF && c != quot) {
			if(!isascii(c))
				continue;

			if(++cnt + 1 >= sizeof(in))
				yyerror("string too long");

			/* we have to guard the line count */
			if(c == '\n')
				yylineno++;

			if(c == '\\') {
				int	b;
				switch(b = fgetc(yyin)) {
					case	EOF:
						yyerror("EOF in quoted string");
						break;

					case	't':
						*p++ = '\t';
						break;

					case	';':
						*p++ = MATCH_CHAR;
						break;

					case	'n':
						*p++ = '\n';
						break;

					default:
						*p++ = b;
				}
			} else {
				*p++ = c;
			}
		}

		if(c == EOF)
			yyerror("EOF in quoted string");

		*p = '\0';
	
		/* if the string is already in the buffer, return that */
		if(stringoff > 0 && (yylval = searchbuf(in)) != -1) {
			return(STR);
		}

		if(stringoff + cnt > sizeof(stringbuf)) {
			yyerror("out of string space!");
			compiler_state = COMPILE_OOS;
			return(OUTOFSPACE);
		}
		bcopy(in,&stringbuf[stringoff],cnt + 2);
		yylval = stringoff;
		stringoff += cnt + 2;
		stringbuf[stringoff - 1] = '\0';
		return(STR);
	}

	switch(c) {
		case	'=':
			{
				int	t;
				t = fgetc(yyin);
				if(t != '=') {
					(void)ungetc(t,yyin);
					return(ASGN);
				}
			}
			return(EQ);

		case	'>':
			{
				int	t;
				t = fgetc(yyin);
				if(t != '=') {
					(void)ungetc(t,yyin);
					return(GT);
				}
			}
			return(GTE);

		case	'<':
			{
				int	t;
				t = fgetc(yyin);
				if(t != '=') {
					(void)ungetc(t,yyin);
					return(LT);
				}
			}
			return(LTE);

		case	'!':
			{
				int	t;
				t = fgetc(yyin);
				if(t != '=') {
					(void)ungetc(t,yyin);
					return(NOT);
				}
			}
			return(NE);

		case	'&':
			{
				int	t;
				t = fgetc(yyin);
				if(t != '&') {
					(void)ungetc(t,yyin);
					return(REF);
				}
			}
			return(AND);

		case	'|':
			{
				int	t;
				t = fgetc(yyin);
				if(t != '|') {
					(void)ungetc(t,yyin);
					return(t);
				}
			}
			return(OR);

		case	'/':
			return(DIV);

		case	'*':
			return(MUL);

		case	'-':
			return(SUB);

		case	'+':
			return(ADD);
	}

	/* punt */
	if(c == '\n')
		yylineno++;
	return(c);
}