/
MOO-1.8.0p5/
/******************************************************************************
  Copyright (c) 1992, 1995, 1996 Xerox Corporation.  All rights reserved.
  Portions of this code were written by Stephen White, aka ghond.
  Use and copying of this software and preparation of derivative works based
  upon this software are permitted.  Any distribution of this software or
  derivative works must comply with all applicable United States export
  control laws.  This software is made available AS IS, and Xerox Corporation
  makes no warranty about the software, its performance or its conformity to
  any specification.  Any person obtaining a copy of this software is requested
  to send their name and post office or electronic mail address to:
    Pavel Curtis
    Xerox PARC
    3333 Coyote Hill Rd.
    Palo Alto, CA 94304
    Pavel@Xerox.Com
 *****************************************************************************/

#include "my-string.h"

#include "config.h"
#include "db.h"
#include "exceptions.h"
#include "execute.h"
#include "functions.h"
#include "list.h"
#include "log.h"
#include "match.h"
#include "parse_cmd.h"
#include "parser.h"
#include "server.h"
#include "storage.h"
#include "unparse.h"
#include "utils.h"
#include "verbs.h"

struct verb_data {
    Var	r;
    int	i;
};

static int
add_to_list(void *data, const char *verb_name)
{
    struct verb_data       *d = data;

    d->i++;
    d->r.v.list[d->i].type = TYPE_STR;
    d->r.v.list[d->i].v.str = str_ref(verb_name);

    return 0;
}

static package
bf_verbs(Var arglist, Byte next, void *vdata, Objid progr)
{ /* (object) */
    Objid	oid = arglist.v.list[1].v.obj;

    free_var(arglist);

    if (!valid(oid))
	return make_error_pack(E_INVARG);
    else if (!db_object_allows(oid, progr, FLAG_READ))
	return make_error_pack(E_PERM);
    else {
	struct verb_data	d;
	
	d.r = new_list(db_count_verbs(oid));
	d.i = 0;
	db_for_all_verbs(oid, add_to_list, &d);

	return make_var_pack(d.r);
    }
}

static enum error
validate_verb_info(Var v, Objid *owner, unsigned *flags, const char **names)
{
    const char *s;

    if (!(v.type = TYPE_LIST
	  && v.v.list[0].v.num == 3
	  && v.v.list[1].type == TYPE_OBJ
	  && v.v.list[2].type == TYPE_STR
	  && v.v.list[3].type == TYPE_STR))
	return E_TYPE;

    *owner = v.v.list[1].v.obj;
    if (!valid(*owner))
	return E_INVARG;

    for (*flags = 0, s = v.v.list[2].v.str; *s; s++) {
	switch (*s) {
	  case 'r': case 'R': *flags |= VF_READ;  break;
	  case 'w': case 'W': *flags |= VF_WRITE; break;
	  case 'x': case 'X': *flags |= VF_EXEC; break;
	  case 'd': case 'D': *flags |= VF_DEBUG; break;
	  default:
	    return E_INVARG;
	}
    }

    *names = v.v.list[3].v.str;
    while (**names == ' ')
	(*names)++;
    if (**names == '\0')
	return E_INVARG;

    *names = str_dup(*names);

    return E_NONE;
}

static int
match_arg_spec(const char *s, db_arg_spec *spec)
{
    if (!mystrcasecmp(s, "none")) {
	*spec = ASPEC_NONE;
	return 1;
    } else if (!mystrcasecmp(s, "any")) {
	*spec = ASPEC_ANY;
	return 1;
    } else if (!mystrcasecmp(s, "this")) {
	*spec = ASPEC_THIS;
	return 1;
    } else
	return 0;
}

static int
match_prep_spec(const char *s, db_prep_spec *spec)
{
    if (!mystrcasecmp(s, "none")) {
	*spec = PREP_NONE;
	return 1;
    } else if (!mystrcasecmp(s, "any")) {
	*spec = PREP_ANY;
	return 1;
    } else
	return (*spec = db_match_prep(s)) != PREP_NONE;
}

static enum error
validate_verb_args(Var v, db_arg_spec *dobj, db_prep_spec *prep,
		   db_arg_spec *iobj)
{
    if (!(v.type = TYPE_LIST
	  && v.v.list[0].v.num == 3
	  && v.v.list[1].type == TYPE_STR
	  && v.v.list[2].type == TYPE_STR
	  && v.v.list[3].type == TYPE_STR))
	return E_TYPE;

    if (!match_arg_spec(v.v.list[1].v.str, dobj)
	|| !match_prep_spec(v.v.list[2].v.str, prep)
	|| !match_arg_spec(v.v.list[3].v.str, iobj))
	return E_INVARG;

    return E_NONE;
}

