/* Copyright (c) 1993 Stephen F. White */

%{
#include "cool.h"
#include "proto.h"
#include "code.h"
#include "execute.h"
#include "servers.h"

static void	yyerror(const char *s);
static void	error(const char  *s, const char *t);
int		yyparse(void);
static int	yylex(void);
static void	do_check_parents(Object *o);
static void	add_parent(Object *o, Objid newparent);
static void	new_global_var(int name, Type_spec type, Var value);
static int	builtin_var(int varno);
static void	start_method(int mname, int blocked);
static void	end_method(void);
static int	code_assign(int varname, int local_opcode,
			    int global_opcode);
static int	find_loopvar(int varname);

static Object	*cur_obj;		/* current object */
static Method	*cur_method;		/* current method */
static int	cur_type;		/* current type specifier */
static int	parse_expecting;	/* OBJECT or METHOD */
static int	lineno;			/* current line number */
static int	loop_depth;		/* current depth of nested loops */
static int	loopvar;		/* variable used for iteration */
static int	inst_ctr;		/* temporary hold for instruction count return */
%}
%union {
    int		 num;
    Objid	 obj;
    Var		 var;
    List	*list;
    Map		*map;
}

%right '='
%left	UPTO MAPSTO
%left	OR
%left	AND
%left	IN
%left   EQ NE
%left   LT LE GT GE
%left	'+' '-'
%left	'*' '/' '%'
%left	NOT NEGATE '@'
%right	'(' ')' '[' ']' '{' '}' '.'

%token <num> NUMBER_CONST STRING_CONST ERROR_CONST TYPE_SPEC ID
%token IF ELSE ELSEIF ENDIF FOR IN ENDFOR AT ENDAT T_BLOCKED
%token T_RETURN CONTINUE BREAK IGNORE TO
%token PARENTS OBJECT ENDOBJECT METHOD ENDMETHOD VERB VAR
%token RMVERB RMMETHOD RMVAR

%token ARGS PLAYER CALLER THIS
%token SETPLAYER

%token STOP
%token MESSAGE MESSAGE_EXPR ADD SUB MUL DIV MOD
%token INDEX SUBSET LSUBSET RSUBSET
%token FIND_METHOD SPEW_METHOD LIST_METHOD DECOMPILE
%token GETLVAR GETGVAR GETGVAREXPR ASGNLVAR ASGNGVAR ASGNGVAREXPR POP
%token ASGNLVARINDEX ASGNGVARINDEX GETSYSVAR
%token OBJPUSH NUMPUSH STRPUSH LISTPUSH MAPPUSH ERRPUSH
%token CLONE DESTROY CHPARENTS TIME CTIME EXPLODE STRSUB PSUB PAD RANDOM
%token SETADD SETREMOVE LISTINSERT LISTAPPEND LISTDELETE LISTASSIGN
%token CRYPT CHECKMEM CACHE_STATS SET_PARSE SORT CONNECT STRCMP
%token F_INDEX RINDEX TOLOWER TOUPPER
%token LOCK UNLOCK TONUM TOOBJ TOSTR TOERR
%token TYPEOF LENGTHOF SERVEROF SERVERNAME SERVERS
%token VERBS VARS METHODS CHILDREN HASPARENT OBJSIZE
%token MATCH MATCH_FULL SHUTDOWN DUMP WRITELOG ECHO DISCONNECT PROGRAM COMPILE
%token T_RAISE PASS ENDAND ENDOR ECHO_FILE SLEEP FORRNG DO DOWHILE
%token WHILE ENDWHILE PUSHPC KILL PS SPLICE ARGSTART ECHON SYNC

%token EXPECTING_OBJECT EXPECTING_METHOD

%token LAST_TOKEN	/* this HAS to be the last token in this file */

%type <num> expr pass_expr
%type <num> builtin_objvar
%type <num> ifstart elseifs elseifstart endif
%type <num> forstart endfor atstart endat whilecond endwhile
%type <num> dostart forrng end
%type <num> count maplist var
%type <obj> object_const opt_pass_to
%type <var> const_expr
%type <list> list_const list_of_constants
%type <map> map_const list_of_constant_pairs
%%
target:
	  EXPECTING_OBJECT OBJECT object_const { cur_obj->id = $3; }
	  object_body ENDOBJECT
	| EXPECTING_METHOD method_body
	;
