/*
// Full copyright information is available in the file ../doc/CREDITS
//
// text database format handling, used by coldcc.
//
// This has become a beast, quick, put it out of its misery and hack it up
// in YACC
*/
#define TEXTDB_C
#define DEBUG_TEXTDB 0
#include "defs.h"
#include <string.h>
#include <ctype.h>
#include "cdc_db.h"
#include "cdc_pcode.h"
#include "util.h"
#include "textdb.h"
#include "moddef.h"
#include "quickhash.h"
/*
// ------------------------------------------------------------------------
// This is a quick hack for compiling a text-formatted coldc file.
//
// should probably eventually do this with yacc
*/
typedef struct idref_s {
Long objnum; /* objnum if its an objnum */
char str[BUF]; /* string name */
Int err;
} idref_t;
/* globals, because its easier this way */
Int use_natives;
Long line_count;
Long method_start;
Obj * cur_obj;
extern Bool print_objs;
extern Bool print_invalid;
extern Bool print_warn;
#define ERR(__s) (printf("\rLine %ld: %s\n", (long) line_count, __s))
#define ERRf(__s, __x) { \
printf("\rLine %ld: ", (long) line_count); \
printf(__s, __x); \
fputc('\n', stdout); \
}
#define WARN(_printf_) { \
if (print_warn) { \
printf("\rLine %ld: WARNING: ", (long) line_count); \
printf _printf_; \
fputc('\n', stdout); \
} \
}
#define DIE(__s) { \
printf("\rLine %ld: ERROR: %s\n", (long) line_count, __s); \
shutdown_coldcc(); \
}
#define DIEf(__fmt, __arg) { \
printf("\rLine %ld: ERROR: ", (long) line_count); \
printf(__fmt, __arg); \
fputc('\n', stdout); \
shutdown_coldcc(); \
}
/* Dancer: This is more portable than the pointer arithmetic
that it replaces. This should work on all boxes */
#define COPY(__buf, __s1, __s2) { \
char s0; \
s0=*__s2; \
*__s2='\0'; \
strcpy(__buf, __s1); \
*__s2=s0; \
}
#define MATCH(__s, __t, __l) (!strnccmp(__s, __t, __l) && isspace(__s[__l]))
#define NEXT_SPACE(__s) {for (; *__s && !isspace(*__s) && *__s != (char) NULL; __s++);}
#define NEXT_WORD(__s) {for (; isspace(*__s) && *__s != (char) NULL; __s++);}
/*
// ------------------------------------------------------------------------
// these are access flags, redone because we may want vars to eventually
// have access as well, although it would be different and more restricted
*/
#define N_NEW 1
#define N_OLD 0
#define N_UNDEF -1
#define A_NONE 0x0
#define A_PUBLIC MS_PUBLIC
#define A_PROTECTED MS_PROTECTED
#define A_PRIVATE MS_PRIVATE
#define A_FROB MS_FROB
#define A_ROOT MS_ROOT
#define A_DRIVER MS_DRIVER
/*
// ------------------------------------------------------------------------
*/
INTERNAL Method * get_method(FILE * fp, Obj * obj, char * name);
char * strchop(char * str, Int len);
INTERNAL void print_dbref(Obj * obj, cObjnum objnum, FILE * fp, Bool objnames);
void blank_and_print_obj(char * what, Obj * obj);
/*
// ------------------------------------------------------------------------
// make this do more eventually
*/
#if 0
INTERNAL void shutdown_coldcc(void) {
exit(1);
}
#endif
extern void shutdown_coldcc(void);
typedef struct holder_s holder_t;
/* native holder */
typedef struct nh_s nh_t;
struct nh_s {
Long objnum;
Ident native; /* the native name */
Ident method; /* if it has been renamed, this is the method name */
Int valid;
nh_t * next;
};
struct holder_s {
Long objnum;
cStr * str;
holder_t * next;
};
holder_t * holders = NULL;
nh_t * nhs = NULL;
INTERNAL Int add_objname(char * str, Long objnum) {
Ident id = ident_get(str);
Obj * obj = NULL;
Long num = INV_OBJNUM;
if (lookup_retrieve_name(id, &num) && num != objnum) {
WARN(("Attempt to rebind existing objname $%s (#%li)",
str, (long) num));
ident_discard(id);
return 0;
}
/* the object doesn't exist yet, so lets add the name to the db,
with the number, and keep it in a holder stack so we can set
the name on the object after it is defined */
obj = cache_retrieve(objnum);
if (!obj) {
holder_t * holder = (holder_t *) malloc(sizeof(holder_t));
lookup_store_name(id, objnum);
holder->objnum = objnum;
holder->str = string_from_chars(ident_name(id), strlen(ident_name(id)));
holder->next = holders;
holders = holder;
} else {
if (num == objnum)
obj->objname = ident_dup(id);
else
object_set_objname(obj, id);
cache_discard(obj);
}
ident_discard(id);
return 1;
}
/* here because data_from_literal() calls it, and genesis wants to handle
it differently -- bad, will fix eventually */
cObjnum get_object_name(Ident id) {
cObjnum num;
if (!lookup_retrieve_name(id, &num)) {
num = db_top++;
add_objname(ident_name(id), num);
}
return num;
}
INTERNAL void cleanup_holders(void) {
holder_t * holder = holders,
* old = NULL;
Long objnum;
Obj * obj;
Ident id;
while (holder != NULL) {
id = ident_get(string_chars(holder->str));
if (!lookup_retrieve_name(id, &objnum)) {
if (print_warn)
printf("\rWARNING: Name $%s for object #%d disapppeared.\n",
ident_name(id), (int) objnum);
} else if (objnum != holder->objnum) {
if (print_warn)
printf("\rWARNING: Name $%s is no longer bound to object #%d.\n",
ident_name(id), (int) objnum);
} else {
obj = cache_retrieve(holder->objnum);
if (obj) {
if (obj->objname == NOT_AN_IDENT)
obj->objname = ident_dup(id);
cache_discard(obj);
} else {
if (print_warn)
printf("\rWARNING: Object $%s (#%d) was never defined.\n",
ident_name(id), (int) objnum);
lookup_remove_name(id);
}
}
string_discard(holder->str);
ident_discard(id);
old = holder;
holder = holder->next;
free(old);
}
}
/* only call with a method which declars a MF_NATIVE flag */
/* holders are redundant, but it lets us keep track of methods defined
native, but which are not */
INTERNAL nh_t * find_defined_native_method(cObjnum objnum, Ident name) {
nh_t * nhp;
for (nhp = nhs; nhp != (nh_t *) NULL; nhp = nhp->next) {
if (nhp->native == name) {
if (nhp->objnum == objnum)
return nhp;
}
}
return (nh_t *) NULL;
}
INTERNAL void remember_native(Method * method) {
nh_t * nh;
nh = find_defined_native_method(method->object->objnum, method->name);
if (nh != (nh_t *) NULL) {
fformat(stdout,
"\rLine %l: ERROR: %O.%s() overrides existing native definition.\n",
line_count, nh->objnum, ident_name(nh->native));
shutdown_coldcc();
}
nh = (nh_t *) malloc(sizeof(nh_t));
nh->objnum = method->object->objnum;
nh->valid = 0;
nh->next = nhs;
nhs = nh;
nh->native = ident_dup(method->name);
nh->method = NOT_AN_IDENT;
}
INTERNAL void frob_n_print_errstr(char * err, char * name, cObjnum objnum);
void verify_native_methods(void) {
Ident mname;
Ident name;
Obj * obj;
Method * method = NULL;
cObjnum objnum;
cList * errors;
cList * code = list_new(0);
native_t * native;
register Int x;
nh_t * nh = (nh_t *) NULL;
/* check the methods we know about */
for (x=0; x < NATIVE_LAST; x++) {
native = &natives[x];
/* if they didn't define it right, ignore it */
if ((strlen(native->bindobj) == 0) || (strlen(native->name) == 0))
continue;
/* get the object name */
name = ident_get(native->bindobj);
if (name == NOT_AN_IDENT)
continue;
/* find the object */
objnum = INV_OBJNUM;
lookup_retrieve_name(name, &objnum);
ident_discard(name);
/* die? */
if (objnum == INV_OBJNUM) {
if (print_warn)
printf("\rWARNING: Unable to find object for native $%s.%s()\n",
native->bindobj, native->name);
continue;
}
/* pull the object or die if we cant */
obj = cache_retrieve(objnum);
if (!obj) {
if (print_warn)
printf("\rWARNING: Unable to retrieve object #%li ($%s)\n",
(long) objnum, native->bindobj);
continue;
}
/* is the name correct? */
name = ident_get(native->name);
if (name == NOT_AN_IDENT) {
if (print_warn)
fformat(stdout,
"\rWARNING: Invalid name \"%s\" for native method on \"%O\"\n",
native->name, obj->objnum);
cache_discard(obj);
continue;
}
/* get a copy to reference the actual method name */
mname = ident_dup(name);
/* see if we have defined it already */
nh = find_defined_native_method(objnum, name);
/* If so, see if we need to change the method name appropriately */
if (nh != (nh_t *) NULL) {
if (nh->method != NOT_AN_IDENT) {
ident_discard(mname);
mname = ident_dup(nh->method);
}
}
/* now find it on the object, use 'mname' as the method name */
method = object_find_method(objnum, mname, FROB_ANY);
/* it does not exist, compile an empty method */
if (method == NULL) {
method = compile(obj, code, &errors);
method->native = x;
method->m_flags |= MF_NATIVE;
object_add_method(obj, mname, method);
if (nh != (nh_t *) NULL)
nh->valid = 1;
if (errors != NULL)
list_discard(errors);
method_discard(method);
/* it was prototyped, set the native structure pointer and
mark the object as dirty */
} else {
if (!(method->m_flags & MF_NATIVE) &&
use_natives != FORCE_NATIVES)
{
if (print_warn)
fformat(stdout, "\rWARNING: method definition %O.%s() overrides native method.\n", obj->objnum, ident_name(mname));
} else {
method->native = x;
method->m_flags |= MF_NATIVE;
obj->dirty = 1;
if (nh != (nh_t *) NULL)
nh->valid = 1;
}
}
ident_discard(mname);
ident_discard(name);
cache_discard(obj);
}
list_discard(code);
/* now cleanup method holders */
while (nhs != (nh_t *) NULL) {
nh = nhs;
nhs = nh->next;
if (nh->method != NOT_AN_IDENT) {
name = nh->method;
ident_discard(nh->native);
} else {
name = nh->native;
}
if (nh->valid) {
ident_discard(name);
} else {
/* remove the native array designator from the method,
but not the native mask */
cur_obj = cache_retrieve(objnum);
if (print_warn)
printf("\rWARNING: No native definition for method .%s()\n",
ident_name(name));
if (cur_obj) {
method = object_find_method_local(cur_obj, name, FROB_ANY);
if (method) {
method->native = -1;
cur_obj->dirty = 1;
}
cache_discard(cur_obj);
}
ident_discard(name);
}
free(nh);
}
}
/*
// ------------------------------------------------------------------------
// its small enough lets just do copies, rather than dealing with pointers
*/
#define NOOBJ 0
#define ISOBJ 1
INTERNAL Int get_idref(char * sp, idref_t * id, Int isobj) {
char str[BUF], * p;
register Int x;
char * end;
id->objnum = INV_OBJNUM;
strcpy(id->str, "");
if (!*sp) {
id->err = 1;
return 0;
}
id->err = 0;
p = sp;
/* special case objnums, drop out of need be */
if (isobj && *p == '#') {
p++;
if (isdigit(*p) || (*p == '-' && isdigit(*(p+1)))) {
id->objnum = strtol(p, &end, 10);
return (end - p)+1;
} else {
DIEf("Invalid objnum \"%s\".", sp)
}
}
/* get just the symbol */
for (x = 0;
*p != (char) NULL && (isalnum(*p) || *p == '_' || *p == '$');
x++, p++);
strncpy(str, sp, x);
p = str;
str[x] = (char) NULL;
if (*p == '$') {
if (!isobj)
DIEf("Invalid symbol '%s.", str)
p++;
}
strcpy(id->str, p);
return x;
}
/*
// ------------------------------------------------------------------------
*/
INTERNAL Long parse_to_objnum(idref_t ref) {
Long id,
objnum = 0;
Int result;
if (ref.str[0] != (char) NULL) {
if (!strncmp(ref.str, "root", 4) && strlen(ref.str) == 4)
return 1;
else if (!strncmp(ref.str, "sys", 3) && strlen(ref.str) == 3)
return 0;
id = ident_get(ref.str);
result = lookup_retrieve_name(id, &objnum);
ident_discard(id);
return (result) ? objnum : INV_OBJNUM;
}
return ref.objnum;
}
/*
// ------------------------------------------------------------------------
*/
INTERNAL Obj * handle_objcmd(char * line, char * s, Int new) {
idref_t obj;
char * p = (char) NULL,
obj_str[BUF];
Obj * target = NULL;
cList * parents = list_new(1); /* will always have a least one parent */
Long objnum;
cData d;
/* grab what should be the object number or name */
p = strchr(s, ':');
if (p == NULL) {
p = strchr(s, ';');
if (p == NULL) {
ERR("Invalid directive termination:");
DIEf("\"%s\"", line);
}
}
/* this gives us a copy for error reporting */
COPY(obj_str, s, p);
/* parse the reference */
s += get_idref(obj_str, &obj, ISOBJ);
/* define initial parents */
if (*s == ':') {
idref_t parent;
char par_str[BUF];
Int len,
more = TRUE;
/* step past ':' and skip whitespace */
s++;
NEXT_WORD(s);
/* get each parent, look them up */
while ((more && *s != (char) NULL) && running) {
p = strchr(s, ',');
if (p == NULL) {
/* we may be at the end of the line.. */
if (s[strlen(s) - 1] != ';')
DIE("Parse Error, unterminated directive.")
s[strlen(s) - 1] = (char) NULL;
strcpy(par_str, s);
len = strlen(par_str);
more = FALSE;
} else {
strncpy(par_str, s, p - s);
par_str[p - s] = (char) NULL;
len = p - s;
}
get_idref(par_str, &parent, ISOBJ);
objnum = parse_to_objnum(parent);
if (VALID_OBJECT(objnum)) {
d.type = OBJNUM;
d.u.objnum = objnum;
parents = list_add(parents, &d);
} else {
if (objnum >= 0) {
WARN(("Ignoring undefined parent \"%s\".", par_str));
} else {
WARN(("Ignoring invalid parent \"%s\".", par_str));
}
WARN(("For object \"%s\".", obj_str));
}
/* skip the last word, ',' and whitespace */
if (more) {
s += (p - s + 1);
NEXT_WORD(s);
}
}
}
objnum = parse_to_objnum(obj);
if (new == N_OLD) {
if (objnum < 0) {
WARN(("old: Invalid Object \"%s\"", obj_str));
list_discard(parents);
return NULL;
} else {
target = cache_retrieve(objnum);
if (!target) {
WARN(("old: Unable to find object \"%s\".", obj_str));
} else if (objnum == ROOT_OBJNUM) {
WARN(("old: attempt to destroy $root ignored."));
} else if (objnum == SYSTEM_OBJNUM) {
WARN(("old: attempt to destroy $sys ignored."));
} else {
ERRf("old: destroying object %s.", obj_str);
target->dead = 1;
cache_discard(target);
target = NULL;
}
}
} else if (new == N_NEW) {
if (!parents->len && objnum != ROOT_OBJNUM)
DIEf("new: Attempt to define object %s without parents.", obj_str);
if (objnum == ROOT_OBJNUM || objnum == SYSTEM_OBJNUM) {
WARN(("new: Attempt to recreate %s ignored.", obj_str));
/* $root and $sys should ALWAYS exist */
target = cache_retrieve(objnum);
} else {
if ((target = cache_retrieve(objnum))) {
WARN(("new: destroying existing object %s.", obj_str));
target->dead = 1;
cache_discard(target);
target = NULL;
}
target = object_new(objnum, parents);
}
} else {
target = cache_retrieve(objnum);
if (!target) {
WARN(("Creating object \"%s\".", obj_str));
if (parents->len == 0 && objnum != ROOT_OBJNUM)
DIEf("Attempt to define object %s without parents.", obj_str);
target = object_new(objnum, parents);
if (!target) {
DIEf("ABORT, unable to create object #%li", (long) objnum);
}
}
}
/* if we should, add the name. If it already has one, we just replace it.*/
if (objnum != ROOT_OBJNUM && objnum != SYSTEM_OBJNUM) {
if (obj.str[0] != (char) NULL)
add_objname(obj.str, target->objnum);
}
/* free up this list */
list_discard(parents);
return target;
}
/*
// ------------------------------------------------------------------------
*/
INTERNAL void handle_parcmd(char * s, Int new) {
cData d;
char * p = NULL,
obj_str[BUF];
Obj * target = NULL;
Long objnum;
cList * parents;
Int num, len;
idref_t id;
/* parse the reference */
len = get_idref(s, &id, ISOBJ);
p = s + len;
NEXT_SPACE(p);
if (*p != ';' || !len)
DIEf("Invalid object definition \"%s\".", s);
objnum = parse_to_objnum(id);
if (objnum == ROOT_OBJNUM)
DIE("Attempt to change $root's parents.");
if (!cur_obj)
DIEf("Attempt to %s parent when no object is defined.",
new ? "add" : "del");
d.type = OBJNUM;
d.u.objnum = objnum;
parents = list_dup(cur_obj->parents);
if (new == N_OLD) {
num = list_search(parents, &d);
if (num != -1) {
parents = list_delete(parents, num);
if (object_change_parents(cur_obj, parents) >= 0)
WARN(("old parent: Oops, something went wrong..."));
}
} else {
target = cache_retrieve(objnum);
if (!target) {
WARN(("Unable to find object \"%s\" for new parent.", obj_str));
return;
}
cache_discard(target);
if (list_search(parents, &d) != -1) {
parents = list_add(parents, &d);
if (object_change_parents(cur_obj, parents) >= 0)
WARN(("newparent: Oops, something went wrong..."));
}
}
}
/*
// ------------------------------------------------------------------------
*/
INTERNAL void handle_namecmd(char * line, char * s, Int new) {
char name[BUF];
char * p;
Long num, other;
Ident id;
/* bump if they have a '$' in the name */
if (*s == '$')
s++;
p = s;
/* skip past the name */
for (; *p && !isspace(*p) && *p != (char) NULL && *p != ';'; p++);
/* copy the name */
COPY(name, s, p);
/* see if it exists */
id = ident_get(name);
if (lookup_retrieve_name(id, &other)) {
ident_discard(id);
WARN(("objname $%s is already bound to objnum #%li",name,(long) other));
return;
}
ident_discard(id);
/* lets see if there is a objnum association, or if we should pick one */
for (; isspace(*p) && *p != (char) NULL; p++);
if (*p != ';') {
if (!p) {
ERR("Abnormal termination of name directive:");
DIEf("\"%s\"", line);
}
if (*p == '#')
p++;
num = (Long) atoi(p);
if (!num && *p != '0') {
ERR("Invalid object number association:");
DIEf("\"%s\"", line);
}
} else {
num = db_top++;
}
add_objname(name, num);
}
/*
// ------------------------------------------------------------------------
*/
INTERNAL void handle_varcmd(char * line, char * s, Int new, Int access) {
cData d;
char * p = s;
Long definer, var;
idref_t name;
Obj * def;
if (*s == '#' || *s == '$') {
s += get_idref(s, &name, ISOBJ);
definer = parse_to_objnum(name);
if (!cache_check(definer)) {
WARN(("Ignoring object variable with invalid parent:"));
if (strlen(line) > 55) {
line[50] = line[51] = line[52] = '.';
line[53] = (char) NULL;
}
WARN(("\"%s\"", line));
return;
}
if (!object_has_ancestor(cur_obj->objnum, definer)) {
WARN(("Ignoring object variable with no ancestor:"));
if (strlen(line) > 55) {
line[50] = line[51] = line[52] = '.';
line[53] = (char) NULL;
}
WARN(("\"%s\"", line));
return;
}
NEXT_WORD(s);
} else {
if (!cur_obj)
DIE("var: attempt to define object variable without defining object.");
definer = cur_obj->objnum;
}
/* strip trailing spaces and semi colons */
while (s[strlen(s) - 1] == ';' || isspace(s[strlen(s) - 1]))
s[strlen(s) - 1] = (char) NULL;
s += get_idref(s, &name, NOOBJ);
if (name.str[0] == (char) NULL)
DIEf("Invalid variable name \"%s\"", p);
var = ident_get(name.str);
if (new == N_OLD) {
def = cache_retrieve(definer);
if (!def)
DIE("Abnormal disappearance of object.");
/* axe the variable */
object_delete_var(cur_obj, def, var);
cache_discard(def);
} else {
d.type = -2;
/* skip the current 'word' until we hit a space or a '=' */
for (; *s && !isspace(*s) && *s != (char) NULL && *s != '='; s++);
/* incase we hit a space and not a '=', bump it up to the next word */
NEXT_WORD(s);
if (*s == '=') {
s++;
NEXT_WORD(s);
data_from_literal(&d, s);
if (d.type == -1) {
if (print_warn) {
printf("\rLine %ld: WARNING: invalid data for variable ", (long) line_count);
print_dbref(cur_obj, cur_obj->objnum, stdout, TRUE);
if (cur_obj->objnum!=definer && (def=cache_retrieve(definer))) {
fputc('<', stdout);
print_dbref(def, def->objnum, stdout, TRUE);
fputc('>', stdout);
cache_discard(def);
}
printf(",%s:\nLine %ld: WARNING: data: %s\nLine %ld: WARNING: Defaulting value to ZERO ('0').\n",
ident_name(var), (long) line_count, strchop(s, 50), (long) line_count);
}
}
}
if (d.type < 0) {
d.type = INTEGER;
d.u.val = 0;
}
object_put_var(cur_obj, definer, var, &d);
data_discard(&d);
}
}
/*
// ------------------------------------------------------------------------
*/
INTERNAL void handle_evalcmd(FILE * fp, char * s, Int new, Int access) {
Long name;
Method * method;
/* set the name as <eval> */
name = ident_get("<eval>");
/* grab the code */
method = get_method(fp, cur_obj, ident_name(name));
/* die if its invalid */
if (!method)
DIE("Method definition failed");
/* run it */
method->name = NOT_AN_IDENT;
method->object = cur_obj;
task_method(cur_obj, method);
/* toss it */
method_discard(method);
ident_discard(name);
}
/*
// ------------------------------------------------------------------------
*/
INTERNAL Int get_method_name(char * s, idref_t * id) {
Int count = 0, x;
char * p;
if (*s == '.')
s++, count++;
for (x=0, p=s; *p != (char) NULL; x++, p++) {
if (isalnum(*p) || *p == '_')
continue;
break;
}
count += x;
strncpy(id->str, s, x);
id->str[x] = (char) NULL;
return count;
}
INTERNAL void handle_bind_nativecmd(FILE * fp, char * s) {
idref_t nat;
idref_t meth;
Ident inat, imeth;
nh_t * n = (nh_t *) NULL;
s += get_method_name(s, &nat);
if (*s == '(')
s+=2;
NEXT_WORD(s);
s += get_method_name(s, &meth);
if (nat.str[0] == (char) NULL || meth.str[0] == (char) NULL)
DIE("Invalid method name in bind_native directive.\n")
inat = ident_get(nat.str);
imeth = ident_get(meth.str);
n = find_defined_native_method(cur_obj->objnum, imeth);
if (n == (nh_t *) NULL)
DIE("Attempt to bind_native to method which is not native.\n")
/* if they've already bound it, we have precedence */
if (n->method != NOT_AN_IDENT)
ident_discard(n->method);
ident_discard(n->native);
/* remember the new method we are bound to */
n->native = ident_dup(inat);
n->method = ident_dup(imeth);
ident_discard(inat);
ident_discard(imeth);
}
INTERNAL void handle_methcmd(FILE * fp, char * s, Int new, Int access) {
char * p = NULL;
cObjnum definer;
Ident name;
idref_t id = {INV_OBJNUM, "", 0};
Method * method;
Obj * obj;
Int flags = MF_NONE;
NEXT_WORD(s);
if (*s == '#' || *s == '$') {
s += get_idref(s, &id, ISOBJ);
if (id.err)
DIE("Invalid object \"$\"")
/* parse the parent.. */
definer = parse_to_objnum(id);
/* make sure it exists, and not just as a name */
if (!cache_check(definer))
DIE("method defined with invalid parent...")
} else {
if (!cur_obj)
DIE("attempt to define method without defining object.");
definer = cur_obj->objnum;
}
s += get_method_name(s, &id);
if (id.str[0] == (char) NULL)
DIE("No method name.");
name = ident_get(id.str);
/* see if any flags are set */
if ((p = strchr(s, ':')) != NULL) {
p++;
while (*p != (char) NULL && running) {
NEXT_WORD(p);
if (!strnccmp(p, "nooverride", 10)) {
p += 10;
flags |= MF_NOOVER;
} else if (!strnccmp(p, "synchronized", 12)) {
p += 12;
flags |= MF_SYNC;
} else if (!strnccmp(p, "locked", 6)) {
p += 6;
flags |= MF_LOCK;
} else if (!strnccmp(p, "native", 6)) {
p += 6;
flags |= MF_NATIVE;
} else if (!strnccmp(p, "forked", 6)) {
p += 6;
flags |= MF_FORK;
} else if (*p == '{' || *p == ';') {
break;
} else {
char ebuf[BUF];
s = p;
NEXT_SPACE(s);
if (*s == ',')
s--;
COPY(ebuf, p, s);
WARN(("Unknown flag: \"%s\".", ebuf));
p = s;
}
if (*p == ',')
p++;
}
} else {
if ((p = strchr(s, ';')) == NULL &&
(p = strchr(s, '{')) == NULL)
DIE("Un-terminted method definition.")
}
obj = cache_retrieve(definer);
if (!obj)
DIE("Abnormal disappearance of object.");
if (*p != ';') {
/* get the method */
method = get_method(fp, obj, ident_name(name));
} else {
cList * code = list_new(0);
cList * errors;
method = compile(obj, code, &errors);
list_discard(code);
if (errors != NULL)
list_discard(errors);
}
if (!method)
DIE("Method definition failed");
method->m_access = access;
method->m_flags = flags;
object_add_method(obj, name, method);
if (method->m_flags & MF_NATIVE)
remember_native(method);
method_discard(method);
/* free up the remaining resources */
ident_discard(name);
cache_discard(obj);
}
/*
// ------------------------------------------------------------------------
*/
INTERNAL void frob_n_print_errstr(char * err, char * name, cObjnum objnum) {
Int line = 0;
cStr * str;
if (strncmp("Line ", err, 5) == 0) {
err += 5;
while (isdigit(*err))
line = line * 10 + *err++ - '0';
err += 2;
}
str = format("\rLine %l: [line %d in %O.%s()]: %s\n",
method_start + line,
line,
objnum,
name,
err);
fputs(str->s, stderr);
string_discard(str);
}
INTERNAL Method * get_method(FILE * fp, Obj * obj, char * name) {
Method * method;
cList * code,
* errors;
cStr * line;
cData d;
Int i;
code = list_new(0);
d.type = STRING;
/* used in printing method errs */
method_start = line_count;
for (line = fgetstring(fp); line && running; line = fgetstring(fp)) {
line_count++;
/* hack for determining the end of a method */
if (line->len == 2 && line->s[0] == '}' && line->s[1] == ';') {
string_discard(line);
method = compile(obj, code, &errors);
list_discard(code);
/* do warnings and errors, if they exist */
for (i = 0; i < errors->len; i++)
frob_n_print_errstr(errors->el[i].u.str->s, name, obj->objnum);
list_discard(errors);
/* return the method, null or not */
return method;
}
d.u.str = line;
code = list_add(code, &d);
string_discard(line);
}
/* We ran out of lines. This wasn't supposed to happen. */
DIE("Text dump ended inside method definition!");
return NULL;
}
/*
// ------------------------------------------------------------------------
*/
#define next_token(__s) { \
NEXT_SPACE(__s); \
NEXT_WORD(__s); \
}
void compile_cdc_file(FILE * fp) {
Int new = 0,
access = A_NONE;
cStr * line,
* str = NULL;
char * p,
* s;
Obj * obj,
* root;
/* start at line 0 */
line_count = 0;
root = cur_obj = cache_retrieve(ROOT_OBJNUM);
/* use fgetstring because it'll expand until we have the whole line */
while ((line = fgetstring(fp)) && running) {
line_count++;
/* Strip trailing spaces from the line. */
while (line->len && isspace(line->s[line->len - 1]))
line->len--;
line->s[line->len] = (char) NULL;
/* Strip unprintables from the line. */
for (p = s = line->s; *p; p++) {
while (*p && !isprint(*p))
p++;
*s++ = *p;
}
*s = (char) NULL;
line->len = s - line->s;
if (!line->len) {
string_discard(line);
continue;
}
/* if we end in a backslash, concatenate */
if (line->s[line->len - 1] == '\\') {
line->s[line->len - 1] = (char) NULL;
line->len--;
if (str != NULL) {
str = string_add(str, line);
string_discard(line);
} else {
str = line;
}
continue;
} else {
if (str != NULL) {
str = string_add(str, line);
string_discard(line);
} else
str = line;
}
s = str->s;
/* ignore beginning space */
NEXT_WORD(s);
/* old, new or who cares? */
if (MATCH(s, "new", 3)) {
new = N_NEW;
next_token(s);
} else if (MATCH(s, "old", 3)) {
new = N_OLD;
next_token(s);
} else
new = N_UNDEF;
/* access? */
if (MATCH(s, "public", 6)) {
access = A_PUBLIC;
next_token(s);
} else if (MATCH(s, "protected", 9)) {
access = A_PROTECTED;
next_token(s);
} else if (MATCH(s, "private", 7)) {
access = A_PRIVATE;
next_token(s);
} else if (MATCH(s, "frob", 4)) {
access = A_FROB;
next_token(s);
} else if (MATCH(s, "root", 4)) {
access = A_ROOT;
next_token(s);
} else if (MATCH(s, "driver", 6)) {
access = A_DRIVER;
next_token(s);
} else {
access = A_NONE;
}
if (MATCH(s, "object", 6) || MATCH(s, "as", 2)) {
if (*s == 'a')
s += 2;
else
s += 6;
NEXT_WORD(s);
obj = handle_objcmd(str->s, s, new);
if (obj != NULL) {
if (cur_obj != NULL)
cache_discard(cur_obj);
if (print_objs)
blank_and_print_obj("Compiling ", obj);
cur_obj = obj;
}
} else if (MATCH(s, "parent", 6)) {
s += 6;
NEXT_WORD(s);
handle_parcmd(s, new);
} else if (MATCH(s, "var", 3)) {
s += 3;
NEXT_WORD(s);
handle_varcmd(str->s, s, new, access);
} else if (MATCH(s, "method", 6)) {
s += 6;
NEXT_WORD(s);
handle_methcmd(fp, s, new, access);
} else if (MATCH(s, "eval", 4)) {
s += 4;
NEXT_WORD(s);
handle_evalcmd(fp, s, new, access);
} else if (MATCH(s, "bind_native", 11)) {
s += 11;
NEXT_WORD(s);
handle_bind_nativecmd(fp, s);
} else if (MATCH(s, "name", 4)) {
s += 4;
NEXT_WORD(s);
handle_namecmd(str->s, s, new);
} else if (strnccmp(s, "//", 2)) {
WARN(("parse error, unknown directive."));
ERRf("\"%s\"\n", s);
shutdown_coldcc();
}
string_discard(str);
str = NULL;
}
cache_discard(root);
verify_native_methods();
fputs("\rCleaning up name holders...", stdout);
fflush(stdout);
cleanup_holders();
fputs("done.\n", stdout);
fflush(stdout);
}
/*
// ------------------------------------------------------------------------
// decompile the binary db to a text file
*/
Int last_length; /* used in doing fancy formatting */
Hash * dump_hash;
void dump_object(Long objnum, FILE *fp, Bool objnames);
INTERNAL char * method_definition(Method * m);
#define PRINT_OBJNAME(__obj, __fp) { \
fputc('$', __fp); \
fputs(ident_name(__obj->objname), __fp); \
}
#define PRINT_OBJNUM(__num, __fp) { \
fprintf(fp, "#%li", (long) __num); \
}
INTERNAL void print_dbref(Obj * obj, cObjnum objnum, FILE * fp, Bool objnames) {
Bool cachepull = FALSE;
if (objnames) {
if (!obj) {
obj = cache_retrieve(objnum);
cachepull = TRUE;
}
if (!obj || obj->objname == -1)
PRINT_OBJNUM(objnum, fp)
else
PRINT_OBJNAME(obj, fp)
if (cachepull)
cache_discard(obj);
} else {
PRINT_OBJNUM(objnum, fp);
}
}
/*
// ------------------------------------------------------------------------
*/
Int text_dump(Bool objnames) {
FILE * fp;
char buf[BUF];
/* Open the output file. */
sprintf(buf, "%s.out", c_dir_textdump);
fp = open_scratch_file(buf, "w");
if (!fp) {
fprintf(stderr, "\rUnable to open temporary file \"%s\".\n", buf);
return 0;
}
last_length = 0;
#if 0
START_SEARCH();
dump_object(ROOT_OBJNUM, fp, objnames);
END_SEARCH();
#endif
dump_hash = hash_new(0);
dump_object(ROOT_OBJNUM, fp, objnames);
hash_discard(dump_hash);
close_scratch_file(fp);
if (rename(buf, c_dir_textdump) == F_FAILURE) {
fprintf(stderr, "\rUnable to rename \"%s\" to \"%s\":\n\t%s\n",
buf, c_dir_textdump, strerror(GETERR()));
return 0;
}
fputc('\n', stdout);
return 1;
}
#define is_system(__n) (__n == ROOT_OBJNUM || __n == SYSTEM_OBJNUM)
void dump_object(Long objnum, FILE *fp, Bool objnames) {
Obj * obj;
cList * objs,
* code;
cData * d,
dobj;
cStr * str;
Var * var;
Int first,
i;
Method * meth;
dobj.type = OBJNUM;
dobj.u.objnum = objnum;
if (hash_find(dump_hash, &dobj) != F_FAILURE)
return;
obj = cache_retrieve(objnum);
/* try to handle this */
if (obj == NULL) {
printf("\rWARNING: NULL object pointer found, you likely used a corrupt binary db!\nWARNING: Attempting to work around. This will probably create a\nWARNING: textdump with invalid ancestors\n");
return;
}
#if 0
/* have we looked at this object yet? */
if (obj->search == cur_search) {
cache_discard(obj);
return;
}
#endif
/* grab the parents list */
objs = list_dup(obj->parents);
cache_discard(obj);
/* first dump any parents which haven't already been dumped. */
if (list_length(objs) != 0) {
for (d = list_first(objs); d; d = list_next(objs, d))
dump_object(d->u.objnum, fp, objnames);
}
if (hash_find(dump_hash, &dobj) != F_FAILURE)
return;
dump_hash = hash_add(dump_hash, &dobj);
/* ok, get this object now */
obj = cache_retrieve(objnum);
#if 0
/* did we get written out since the last check? */
if (obj->search == cur_search) {
cache_discard(obj);
return;
}
/* ok, lets do it then, mark it dirty and update cur_search */
obj->dirty = 1;
obj->search = cur_search;
#endif
/* let them know? */
if (print_objs)
blank_and_print_obj("Decompiling ", obj);
/* put 'new' on everything except the system objects */
if (!is_system(obj->objnum))
fputs("new ", fp);
/* print the object definition */
fputs("object ", fp);
print_dbref(obj, obj->objnum, fp, objnames);
/* add the parents */
if (objs->len != 0) {
fputc(':', fp);
fputc(' ', fp);
first = 1;
for (d = list_first(objs); d; d = list_next(objs, d)) {
if (!first)
fputs(", ", fp);
first = 0;
print_dbref(NULL, d->u.objnum, fp, objnames);
}
}
list_discard(objs);
fputs(";\n", fp);
/* if we are doing number-only, put a name definition in */
if (!objnames && obj->objname != -1 && !is_system(obj->objnum)) {
fputs("name $", fp);
fputs(ident_name(obj->objname), fp);
fprintf(fp, " #%li", (long) obj->objnum);
fputs(";\n", fp);
}
fputc('\n', fp);
/* define variables */
for (i = 0; i < obj->vars.size; i++) {
var = &obj->vars.tab[i];
if (var->name == -1)
continue;
if (!cache_check(var->cclass))
continue;
str = data_to_literal(&var->val, objnames);
fputs("var ", fp);
print_dbref(NULL, var->cclass, fp, objnames);
fformat(fp, " %I = %S;\n", var->name, str);
string_discard(str);
}
fputc('\n', fp);
/* define methods */
for (i = 0; i < obj->methods.size; i++) {
meth = obj->methods.tab[i].m;
if (!meth)
continue;
/* define it */
fputs(method_definition(meth), fp);
/* list it */
code = decompile(meth, obj, 4, FMT_FULL_PARENS);
if (list_length(code) == 0) {
fputs(";\n\n", fp);
} else {
fputs(" {\n", fp);
for (d = list_first(code); d; d = list_next(code, d)) {
fputs(" ", fp);
fputs(string_chars(d->u.str), fp);
putc('\n', fp);
}
/* end it */
fputs("};\n\n", fp);
}
list_discard(code);
/* if it is native, and they have renamed it, put a rename
directive down */
if (meth->m_flags & MF_NATIVE && meth->native != -1) {
if (strcmp(ident_name(meth->name), natives[meth->native].name))
fprintf(fp, "bind_native .%s() .%s();\n\n",
natives[meth->native].name,
ident_name(meth->name));
}
}
fputc('\n', fp);
/* now dump it's children */
objs = list_dup(obj->children);
cache_discard(obj);
if (objs->len) {
for (d = list_first(objs); d; d = list_next(objs, d))
dump_object(d->u.objnum, fp, objnames);
}
list_discard(objs);
}
#define ADD_FLAG(__bit, __str1, __str2) { \
if (m->m_flags & __bit) { \
if (flag) \
strcat(flags, __str1); \
else { \
strcpy(flags, __str2); \
flag++; \
} \
} \
}
INTERNAL char * method_definition(Method * m) {
static char buf[255];
static char flags[50];
char * s;
Int flag = 0;
/* method access */
if (m->m_access == MS_PRIVATE)
strcpy(buf, "private ");
else if (m->m_access == MS_PROTECTED)
strcpy(buf, "protected ");
else if (m->m_access == MS_ROOT)
strcpy(buf, "root ");
else if (m->m_access == MS_FROB)
strcpy(buf, "frob ");
else if (m->m_access == MS_DRIVER)
strcpy(buf, "driver ");
else
strcpy(buf, "public ");
/* method name */
s = ident_name(m->name);
#if 0
/* this should else and use string_add_unparsed, but, ohwell */
if (is_valid_ident(s))
#endif
strcat(buf, "method .");
strcat(buf, s);
strcat(buf, "()");
/* flags */
if (m->m_flags & MF_NOOVER) {
strcpy(flags, "nooverride");
flag++;
}
ADD_FLAG(MF_SYNC, ", synchronized", "synchronized");
ADD_FLAG(MF_LOCK, ", locked", "locked");
ADD_FLAG(MF_NATIVE, ", native", "native");
ADD_FLAG(MF_FORK, ", forked", "forked");
if (flag) {
strcat(buf, ": ");
strcat(buf, flags);
}
return buf;
}
void blank_and_print_obj(char * what, Obj * obj) {
register int x;
static Int len = 0;
Number_buf nbuf;
char * sn;
/* white out what we just printed */
for (x=len; x; x--)
fputc('\b', stdout);
fputs("\b\b\b\b", stdout);
for (x=len; x; x--)
fputc(' ', stdout);
fputs(" \r", stdout);
/* let them know whats up now */
fputs(what, stdout);
if (obj->objname == NOT_AN_IDENT) {
sn = long_to_ascii(obj->objnum, nbuf);
fputc('#', stdout);
} else {
sn = ident_name(obj->objname);
fputc('$', stdout);
}
fputs(sn, stdout);
fputs("...", stdout);
/* flush */
fflush(stdout);
len = strlen(sn);
}
/* the idea is to do this on strings that may be VERY large */
/* len MUST be more than 4 */
char * strchop(char * str, Int len) {
register int x;
for (x=0; x < len; x++) {
if (str[x] == (char) NULL)
return (char) NULL;
}
/* null terminate it and put an elipse in */
str[x] = (char) NULL;
str[x-1] = str[x-2] = str[x-3] = '.';
return str;
}