static package
bf_add_verb(Var arglist, Byte next, void *vdata, Objid progr) 
{ /* (object, info, args) */
    Objid		oid = arglist.v.list[1].v.obj;
    Var			info = arglist.v.list[2];
    Var			args = arglist.v.list[3];
    Objid		owner;
    unsigned		flags;
    const char	       *names;
    db_arg_spec		dobj, iobj;
    db_prep_spec	prep;
    enum error		e;

    if ((e = validate_verb_info(info, &owner, &flags, &names)) != E_NONE)
	;			/* Already failed */
    else if ((e = validate_verb_args(args, &dobj, &prep, &iobj)) != E_NONE)
	free_str(names);
    else if (!valid(oid)) {
	free_str(names);
	e = E_INVARG;
    } else if (!db_object_allows(oid, progr, FLAG_WRITE)
	       || (progr != owner  &&  !is_wizard(progr))) {
	free_str(names);
	e = E_PERM;
    } else
	db_add_verb(oid, names, owner, flags, dobj, prep, iobj);

    free_var(arglist);
    if (e == E_NONE)
	return no_var_pack();
    else
	return make_error_pack(e);
}

enum error
validate_verb_descriptor(Var desc)
{
    if (desc.type == TYPE_STR)
	return E_NONE;
    else if (desc.type != TYPE_INT)
	return E_TYPE;
    else if (desc.v.num <= 0)
	return E_INVARG;
    else
	return E_NONE;
}

db_verb_handle
find_described_verb(Objid oid, Var desc)
{
    if (desc.type == TYPE_INT)
	return db_find_indexed_verb(oid, desc.v.num);
    else {
	int flag = server_flag_option("support_numeric_verbname_strings");
	
	return db_find_defined_verb(oid, desc.v.str, flag);
    }
}

static package
bf_delete_verb(Var arglist, Byte next, void *vdata, Objid progr)
{ /* (object, verb-desc) */
    Objid		oid = arglist.v.list[1].v.obj;
    Var			desc = arglist.v.list[2];
    db_verb_handle	h;
    enum error		e;

    if ((e = validate_verb_descriptor(desc)) != E_NONE)
	;			/* Do nothing; e is already set. */
    else if (!valid(oid))
	e = E_INVARG;
    else if (!db_object_allows(oid, progr, FLAG_WRITE))
	e = E_PERM;
    else {
	h = find_described_verb(oid, desc);
	if (h.ptr)
	    db_delete_verb(h);
	else
	    e = E_VERBNF;
    }

    free_var(arglist);
    if (e == E_NONE)
	return no_var_pack();
    else
	return make_error_pack(e);
}

static package
bf_verb_info(Var arglist, Byte next, void *vdata, Objid progr)
{ /* (object, verb-desc) */
    Objid		oid = arglist.v.list[1].v.obj;
    Var			desc = arglist.v.list[2];
    db_verb_handle	h;
    Var			r;
    unsigned		flags;
    char	       *s;
    enum error		e;

    if ((e = validate_verb_descriptor(desc)) != E_NONE
	|| (e = E_INVARG, !valid(oid))) {
	free_var(arglist);
	return make_error_pack(e);
    }

    h = find_described_verb(oid, desc);
    free_var(arglist);

    if (!h.ptr)
	return make_error_pack(E_VERBNF);
    else if (!db_verb_allows(h, progr, VF_READ))
	return make_error_pack(E_PERM);

    r = new_list(3);
    r.v.list[1].type = TYPE_OBJ;
    r.v.list[1].v.obj = db_verb_owner(h);
    r.v.list[2].type = TYPE_STR;
    r.v.list[2].v.str = s = str_dup("xxxx");
    flags = db_verb_flags(h);
    if (flags & VF_READ)  *s++ = 'r';
    if (flags & VF_WRITE) *s++ = 'w';
    if (flags & VF_EXEC)  *s++ = 'x';
    if (flags & VF_DEBUG)  *s++ = 'd';
    *s = '\0';
    r.v.list[3].type = TYPE_STR;
    r.v.list[3].v.str = str_ref(db_verb_names(h));

    return make_var_pack(r);
}