object_body:
	  parents			{ do_check_parents(cur_obj); }
	  obj_declarations methods
	;
parents:
	  /* NOTHING */			{ cur_obj->parents = list_new(0); }
	| PARENTS parentlist ';'
	;
parentlist:
	  object_const			{ add_parent(cur_obj, $1); }
	| parentlist ',' object_const	{ add_parent(cur_obj, $3); }
	;
method_declarations:
	  /* NOTHING */	
	| method_declarations method_decl
	;
obj_declarations:
	  /* NOTHING */	
	| obj_declarations obj_decl
	;
obj_decl:
	  verb_decl
	| global_var_decl
	;
method_decl:
	  local_var_decl
	| ignore_decl
	;
local_var_decl:
	  VAR { cur_type = VAR; } local_list ';' 
	;
global_var_decl:
	  TYPE_SPEC { cur_type = $1; } global_list ';'
	| ID '=' const_expr ';'
	    { new_global_var($1, $3.type, $3); }
	;
verb_decl:
	  VERB STRING_CONST '=' ID ';' 
	    { verb_add(cur_obj, $2, -1, $4); }
	| VERB STRING_CONST ':' STRING_CONST '=' ID ';' 
	    { verb_add(cur_obj, $2, $4, $6); }
	;
ignore_decl:
	  IGNORE errorlist ';'
	;
errorlist:
	  error_const
	| errorlist ',' error_const
error_const:
	  ERROR_CONST
	    {
		if (NOIGNORE($1)) {
		    error("Cannot catch or ignore:", err_id2name($1));
		} else if (cur_method) {
		    cur_method->ehandler[$1] = EH_IGNORE;
		}
	    }
	;
global_list:  global_item
	| global_list ',' global_item
	;
global_item:
	  ID
	    { new_global_var($1, cur_type, var_init(cur_type)); }
	| ID '=' const_expr
	    { new_global_var($1, cur_type, $3); }
	;
local_list:	local_item
	| local_list ',' local_item
	;
local_item:
	  ID
	    {   int dummy;
		if (cur_method && var_add_local(cur_method, $1, &dummy)) {
		    error("Variable multiply defined:  ",
			sym_get(cur_obj, $1)->str);
		    sym_free(cur_obj, $1);
		}
	    }
	;
methods:
	  /* NOTHING */
	| methods method
	;
method:
	  METHOD ID
	    { start_method($2, 0); }
	  method_body ENDMETHOD
	    { end_method(); }
	| T_BLOCKED METHOD ID
	    { start_method($3, 1); }
	  method_body ENDMETHOD
	    { end_method(); }
	;
method_body:
	  method_declarations statements
	;
statements:
	  /* NOTHING */
	| statements statement
	;
statement:
	  ifstart statements end elseifs elsepart endif
	    { machine[$1 + 1] = $3; machine[$1 + 2] = $6; }
	| forstart statements endfor	{ machine[$1 + 4] = $3; }
	| forrng statements endfor	{ machine[$1 + 2] = $3; }
	| WHILE { code(PUSHPC); } whilecond statements endwhile	
					{ machine[$3 + 1] = $5; }
	| dostart statements WHILE '('	{ $<num>$ = progi; }
	  expr ')' ';'			{ inst_ctr = code3(DOWHILE, $1 + 2, $<num>5);
	                                  machine[$1 + 1] = inst_ctr;
					  loop_depth--; }
	| atstart statements endat	{ machine[$1 + 1] = $3; }
	| expr ';'			{ code(POP); }
        | ID '=' expr ';'
	    { code_assign($1, ASGNLVAR, ASGNGVAR); }
	| ID '[' expr ']' '=' expr ';'
	    { code_assign($1, ASGNLVARINDEX, ASGNGVARINDEX); }
	| T_RETURN expr ';'		{ code(T_RETURN); }
	| T_RETURN ';'			{ code3(NUMPUSH, 0, T_RETURN); }
	| T_RAISE expr ';'		{ code(T_RAISE); }
	| CONTINUE count ';'	
	    {
	        if ($2 < 1 || $2 > loop_depth) {
		    yyerror("Invalid continue statement.");
		} else {
		    code2(CONTINUE, $2);
		}
	    }
	| BREAK count ';'
	    {
	        if ($2 < 1 || $2 > loop_depth) {
		    yyerror("Invalid break statement.");
		} else {
		    code2(BREAK, $2);
		}
	    }
	| ';'
	| error ';'
	;
