/* Copyright (c) 1993 Stephen F. White */
#include <stdio.h>
#ifdef SYSV
#include <string.h>
#else
#include <strings.h>
#endif
#include <ctype.h>
#include <sys/time.h>
#include "config.h"
#include "cool.h"
#include "proto.h"
#include "sys_proto.h"
#include "netio.h"
#include "execute.h"
#include "servers.h"
/*
* execute.c
*
* This file contains execution global parameters, and is the interface
* to the finite state machine executor. The interface is through
* call_method(), resume_method_*() or call_verb().
*
* It also contains functions useful for the opcodes of the state machine,
* including push(), pop(), pushn(), raise() and send_message_and_block().
*/
/*
* The actual execution globals
*/
Event frame; /* contains pc, sp, local vars, etc. */
Object * this;
enum state ex_state; /* current execution state */
Var ex_retval; /* return value of current method */
/*
* Prototypes for functions local to this file
*/
static void execute(void);
static Error call_verb_recursive(Object *o, int msgid, int age, int ticks,
Objid player, Objid from, Objid to, const char *verb,
const char *argstr);
static void free_frame(Event *e);
static void clear_locks(Object *o, int msgid);
static String *add_traceback(String *str);
static void
do_method(Object *o, Method *m, int msgid, int age, int ticks, Objid player,
Objid from, Objid to, const char *message, List *args)
{
frame.sp = 0; frame.pc = 0;
frame.nvars = var_count_local(m);
var_init_local(o, m, frame.stack);
frame.sp += frame.nvars;
frame.msgid = msgid;
frame.age = age;
frame.ticks = ticks;
frame.player = player;
frame.caller = from;
frame.this = to;
frame.on = o;
frame.on->ref++;
frame.args = list_dup(args);
frame.m = m;
frame.m->ref++;
frame.last_opcode = -1;
pushn(-1); /* the terminator */
ex_state = RUNNING;
execute();
}
Error
call_method(int msgid, int age, int ticks, Objid player, Objid from, Objid to,
const char *message, List *args, Objid on)
{
Method *m;
Object *o = retrieve(on);
if (!o) {
return E_OBJNF;
} else if ((m = find_method_recursive(o, message, &o))) {
do_method(o, m, msgid, age, ticks, player, from, to, message, args);
return E_NONE;
} else { /* not found */
return E_METHODNF;
}
}
/*
* call_verb() - Call a verb on the local object
*
* Trashes "cmd", but does not free it
*
*/
Error
call_verb(int msgid, int age, int ticks, Objid player, Objid from, Objid to,
List *args)
{
char *cmd, *start, *end, *argstr;
Error r;
if (args->len != 1) {
return E_NARGS;
} else if (args->el[0].type != STR) {
return E_ARGTYPE;
}
/*
* parse out verb (first word)
*/
start = cmd = str_dup(args->el[0].v.str->str);
while (isspace(*start)) {
start++;
}
if (!*start) {
FREE(cmd);
return E_VERBNF;
}
end = start;
while (*end && !isspace(*end))
end++;
if (*end) {
argstr = end + 1;
*end = '\0';
} else {
argstr = end;
}
r = call_verb_recursive(retrieve(to), msgid, age, ticks, player, from, to,
start, argstr);
FREE(cmd);
return r;
}
/*
* call_verb_recursive()
*
* Attempts to find a verb on an object, or any of its ancestors.
* If one is found, the associated method is called, again starting
* at the base object.
*
* Return values: E_VERBNF indicates no verb was found;
* E_NONE indicates a verb was found.
*/
static Error
call_verb_recursive(Object *o, int msgid, int age, int ticks, Objid player,
Objid from, Objid to, const char *verb, const char *argstr)
{
Verbdef *v;
const char *dobj = 0, *prep = 0, *iobj = 0;
int dobjlen = 0, preplen = 0;
List *args, *parents;
int i;
Error r;
if (!o) {
return E_VERBNF; /* not a valid object, therefore no verb found */
}
for (v = o->verbs; v; v = v->next) { /* for all verbs */
if (verb_match(sym_get(o, v->verb)->str, verb)) {
if (v->prep == -1) {
dobj = argstr;
dobjlen = strlen(argstr);
} else if (!prep_match(sym_get(o, v->prep)->str, argstr,
&dobj, &prep, &iobj, &dobjlen, &preplen)) {
continue; /* prep doesn't match; keep looking */
}
args = list_new(4);
args->el[0].type = STR;
args->el[0].v.str = string_cpy(verb);
args->el[1].type = args->el[2].type = args->el[3].type = STR;
args->el[1].v.str = string_ncpy(dobj, dobjlen);
args->el[2].v.str = prep ? string_ncpy(prep, preplen) : sym_sys(BLANK);
args->el[3].v.str = iobj ? string_cpy(iobj) : sym_sys(BLANK);
r = call_method(msgid, age, ticks, player, from, to,
sym_get(o, v->method)->str, args, to);
list_free(args);
return r;
/* found a match, quit */
}
}
parents = list_dup(o->parents); /* just in case o gets swapped out */
for (i = 0; i < parents->len; i++) {
r = call_verb_recursive(retrieve(parents->el[i].v.obj), msgid, age,
ticks, player, from, to, verb, argstr);
if (r != E_VERBNF) {
list_free(parents);
return r; /* found a match on ancestor, quit */
}
}
list_free(parents);
return E_VERBNF; /* no match found */
}
static void
execute(void)
{
Op_entry *op; /* index into opcode table */
ex_retval.type = NUM;
ex_retval.v.num = 0;
if (frame.on->id.id == SYS_OBJ) { /* methods on SYS_OBJ */
frame.ticks = 0; /* always get a fresh supply of ticks */
}
this = retrieve(frame.this);
if (!this) {
ex_state = STOPPED;
}
while (ex_state == RUNNING) {
if ((op = opcodes[frame.m->code[frame.pc]])) {
frame.last_opcode = frame.m->code[frame.pc];
frame.pc++;
(op->func)();
frame.ticks++;
if (frame.ticks >= max_ticks) {
raise(E_TICKS);
}
} else {
writelog();
fprintf(stderr, "execute(): unknown opcode %d\n",
frame.m->code[frame.pc]);
raise(E_INTERNAL);
break;
}
}
if (ex_state != BLOCKED) {
clear_locks(this, frame.msgid);
free_frame(&frame);
}
if (ex_state == STOPPED) {
List *args;
if (ex_retval.type == NUM && ex_retval.v.num == 0) {
args = list_dup(zero);
} else {
args = list_new(1);
args->el[0] = ex_retval;
}
(void) send_message(frame.msgid, frame.age, frame.ticks, frame.player,
frame.this, frame.caller, sym_sys(RETURN), args, 0,
frame.caller);
}
}
String *
add_traceback_header(String *str, Error e)
{
str = string_cat(str, "ERROR: ");
str = string_cat(str, err_id2desc(e));
str = string_cat(str, "\r\nraised in ");
str = add_traceback(str);
return str;
}
static String *
add_traceback(String *str)
{
int lineno;
/* calcluate the offending line # using decompile_method() */
(void) decompile_method((String *) 0, frame.on, frame.m, 0, 0, 0, 0,
frame.pc, &lineno);
str = string_cat(str, "method \"");
str = string_cat(str, sym_get(frame.on, frame.m->name)->str);
str = string_cat(str, "\" on ");
str = string_catobj(str, frame.on->id, 1);
str = string_cat(str, ", line ");
str = string_catnum(str, lineno);
#if 0
if ((opc = opcodes[frame.last_opcode]->name)) {
str = string_cat(str, ", last opcode ");
str = string_cat(str, opc);
}
str = string_cat(str, ", PC = ");
str = string_catnum(str, frame.pc);
#endif
return str;
}
List *
make_raise_args(Error e)
{
List *r = list_new(2);
r->el[0].type = ERR;
r->el[0].v.err = e;
r->el[1].type = STR;
r->el[1].v.str = string_new(0);
return r;
}
void
send_raise(List *raise_args)
{
(void) send_message(frame.msgid, frame.age, frame.ticks, frame.player,
frame.this, frame.caller, sym_sys(RAISE),
raise_args, 0, frame.caller);
}
void
raise(Error e)
{
Var ev;
List *raise_args;
ev.type = ERR; ev.v.err = e;
if (e != E_STACKOVR) {
push(ev);
}
if (e == E_NONE) {
return;
}
switch(frame.m->ehandler[e]) {
case EH_DEFAULT:
raise_args = make_raise_args(e);
raise_args->el[1].v.str = add_traceback_header(raise_args->el[1].v.str,
e);
send_raise(raise_args);
ex_state = RAISED;
break;
case EH_IGNORE:
case EH_CATCH: /* catch is unimplemented, does an ignore for now */
break;
}
}
/*
* send_message_and_block()
*
* Sends a message and queues up an event for the message's reply.
* As with send_message(), "msg" and "args" are eaten.
*/
void
send_message_and_block(Objid from, Objid to, String *msg, List *args, Objid on)
{
Event *e = MALLOC(Event, 1);
Error r;
Timeval now;
*e = frame;
e->msg = 0;
gettimeofday(&now, 0);
e->timeout_at = now;
e->timeout_at.tv_sec += MSG_TIMEOUT;
e->retry_interval = MSG_RETRY;
e->retry_at = timer_addmsec(now, e->retry_interval);
r = send_message(-1, frame.age + 1, frame.ticks, frame.player, from,
to, msg, args, e, on);
if (r == E_NONE) {
ex_state = BLOCKED;
} else {
FREE(e);
raise(r);
}
}
void
resume_method_return(Event *e, Var retval)
{
frame = *e; /* restore execution parameters */
ex_state = RUNNING; /* start the beast */
retval = var_dup(retval);
push(retval); /* push return value */
execute();
}
void
resume_method_raise(Event *e, List *args)
{
frame = *e; /* restore execution parameters */
switch(e->m->ehandler[args->el[0].v.err]) {
case EH_DEFAULT:
if (args->el[1].v.str->str[0]) {
args->el[1].v.str = string_cat(args->el[1].v.str, "\r\ncalled from ");
args->el[1].v.str = add_traceback(args->el[1].v.str);
} else {
args->el[1].v.str = add_traceback_header(args->el[1].v.str,
args->el[0].v.err);
}
send_raise(list_dup(args));
ex_state = RAISED;
break;
case EH_IGNORE:
case EH_CATCH:
push(args->el[0]);
ex_state = RUNNING;
break;
}
execute();
}
void
resume_method_halt(Event *e)
{
frame = *e;
ex_state = HALTED;
execute();
}
void
resume_method(Event *e)
{
frame = *e;
ex_state = RUNNING;
execute();
}
static void
free_frame(Event *e)
{
Var v;
while(e->sp) {
v = pop(); var_free(v);
}
list_free(e->args);
free_method(e->on, e->m);
free_object(e->on);
}
static void
clear_locks(Object *o, int msgid)
{
Lock *l, *prev = 0, *next;
if (!o) return;
for (l = o->locks; l; l = next) {
next = l->next;
if (l->added_by == msgid) {
if (prev) {
prev->next = l->next;
} else {
o->locks = l->next;
}
string_free(l->name);
FREE(l);
} else {
prev = l;
}
}
}
void
push(Var v)
{
if (frame.sp >= STACK_SIZE) {
raise(E_STACKOVR);
} else {
frame.stack[frame.sp++] = v;
}
}
Var
pop(void)
{
if (frame.sp <= 0) {
raise(E_STACKUND);
return zero->el[0];
} else {
return frame.stack[--frame.sp];
}
}
void
pushpc(int i)
{
Var n;
n.type = PC;
n.v.num = i;
push(n);
}
void
pushn(long i)
{
Var n;
n.type = NUM;
n.v.num = i;
push(n);
}
Var
pop_args(int num)
{
Var r;
r.type = LIST;
r.v.list = list_new(num);
for (; num; num--) {
r.v.list->el[num - 1] = pop();
}
return r;
}
/*
* psub() - do % substitutions from variables
*
* `s' is the string to be substituted on. Returns a String containing
* the result: Each %foo is replaced by variable 'foo'.
*
* NOTE: psub() is in here (execute.c) because it needs access to the
* stack for local vars.
*/
static String *dosub(String *r, const char *vname, int vnamelen);
String *
psub(const char *s)
{
String *r = string_new(0);
int state = 0;
const char *vname;
while(*s) {
switch(state) {
case 0: /* waiting for a % */
if (*s == '%') {
state = 1;
} else {
r = string_catc(r, *s);
}
break;
case 1: /* got %, waiting for a varname */
if (*s == '%') { /* got %%, */
r = string_catc(r, '%'); /* append % */
state = 0;
} else {
vname = s; /* got varname start */
state = 2;
}
break;
case 2:
if (*s == '%') { /* got %foo%, do substitution */
r = dosub(r, vname, s - vname);
state = 0;
} else if (!isalnum(*s)) { /* got %foo, do substitution */
r = dosub(r, vname, s - vname);
r = string_catc(r, *s); /* append terminating char */
state = 0;
}
break;
}
s++;
}
if (state == 2) {
r = dosub(r, vname, s - vname);
}
return r;
}
static String *
dosub(String *r, const char *vname, int vnamelen)
{
int varno;
char *start;
if (var_find_local_by_name(frame.on, frame.m, vname, vnamelen, &varno)) {
start = r->str + r->len;
r = var_tostring(r, frame.stack[varno], 0);
if (isupper(vname[0]) && islower(*start)) { /* if caps sub, */
*start = toupper(*start); /* capitalize result */
}
}
return r;
}
Timeval
timer_sub(Timeval t1, Timeval t2)
{
t1.tv_sec -= t2.tv_sec;
t1.tv_usec -= t2.tv_usec;
if (t1.tv_usec < 0) {
t1.tv_usec += 1000000;
t1.tv_sec--;
}
return t1;
}
Timeval
timer_addmsec(Timeval t, int msec)
{
t.tv_sec += msec / 1000;
t.tv_usec += (msec % 1000) * 1000;
if (t.tv_usec > 1000000) {
t.tv_usec -= 1000000;
t.tv_sec++;
}
return t;
}