static package
bf_set_verb_info(Var arglist, Byte next, void *vdata, Objid progr)
{ /* (object, verb-desc, {owner, flags, names}) */
    Objid		oid = arglist.v.list[1].v.obj;
    Var			desc = arglist.v.list[2];
    Var			info = arglist.v.list[3];
    Objid		new_owner;
    unsigned		new_flags;
    const char	       *new_names;
    enum error		e;
    db_verb_handle	h;

    if ((e = validate_verb_descriptor(desc)) != E_NONE)
	;			/* Do nothing; e is already set. */
    else if (!valid(oid))
	e = E_INVARG;
    else
	e = validate_verb_info(info, &new_owner, &new_flags, &new_names);
    
    if (e != E_NONE) {
	free_var(arglist);
	return make_error_pack(e);
    }

    h = find_described_verb(oid, desc);
    free_var(arglist);

    if (!h.ptr) {
	free_str(new_names);
	return make_error_pack(E_VERBNF);
    } else if (!db_verb_allows(h, progr, VF_WRITE)
	     || (!is_wizard(progr)  &&  db_verb_owner(h) != new_owner)) {
	free_str(new_names);
	return make_error_pack(E_PERM);
    }

    db_set_verb_owner(h, new_owner);
    db_set_verb_flags(h, new_flags);
    db_set_verb_names(h, new_names);

    return no_var_pack();
}

static const char *
unparse_arg_spec(db_arg_spec spec)
{
    switch (spec) {
      case ASPEC_NONE:
	return str_dup("none");
      case ASPEC_ANY:
	return str_dup("any");
      case ASPEC_THIS:
	return str_dup("this");
      default:
	panic("UNPARSE_ARG_SPEC: Unknown db_arg_spec!");
	return "";
    }
}

static package
bf_verb_args(Var arglist, Byte next, void *vdata, Objid progr)
{ /* (object, verb-desc) */
    Objid		oid = arglist.v.list[1].v.obj;
    Var			desc = arglist.v.list[2];
    db_verb_handle	h;
    db_arg_spec		dobj, iobj;
    db_prep_spec	prep;
    Var			r;
    enum error		e;

    if ((e = validate_verb_descriptor(desc)) != E_NONE
	|| (e = E_INVARG, !valid(oid))) {
	free_var(arglist);
	return make_error_pack(e);
    }

    h = find_described_verb(oid, desc);
    free_var(arglist);

    if (!h.ptr)
	return make_error_pack(E_VERBNF);
    else if (!db_verb_allows(h, progr, VF_READ))
	return make_error_pack(E_PERM);

    db_verb_arg_specs(h, &dobj, &prep, &iobj);
    r = new_list(3);
    r.v.list[1].type = TYPE_STR;
    r.v.list[1].v.str = unparse_arg_spec(dobj);
    r.v.list[2].type = TYPE_STR;
    r.v.list[2].v.str = str_dup(db_unparse_prep(prep));
    r.v.list[3].type = TYPE_STR;
    r.v.list[3].v.str = unparse_arg_spec(iobj);

    return make_var_pack(r);
}

static package
bf_set_verb_args(Var arglist, Byte next, void *vdata, Objid progr)
{ /* (object, verb-desc, {dobj, prep, iobj}) */
    Objid		oid = arglist.v.list[1].v.obj;
    Var			desc = arglist.v.list[2];
    Var			args = arglist.v.list[3];
    enum error		e;
    db_verb_handle	h;
    db_arg_spec		dobj, iobj;
    db_prep_spec	prep;

    if ((e = validate_verb_descriptor(desc)) != E_NONE)
	;			/* Do nothing; e is already set. */
    else if (!valid(oid))
	e = E_INVARG;
    else
	e = validate_verb_args(args, &dobj, &prep, &iobj);

    if (e != E_NONE) {
	free_var(arglist);
	return make_error_pack(e);
    }

    h = find_described_verb(oid, desc);
    free_var(arglist);

    if (!h.ptr)
	return make_error_pack(E_VERBNF);
    else if (!db_verb_allows(h, progr, VF_WRITE))
	return make_error_pack(E_PERM);

    db_set_verb_arg_specs(h, dobj, prep, iobj);

    return no_var_pack();
}

static void
lister(void *data, const char *line)
{
    Var	       *r = (Var *) data;
    Var		v;
    
    v.type = TYPE_STR;
    v.v.str = str_dup(line);
    *r = listappend(*r, v);
}