count:	  /* NOTHING */		{ $$ = 1; }
	| NUMBER_CONST		{ $$ = $1; }
	;
ifstart:
	  IF '(' expr ')'	{ $$ = code3(IF, 0, 0); }
	;
elseifs:
	  /* NOTHING */				 { $$ = -1; }
	| elseifs elseifstart statements end	
	    { machine[$2 + 1] = $4; if ($1 >= 0) $$ = $1; else $$ = $2; }
	;
elseifstart:
	  ELSEIF '(' expr ')'	{ $$ = code2(ELSEIF, 0); }
	;
elsepart:
	  /* NOTHING */
	| ELSE { code(ELSE); } statements end
	;
end:      /* NOTHING */		{ $$ = code(STOP) + 1; }
	;
endif:	    ENDIF			{ $$ = code(STOP) + 1; }
	;
whilecond:	'(' expr ')'	{ $$ = code2(WHILE, 0); loop_depth++; }
	;
endwhile:   ENDWHILE		{ loop_depth--; $$ = code(STOP) + 1; }
	;
dostart:	DO		{ $$ = code2(DO, 0); loop_depth++; }
	;
forstart:
	  FOR ID IN '(' expr ')'
			        { loopvar = find_loopvar($2);
				  $$ = code2(NUMPUSH, 0);
				  code3(FOR, loopvar, 0);
				  loop_depth++; }
	;
forrng:
	  FOR ID IN '[' expr UPTO expr ']'	
				{ loopvar = find_loopvar($2);
				  $$ = code3(FORRNG, loopvar, 0); 
				  loop_depth++; }
	;
endfor:	  ENDFOR		{ loop_depth--; $$ = code(STOP) + 1; }
	;
atstart:
	  AT '(' expr ')'	{ $$ = code2(AT, 0); }
	;
endat:
	  ENDAT			{ $$ = code(STOP) + 1; }
	;
arglist:
	  /* NOTHING */	
	| nonempty_arglist
	;
nonempty_arglist:
	  argexpr
        | nonempty_arglist ',' argexpr
        ;
argexpr:
	  expr			{ }
	| '@' expr		{ code(SPLICE); }
	;
maplist:
	  /* NOTHING */		{ $$ = 0; }
	| expr MAPSTO expr		{ $$ = 1; }
	| maplist ',' expr MAPSTO expr	{ $$ = $1 + 1; }
	;
object_const:
	  '#' NUMBER_CONST '@' ID
	    {
		int		serv;

		if ((serv = serv_name2id(sym_get(cur_obj, $4)->str)) < 0) {
		    error("Server not found:", sym_get(cur_obj, $4)->str);
		} else {
		    $$.id = $2;
		    $$.server = serv;
		}
		sym_free(cur_obj, $4);
	    }
	| '#' NUMBER_CONST
	    { $$.id = $2; $$.server = 0; }
	| '#' '-' NUMBER_CONST	 
	    { $$.id = - $3; $$.server = 0; }
	;
opt_pass_to:
	  /* NOTHING*/
	    {
		if (!cur_obj->parents->len) {
		    yyerror("Object has no parents to pass() to");
		} else {
		    $$ = cur_obj->parents->el[0].v.obj;
		}
	    }
	| TO object_const
	    {
		if (!hasparent(cur_obj, $2)) {
		    yyerror("Object doesn't have indicated parent");
		} else {
		    $$ = $2;
		}
	    }
pass_expr:
	  PASS '(' { code(ARGSTART); } arglist ')' opt_pass_to
	    {
		$$ = code2(PASS, $6.id);
	    }
	;
builtin_objvar:
	  PLAYER	{ $$ = code(PLAYER); }
	| CALLER	{ $$ = code(CALLER); }
	| THIS		{ $$ = code(THIS); }
	| ARGS		{ $$ = code(ARGS); }
	;
expr:	  NUMBER_CONST	{ $$ = code2(NUMPUSH, $1); }
	| STRING_CONST  { $$ = code2(STRPUSH, $1); }
	| object_const  { $$ = code3(OBJPUSH, $1.id, $1.server); }
	| ERROR_CONST	{ $$ = code2(ERRPUSH, $1); }
	| TYPE_SPEC	{ $$ = code2(NUMPUSH, $1); }
	| '$' ID	{ $$ = code2(GETSYSVAR, $2); }
	| '{' { code(ARGSTART); } arglist '}'	{ $$ = code(LISTPUSH); }
	| '[' maplist ']'	{ $$ = code2(MAPPUSH, $2); }
	| pass_expr
	| PARENTS	{ $$ = code(PARENTS); }
	| ID '(' { code(ARGSTART); } arglist ')'
	    {
		int	f = bf_lookup(sym_get(cur_obj, $1)->str);
		    
		if (f < 0) {
			error("Built-in function not found:  ",
			    sym_get(cur_obj, $1)->str);
			break;
		} else {
		    $$ = code(f);
		}
		sym_free(cur_obj, $1);
	    }
	| builtin_objvar
	| var
	| expr '.' ID
	    { $$ = $1; code3(ARGSTART, MESSAGE, $3); }
	| expr '.' '(' expr ')'
	    { $$ = $1; code2(ARGSTART, MESSAGE_EXPR); }
	| expr '.' ID '(' { code(ARGSTART); } arglist ')'
	    { $$ = $1; code2(MESSAGE, $3); }
	| expr '.' '(' expr ')' '(' { code(ARGSTART); } arglist ')'
	    { $$ = $1; code(MESSAGE_EXPR);  }
	| expr '[' expr ']'	{ $$ = $1; code(INDEX); }
	| expr '[' expr UPTO expr ']'		{ $$ = $1; code(SUBSET); }
	| expr '[' UPTO expr ']'		{ $$ = $1; code(LSUBSET); }
	| expr '[' expr UPTO ']'		{ $$ = $1; code(RSUBSET); }
	| expr '+' expr		{ $$ = $1; code(ADD); }
	| expr '-' expr		{ $$ = $1; code(SUB); }
	| expr '*' expr		{ $$ = $1; code(MUL); }
	| expr '/' expr		{ $$ = $1; code(DIV); }
	| expr '%' expr		{ $$ = $1; code(MOD); }
	| '-' expr  %prec NEGATE	{ $$ = $2; code(NEGATE); }
	| expr AND		{ $<num>$ = code2(AND, 0); }
	           expr		{ inst_ctr = code(ENDAND);
	                          machine[$<num>3 + 1] = inst_ctr;
				  $$ = $1; }
	| expr OR 		{ $<num>$ = code2(OR, 0); }
		   expr		{ inst_ctr = code(ENDOR);
		    		  machine[$<num>3 + 1] = inst_ctr;
		  		  $$ = $1; }
	| NOT expr		{ $$ = $2; code(NOT); }
	| expr EQ expr		{ $$ = $1; code(EQ); }
	| expr NE expr		{ $$ = $1; code(NE); }
	| expr LT expr		{ $$ = $1; code(LT); }
	| expr LE expr		{ $$ = $1; code(LE); }
	| expr GT expr		{ $$ = $1; code(GT); }
	| expr GE expr		{ $$ = $1; code(GE); }
	| '(' expr ')'		{ $$ = $2; }
	| expr IN expr		{ $$ = $1; code(IN); }
	;
var:	ID
	    {
		int	varno;
		Var	v;

		if ((varno = builtin_var($1))) {
		    $$ = code(varno);
		} else if (cur_method && var_find(cur_method->vars, $1, &varno)) {
		    $$ = code2(GETLVAR, varno);
		    sym_free(cur_obj, $1);
		} else if (cur_obj && cur_obj->parents
       && var_get_global(cur_obj, sym_get(cur_obj, $1)->str, &v) == E_NONE) {
		    $$ = code2(GETGVAR, $1);
		} else {
		    error("Variable not declared:  ",
			sym_get(cur_obj, $1)->str);
		    sym_free(cur_obj, $1);
		}
	    }
	;