static package
bf_verb_code(Var arglist, Byte next, void *vdata, Objid progr)
{ /* (object, verb-desc [, fully-paren [, indent]]) */
    int			nargs = arglist.v.list[0].v.num;
    Objid		oid = arglist.v.list[1].v.obj;
    Var			desc = arglist.v.list[2];
    int			parens = nargs >= 3 && is_true(arglist.v.list[3]);
    int 		indent = nargs < 4 || is_true(arglist.v.list[4]);
    db_verb_handle	h;
    Var			code;
    enum error		e;

    if ((e = validate_verb_descriptor(desc)) != E_NONE
	|| (e = E_INVARG, !valid(oid))) {
	free_var(arglist);
	return make_error_pack(e);
    }

    h = find_described_verb(oid, desc);
    free_var(arglist);
    
    if (!h.ptr)
	return make_error_pack(E_VERBNF);
    else if (!db_verb_allows(h, progr, VF_READ))
	return make_error_pack(E_PERM);

    code = new_list(0);
    unparse_program(db_verb_program(h), lister, &code, parens, indent,
		    MAIN_VECTOR);

    return make_var_pack(code);
}

static package
bf_set_verb_code(Var arglist, Byte next, void *vdata, Objid progr) 
{ /* (object, verb-desc, code) */
    Objid		oid = arglist.v.list[1].v.obj;
    Var			desc = arglist.v.list[2];
    Var			code = arglist.v.list[3];
    int			i;
    Program	       *program;
    db_verb_handle	h;
    Var			errors;
    enum error		e;

    for (i = 1; i <= code.v.list[0].v.num; i++)
	if (code.v.list[i].type != TYPE_STR) {
	    free_var(arglist);
	    return make_error_pack(E_TYPE);
	}

    if ((e = validate_verb_descriptor(desc)) != E_NONE
	|| (e = E_INVARG, !valid(oid))) {
	free_var(arglist);
	return make_error_pack(e);
    }

    h = find_described_verb(oid, desc);
    if (!h.ptr) {
	free_var(arglist);
	return make_error_pack(E_VERBNF);
    } else if (!is_programmer(progr) || !db_verb_allows(h, progr, VF_WRITE)) {
	free_var(arglist);
	return make_error_pack(E_PERM);
    }

    program = parse_list_as_program(code, &errors);
    if (program) {
	if (task_timed_out)
	    free_program(program);
	else
	    db_set_verb_program(h, program);
    }

    free_var(arglist);
    return make_var_pack(errors);
}

static package
bf_eval(Var arglist, Byte next, void *data, Objid progr)
{
    package p;
    if (next == 1) {
	
	if (!is_programmer(progr)) {
	    free_var(arglist);
	    p = make_error_pack(E_PERM);
	} else {
	    Var		errors;
	    Program    *program = parse_list_as_program(arglist, &errors);
	    
	    free_var(arglist);
	    if (program) {
		free_var(errors);
		if (setup_activ_for_eval(program))
		    p = make_call_pack(2, 0);
		else {
		    free_program(program);
		    p = make_error_pack(E_MAXREC);
		}
	    } else {
		Var r;
		
		r = new_list(2);
		r.v.list[1].type = TYPE_INT;
		r.v.list[1].v.num = 0;
		r.v.list[2] = errors;
		p = make_var_pack(r);
	    }
	}
    } else { /* next == 2 */
	Var r;
	
	r = new_list(2);
	r.v.list[1].type = TYPE_INT;
	r.v.list[1].v.num = 1;
	r.v.list[2] = arglist; 
	p = make_var_pack(r);
    }
    return p;
}

void
register_verbs(void)
{ 
    register_function("verbs", 1, 1, bf_verbs, TYPE_OBJ);
    register_function("verb_info", 2, 2, bf_verb_info, TYPE_OBJ, TYPE_ANY);
    register_function("set_verb_info", 3, 3, bf_set_verb_info,
			     TYPE_OBJ, TYPE_ANY, TYPE_LIST);
    register_function("verb_args", 2, 2, bf_verb_args, TYPE_OBJ, TYPE_ANY);
    register_function("set_verb_args", 3, 3, bf_set_verb_args,
			     TYPE_OBJ, TYPE_ANY, TYPE_LIST);
    register_function("add_verb", 3, 3, bf_add_verb,
			     TYPE_OBJ, TYPE_LIST, TYPE_LIST);
    register_function("delete_verb", 2, 2, bf_delete_verb, TYPE_OBJ, TYPE_ANY);
    register_function("verb_code", 2, 4, bf_verb_code,
			     TYPE_OBJ, TYPE_ANY, TYPE_ANY, TYPE_ANY);
    register_function("set_verb_code", 3, 3, bf_set_verb_code,
			     TYPE_OBJ, TYPE_ANY, TYPE_LIST);
    register_function("eval", 1, 1, bf_eval, TYPE_STR);
}