const_expr:
	  STRING_CONST
	    { $$.type = STR;  $$.v.str = string_dup(sym_get(cur_obj, $1));
	      sym_free(cur_obj, $1); }
	| NUMBER_CONST
	    { $$.type = NUM;  $$.v.num = $1; }
	| '-' NUMBER_CONST
	    { $$.type = NUM;  $$.v.num = - $2; }
	| object_const
	    { $$.type = OBJ;  $$.v.obj = $1; }
	| ERROR_CONST
	    { $$.type = ERR;  $$.v.err = $1; }
	| list_const
	    { $$.type = LIST;  $$.v.list = $1; }
	| map_const
	    { $$.type = MAP;  $$.v.map = $1; }
	;
list_const:
	  '{' list_of_constants '}'		{ $$ = $2; }
	;
map_const:
	  '[' list_of_constant_pairs ']'	{ $$ = $2; }
	;
list_of_constants:
	  /* NOTHING */
	    { $$ = list_new(0); }
	| const_expr
	    { $$ = list_new(1);  $$->el[0] = $1; }
	| list_of_constants ',' const_expr
	    { $$ = list_append($1, $3, -1); }
	;
list_of_constant_pairs:
	  /* NOTHING */
	    { $$ = map_new(0); }
	| const_expr MAPSTO const_expr
	    { $$ = map_new(0);  map_add($$, $1, $3); }
	| list_of_constant_pairs ',' const_expr MAPSTO const_expr
	    { $$ = map_add($1, $3, $5); }
	;
%%

static void	 (*parse_perror)(const char *s);
static int	 (*parse_getc)(void);
static void	 (*parse_ungetc)(int c);
static int	 nerrors;
static Objid	 programmer;
static int	 parse_start;		/* flag for start of object or method */
static int	 line_start;		/* flag for start of line */
static int	 cpp_output;            /* flag: interpret # at line start */
static char	 progfile_name[MAX_PATH_LEN];
static int	 eoo;			/* end of object flag */
static void	 skip_whitespace(void);

static int
follow(int expect, int ifyes, int ifno)     /* look ahead for >=, etc. */
{
    int c = (*parse_getc)();

    if (c == expect) {
        return ifyes;
    }
    (*parse_ungetc)(c);
    return ifno;
}

struct tt_entry {
    const char	*name;
    int		token;
} tokentable[] = {
    { "if",		IF },
    { "else",		ELSE },
    { "elseif",		ELSEIF },
    { "endif",		ENDIF },
    { "for",		FOR },
    { "while",		WHILE },
    { "do",		DO },
    { "in",		IN },
    { "to",		TO },
    { "endfor",		ENDFOR },
    { "endwhile",	ENDWHILE },
    { "at",		AT },
    { "endat",		ENDAT },
    { "object",		OBJECT },
    { "endobject",	ENDOBJECT },
    { "method",		METHOD },
    { "endmethod",	ENDMETHOD },
    { "verb",		VERB },
    { "ignore",		IGNORE },
    { "parents",	PARENTS },
#if 0
    { "args",		ARGS },
    { "player",		PLAYER },
    { "caller",		CALLER },
    { "this",		THIS },
#endif
    { "return",		T_RETURN },
    { "raise",		T_RAISE },
    { "continue",	CONTINUE },
    { "break",		BREAK },
    { "pass",		PASS },
    { "var",		VAR },
    { "blocked",	T_BLOCKED },
};

static void
get_lineno(void)
{
    int		c, i;

    lineno = 0;
    while ((c = (*parse_getc)()) == ' ')		/* skip leading space */
	;					
    while (isdigit(c)) {
	lineno = 10 * lineno + c - '0';		/* store it, depends on ASCII */
	if ((c = (*parse_getc)()) == EOF) {		/* if EOF, */
	    return;				/* abort */
	}
    }
    while ((c = (*parse_getc)()) != '"') {		/* skip to first " */
	if (c == EOF || c == '\n') {		/* abort if no " before EOL */
	    return;
	}
    }
    for (i = 0; i < MAX_PATH_LEN - 1; i++) {
        if ((c = (*parse_getc)()) == EOF || c == '"' || c == '\n') {
	    break;
	}
	progfile_name[i] = c;
    }
    progfile_name[i] = '\0';
    while (c != '\n' && c != EOF) {
	c = (*parse_getc)();
    }
}

static void
skip_whitespace(void)
{
    int		c;

    do {
	c = (*parse_getc)();
	if (c == '\n') {
	    line_start = 1;
	    lineno++;
	} else if (isspace(c)) {
	    line_start = 0;
	}
    } while (isspace(c));
    (*parse_ungetc)(c);
}

static int
tokenize_number(int c)
{
    int		n = 0;

    do {
	n = n * 10 + c - '0';
	c = (*parse_getc)();
    } while (isdigit(c));
    (*parse_ungetc)(c);
    yylval.num = n;
    return NUMBER_CONST;
}

static int
type_spec(const char *s, Type_spec *t)
{
    if (!strcasecmp(s, "num")) {
	*t = NUM;  return 1;
    } else if (!strcasecmp(s, "str")) {
	*t = STR;  return 1;
    } else if (!strcasecmp(s, "obj")) {
	*t = OBJ;  return 1;
    } else if (!strcasecmp(s, "list")) {
	*t = LIST;  return 1;
    } else if (!strcasecmp(s, "map")) {
	*t = MAP;  return 1;
    } else if (!strcasecmp(s, "err")) {
	*t = ERR;  return 1;
    }
    return 0;
}

static int
tokenize_ident(int  c)
{
    int		i;
    String	*str;
    Error	e;
    Type_spec	t;

    str = string_new(0);
    str = string_catc(str, c);
    while(isalnum(c = (*parse_getc)()) || c == '_') {
	str = string_catc(str, c);
    }
    (*parse_ungetc)(c);

/*
 * check for multi-char alpha tokens (if, for, else, etc.)
 */
    for (i = 0; i < Arraysize(tokentable); i++) {
	if (!strcasecmp(str->str, tokentable[i].name)) {
	    string_free(str);
	    if (tokentable[i].token == ENDOBJECT) {
		eoo = 1;
	    }
	    return tokentable[i].token;
	}
    }
/*
 * check for error names (E_*)
 */
    if (str->str[0] == 'E' && str->str[1] == '_') {
	e = err_name2id(str->str);
	yylval.num = (int) e;
	string_free(str);
	return ERROR_CONST;
    }
/*
 * check for type specifiers 
 */
    if (type_spec(str->str, &t)) {
	string_free(str);
	yylval.num = (int) t;
	return TYPE_SPEC;
    }
/*
 * otherwise, it's just a normal boring identifier
 */
    yylval.num = sym_add(cur_obj, str);
    return ID;
} 

static int
tokenize_string(int c)
{
    String	*str;

    str = string_new(0);
    while(1) {
	c = (*parse_getc)();
	if (c == '"') {
	    break;
	} else if (c == '\\') {		/* blackslash escapes */
	    c = (*parse_getc)();
	    switch(c) {
	      case 'n':			/* newline */
		  /* turn it into a CRLF for braindead clients */
		str = string_cat(str, "\r\n");
		continue;
	      case 't':			/* tab */
		c = '\t'; break;
	      default:			/* default escape does nothing */
		break;			/* this handles \", \\ and \<cr> */
	    } /* switch */
	} else if (c == '\n' || c == EOF) {
	    error("Missing quote", "");
	    break;
	}
	str = string_catc(str, c);
    }
    yylval.num = sym_add(cur_obj, str);
    return STRING_CONST;
}

static int
yylex(void)
{
    int		c;

    if (parse_start) {
	parse_start = 0;
	if (parse_expecting == OBJECT) {
	    return EXPECTING_OBJECT;
	} else if (parse_expecting == METHOD) {
	    return EXPECTING_METHOD;
	} /* if */
    } /* if */

    if (eoo) {			/* end of object, return end of file */
	return EOF;
    }
    skip_whitespace();
    c = (*parse_getc)();

    while (cpp_output && line_start && c == '#') {
	get_lineno();		/* get line number from # directive */
	skip_whitespace();
	c = (*parse_getc)();
    }
    line_start = 0;
    if (isdigit(c)) {
	return tokenize_number(c);
    } else if (isalpha(c) || c == '_')  {
	return tokenize_ident(c);
    } else if (c == '"') {	/* string constants */
	return tokenize_string(c);
    } 
    switch(c) {
      case '>':         return follow('=', GE, GT);
      case '<':         return follow('=', LE, LT);
      case '=':         return follow('=', EQ, follow('>', MAPSTO, '='));
      case '!':         return follow('=', NE, NOT);
      case '&':		return follow('&', AND, '&');
      case '|':		return follow('|', OR, '|');
      case '.':		return follow('.', UPTO, '.');
      default:          return c;
    }
}