char rcsid_verbs[] = "$Id: verbs.c,v 2.8 1996/05/12 21:29:46 pavel Exp $";

/* $Log: verbs.c,v $
 * Revision 2.8  1996/05/12  21:29:46  pavel
 * Fixed memory leak for verb names string in bf_add_verb.  Release 1.8.0p5.
 *
 * Revision 2.7  1996/03/19  07:19:13  pavel
 * Fixed off-by-one bug in type-checking in set_verb_code().  Release 1.8.0p2.
 *
 * Revision 2.6  1996/02/08  06:39:44  pavel
 * Renamed TYPE_NUM to TYPE_INT.  Updated copyright notice for 1996.
 * Release 1.8.0beta1.
 *
 * Revision 2.5  1996/01/16  07:29:04  pavel
 * Fixed `parens' and `indent' arguments to verb_code() to allow values of any
 * type.  Release 1.8.0alpha6.
 *
 * Revision 2.4  1996/01/11  07:51:42  pavel
 * Fixed bad free() in bf_add_verb().  Release 1.8.0alpha5.
 *
 * Revision 2.3  1995/12/31  03:28:27  pavel
 * Fixed C syntax botch in bf_delete_verb().  Release 1.8.0alpha4.
 *
 * Revision 2.2  1995/12/28  00:25:40  pavel
 * Added support for using numbers to designate defined verbs in built-in
 * functions.  Release 1.8.0alpha3.
 *
 * Revision 2.1  1995/12/11  07:53:59  pavel
 * Accounted for verb programs never being NULL any more.
 *
 * Release 1.8.0alpha2.
 *
 * Revision 2.0  1995/11/30  04:43:36  pavel
 * New baseline version, corresponding to release 1.8.0alpha1.
 *
 * Revision 1.16  1992/10/23  23:03:47  pavel
 * Added copyright notice.
 *
 * Revision 1.15  1992/10/23  22:23:43  pavel
 * Eliminated all uses of the useless macro NULL.
 *
 * Revision 1.14  1992/10/21  03:02:35  pavel
 * Converted to use new automatic configuration system.
 *
 * Revision 1.13  1992/10/17  20:58:25  pavel
 * Global rename of strdup->str_dup, strref->str_ref, vardup->var_dup, and
 * varref->var_ref.
 *
 * Revision 1.12  1992/10/06  18:25:57  pavel
 * Changed name of global Parser_Client to avoid a name clash.
 *
 * Revision 1.11  1992/09/24  16:44:38  pavel
 * Made the parsing done by eval() and set_verb_code() abort if the task runs
 * out of seconds.
 *
 * Revision 1.10  1992/09/21  17:38:05  pavel
 * Restored RCS log information.
 *
 * Revision 1.9  1992/09/14  18:41:15  pjames
 * Moved rcsid to bottom.
 * Changed bf_eval to avoid a compiler warning when compiled un-optimized.
 *
 * Revision 1.8  1992/09/14  17:31:23  pjames
 * Moved db_modification code to db modules.
 *
 * Revision 1.7  1992/09/08  22:00:18  pjames
 * Renambed bf_verb.c to verbs.c
 *
 * Revision 1.6  1992/08/31  22:29:24  pjames
 * Changed some `char *'s to `const char *'
 *
 * Revision 1.5  1992/08/28  16:16:14  pjames
 * Changed myfree(*, M_STRING) to free_str(*).
 * Changed some strref's to strdup.
 *
 * Revision 1.4  1992/08/21  00:42:59  pavel
 * Renamed include file "parse_command.h" to "parse_cmd.h".
 *
 * Revision 1.3  1992/08/10  17:21:32  pjames
 * Updated #includes.  Updated to use new registration format.  Built in
 * functions now only receive programmer, instead of entire Parse_Info.
 *
 * Revision 1.2  1992/07/20  23:55:24  pavel
 * Added rcsid_<filename-root> declaration to hold the RCS ident. string.
 *
 * Revision 1.1  1992/07/20  23:23:12  pavel
 * Initial RCS-controlled version.
 */