static int
obj_result(Object *new, int expecting, int do_init)
{
    char	 buf[80];
    int		 exists, r = 1;

    exists = valid(new->id);

    if (!new) {
	sprintf(buf, "Null compile; end of objects assumed.");
    } else if (nerrors > 0) {
	if (new->id.id >= 0) {
	    sprintf(buf, "%d error(s).  Object #%d not programmed.", nerrors,
		    new->id.id);
	} else {
	    sprintf(buf, "%d error(s).", nerrors);
	}
	free_object(new);
    } else if (expecting >= 0 && !exists) {
	sprintf(buf, "Object #%d does not exist.", new->id.id);
	free_object(new);
    } else if (expecting >= 0 && !can_program(programmer, new->id)) {
	sprintf(buf, "Object #%d:  Permission denied.", new->id.id);
	free_object(new);
    } else {
	assign_object(new->id, new);
	if (exists) {
	    sprintf(buf, "Object #%d reprogrammed.", new->id.id);
	} else {
	    sprintf(buf, "Object #%d programmed.", new->id.id);
	    if (do_init) {
		send_message(-1, 0, 0, sys_obj, sys_obj, new->id,
		    sym_sys(INIT), list_dup(empty_list), 0, new->id);
	    }
	}
	r = 0;
    }
    parse_perror(buf);
    return r;
}

int
compile(Playerid player, int (*f_getc)(void), void (*f_ungetc)(int c),
	void (*f_perror)(const char *s), int expecting, Object *o,
        String *mname, int is_cpp_output, int do_init)
{
    int		c;

    cpp_output = is_cpp_output;
    nerrors = 0;
    lineno = 1;
    line_start = 1;
    eoo = 0;
    programmer.id = player;
    programmer.server = 0;
    progfile_name[0] = '\0';

    parse_getc = f_getc;
    parse_ungetc = f_ungetc;
    parse_perror = f_perror;
    cur_method = 0;

    switch(expecting) {
      case -1:			/* expecting objects, can create new */
      case 0:			/* expecting objects */
	parse_expecting = OBJECT;
	while((c = (*parse_getc)()) != EOF) {
	    (*parse_ungetc)(c);
	    cur_obj = new_object();
	    sym_init(cur_obj);
	    cur_method = 0;
	    parse_start = 1;
	    yyparse();
	    eoo = 0;
	    if (obj_result(cur_obj, expecting, do_init)) {
		break;
	    }
	    skip_whitespace();
	}
	break;
      case 1:			/* expecting a single method */
	parse_start = 1;
	parse_expecting = METHOD;
	cur_obj = o;
	start_method(sym_add(o, string_dup(mname)), 0);
	yyparse();
	if (nerrors) {
	    char	buf[80];
	    sprintf(buf, "%d error(s).  Method not programmed.", nerrors);
	    parse_perror(buf);
	    code_copy(cur_method);
	    free_method(cur_obj, cur_method);
	} else {
	    end_method();
	    cache_put(cur_obj, cur_obj->id.id);
	}
	break;
    }
    return nerrors;
}

static void
error(const char  *s, const char *t)	/* print error message */
{
    String	*str;

    nerrors++;
    str = string_new(0);
    if (progfile_name[0]) {
	str = string_cat(str, progfile_name);
	str = string_cat(str, ":  ");
    }
    str = string_catnum(str, lineno);
    str = string_cat(str, ":  ");
    str = string_cat(str, s);
    if (t) {
	str = string_catc(str, ' ');
	str = string_cat(str, t);
    }
    (*parse_perror)(str->str);
    string_free(str);
}

static void  yyerror(const char *s)
{
    error(s, "");
}

static void
end_method(void)
{
    Method	*m;
 
    if (!cur_method) {
	return;
    }
    code(STOP);
    code_copy(cur_method);
    if (parse_expecting == METHOD) {
	rm_method(cur_obj, sym_get(cur_obj, cur_method->name)->str);
    } else if ((m = find_method(cur_obj,
	    sym_get(cur_obj, cur_method->name)->str))) {
	error("Method multiply defined:  ", sym_get(cur_obj, m->name)->str);
	free_method(cur_obj, cur_method);
	return;
    }
    add_method(cur_obj, cur_method);
}

int
code_assign(int varname, int local_opcode, int global_opcode)
{
    int		varno;
    int		addr = 0;
    Var		v;

    if ((varno = builtin_var(varname))) {
	if (local_opcode == ASGNLVARINDEX) {
	    error("Cannot index assign built-in variable",
		  sym_get(cur_obj, varname)->str);
	} else if (varno == PLAYER) {
	    code(SETPLAYER);
	} else {
	    error("Cannot assign to built-in variable",
		   sym_get(cur_obj, varname)->str);
	}
    } else if (cur_method && var_find(cur_method->vars, varname, &varno)) {
	addr = code2(local_opcode, varno);
	sym_free(cur_obj, varname);
    } else if (cur_obj && cur_obj->parents &&
	       var_get_global(cur_obj,
	      	sym_get(cur_obj, varname)->str, &v) == E_NONE) {
	addr = code2(global_opcode, varname);
    } else {
	error("Variable not declared:  ",
	    sym_get(cur_obj, varname)->str);
	sym_free(cur_obj, varname);
    }
    return addr;
}

static void
do_check_parents(Object *o)
{
    switch(check_parents(programmer, o, o->parents)) {
      case E_NONE:
	return;
      case E_INVIND:
	yyerror("Parent must be local");
	break;
      case E_OBJNF:
	yyerror("Parent does not exist");
	break;
      case E_MAXREC:
	yyerror("Parents list would cause a loop");
	break;
      case E_RANGE:
	yyerror("Object must have at least one parent");
	break;
      case E_PERM:
	yyerror("Cannot use parent; Permission Denied");
	break;
      default:
	yyerror("Parents list invalid");
	break;
    }
}

static void
add_parent(Object *o, Objid newparent)
{
    Var		v;

    v.type = OBJ;
    v.v.obj = newparent;
    if (!o) {
	return;
    } else if (!o->parents) {
	o->parents = list_new(1);
	o->parents->el[0] = v;
    } else {
	o->parents = list_setadd(o->parents, v);
    }
}

static void
start_method(int name, int blocked)
{
    int		e;

    loop_depth = 0;
    cur_method = MALLOC(Method, 1);
    cur_method->ref = 1;
    cur_method->name = name;
    cur_method->blocked = blocked;
    cur_method->ninst = 0;
    cur_method->code = 0;
    cur_method->vars = 0;
    for (e = 0; e < NERRS; e++) {
	cur_method->ehandler[e] = EH_DEFAULT;
    }
    cur_method->next = 0;
}

static int
find_loopvar(int varname)
{
    int		varno = 0;
    Var		dummy;

    if (var_get_global(cur_obj,sym_get(cur_obj, varname)->str, &dummy)
	!= E_VARNF) {
	    yyerror("'for' variable must be local");
	    varno = -1;
    } else if (var_add_local(cur_method, varname, &varno)) {
	sym_free(cur_obj, varname);
    }
    return varno;
}

static void
new_global_var(int name, Type_spec type, Var value)
{
    Var	r;

    if (type != value.type) {
	error("Type mismatch on initializer for variable: ",
	      sym_get(cur_obj, name)->str);
	sym_free(cur_obj, name);
    } /* if */
    if (var_get_global(cur_obj, sym_get(cur_obj, name)->str, &r) == E_NONE) {
	if (type != r.type) {
	    error("Type mismatch on inherited variable: ",
		  sym_get(cur_obj, name)->str);
	    sym_free(cur_obj, name);
	} /* if */
    } /* if */
    if (var_add_global(cur_obj, name, value) != E_NONE) {
	error("Variable multiply defined:  ",
	    sym_get(cur_obj, name)->str);
	sym_free(cur_obj, name);
    } /* if */
} /* new_global_var() */

static int
builtin_var(int varno)
{
    const char	*name = sym_get(cur_obj, varno)->str;

    if (!strcasecmp(name, "player")) {
	return PLAYER;
    } else if (!strcasecmp(name, "caller")) {
	return CALLER;
    } else if (!strcasecmp(name, "args")) {
	return ARGS;
    } else if (!strcasecmp(name, "this")) {
	return THIS;
    } else {
	return 0;
    }
}