mirror of
git://git.psyced.org/git/psyclpc
synced 2024-08-15 03:20:16 +00:00
9358 lines
260 KiB
C
9358 lines
260 KiB
C
/*
|
|
* Storing objects:
|
|
*
|
|
* It works to have their names in the .o files surrounded by backticks.
|
|
* On restore we can convert them to a string.
|
|
*
|
|
* Alternatives:
|
|
* Attempting to restore objects themselves results in heavy recursions.
|
|
* Assigning them only when they are already loaded, kind of works,
|
|
* but I made some mistake that causes occasional cores.
|
|
* Maybe it's because I count the sizes by string length, and then
|
|
* I don't really put a string inside..
|
|
*
|
|
* However, should you want to have objects saved as string name
|
|
* rather then 0 or even removed (keys in mappings etc.), you can safely
|
|
* activate USE_RESTORED_OBJECTS.
|
|
*
|
|
* Without it, psyclpc behaves like LDMud always did. No changes.
|
|
*/
|
|
|
|
/*---------------------------------------------------------------------------
|
|
* Object handling.
|
|
*
|
|
*---------------------------------------------------------------------------
|
|
* Objects are the core of LPMud: everything a players sees, handles and
|
|
* is, is an object. Unfortunately this also means that a lot of object
|
|
* related functions are scattered all over the driver, so this file is
|
|
* actually quite short.
|
|
*
|
|
* The first part deals with the creation and deallocation of objects
|
|
* and programs. The second parts implements a pointer table which can
|
|
* be used to sequentialize the set of arrays and mappings, detecting
|
|
* cycles and shared usages while doing this. The last and largest part
|
|
* are the functions to save an object into a save file, and restoring
|
|
* it from there.
|
|
*
|
|
* -- object_t --
|
|
*
|
|
* object_t {
|
|
* unsigned short flags;
|
|
* p_int ref;
|
|
#ifdef USE_SET_LIGHT
|
|
* short total_light;
|
|
#endif
|
|
* mp_int time_reset;
|
|
* mp_int time_of_ref;
|
|
* mp_int time_cleanup;
|
|
* mp_int load_time;
|
|
* p_int load_id;
|
|
* p_int extra_ref; (ifdef DEBUG)
|
|
* program_t * prog;
|
|
* string_t * name;
|
|
* string_t * load_name;
|
|
* object_t * next_all;
|
|
* object_t * prev_all;
|
|
* object_t * next_hash;
|
|
* object_t * next_inv;
|
|
* object_t * contains;
|
|
* object_t * super;
|
|
* TODO: The environment members (plus light) could be put into a special
|
|
* TODO:: sentence and thus concentrated in a separated source file.
|
|
* sentence_t * sent;
|
|
* wiz_list_t * user;
|
|
* wiz_list_t * eff_user;
|
|
* Bool open_sqlite_db (ifdef USE_SQLITE)
|
|
* int extra_num_variables; (ifdef DEBUG)
|
|
* svalue_t * variables;
|
|
* unsigned long ticks, gigaticks;
|
|
* }
|
|
*
|
|
* The .flags collect some vital information about the object:
|
|
* O_HEART_BEAT : the object has a heartbeat
|
|
#ifdef USE_SET_IS_WIZARD
|
|
* O_IS_WIZARD : the object is a 'wizard' - this bit is set with
|
|
* the efun set_is_wizard()
|
|
#endif
|
|
* O_ENABLE_COMMANDS : can execute commands ("is a living")
|
|
* O_CLONE : is a clone, or uses a replaced program
|
|
* O_DESTRUCTED : has actually been destructed
|
|
* O_SWAPPED : program and/or variables have been swapped out
|
|
* O_ONCE_INTERACTIVE : is or was interactive
|
|
* O_RESET_STATE : is in a virgin resetted state
|
|
* O_WILL_CLEAN_UP : call clean_up() when time is due
|
|
* O_LAMBDA_REFERENCED: a reference to a lambda was taken; this may
|
|
* inhibit a replace_program().
|
|
* O_SHADOW : object is shadowed
|
|
* O_REPLACED : program was replaced.
|
|
*
|
|
* .ref counts the number of references to this object: it is this count
|
|
* which can keep a destructed object around. Destructed objects are
|
|
* stripped of everything but the basic object_t, but this one
|
|
* is kept until the last reference is gone.
|
|
*
|
|
* .time_of_ref is the time() of the last apply on this object. The swapper
|
|
* uses this timestamp to decide whether to swap the object or not.
|
|
*
|
|
* .time_cleanup is the time() when the next variable cleanup is due.
|
|
*
|
|
* Similar, .time_reset is the time() when the object should be reset
|
|
* again. A time of 0 means: never.
|
|
* The timing is not strict: any time after the given time is
|
|
* sufficient. A reset object has its O_RESET_STATE flag set, which is
|
|
* reset in an apply. If the time of reset is reached, but the object
|
|
* is still in a reset state, or it is swapped out, the backend simply
|
|
* sets a new .time_reset time, but does not do any real action.
|
|
* To reduce the lag caused by the reset calls, all objects are kept
|
|
* in the reset_table sorted by their time_reset. The .next_reset pointer
|
|
* is used to built the table.
|
|
*
|
|
* .load_time simply is the time when the object was created. .load_id
|
|
* serves to determine the creation order of objects created at the
|
|
* same .load_time - this is used mostly for efuns like clones().
|
|
*
|
|
* .prog is a pointer to the program_tm, the bunch of bytecode for
|
|
* this object. The program is shared between the master object (the
|
|
* blueprint) and its clones. It is possible to replace the program
|
|
* of a single object with a different one, but special care has
|
|
* to be taken if lambda closures have been created.
|
|
*
|
|
* .variables is the block of variables of this object. Obviously they
|
|
* can't be shared between master and clones. The number of variables
|
|
* implicitely known by the program.
|
|
*
|
|
* .name and .load_name are the two names of the object. .name (an untabled
|
|
* string) is the objects 'real' name: something like "std/thing" for
|
|
* a blueprint, and "std/thing#45" for a clone. This name never has
|
|
* a leading '/'. However, this name can be changed with the efun
|
|
* rename_object().
|
|
*
|
|
* The .load_name (a tabled string) is the name of the file from which
|
|
* the object was created. It is identical in both blueprint and clones
|
|
* (in our example "/std/thing") and can't be changed. In compat mode,
|
|
* this name has no leading '/'. However, for virtual objects .load_name
|
|
* is the virtual name - the real program name is .prog->name.
|
|
*
|
|
* Both .name and .load_name never contain a '\0' as part of the name.
|
|
*
|
|
* .sent is the list of annotations to the object. Primary use is to
|
|
* hold the list of commands ("sentences") defined by this object. Just
|
|
* the first entry in this list has a special role: if the object is
|
|
* shadowed, interactive, or using the editor, it is a "shadow_t"
|
|
* and keeps the list of shadows resp. the other information.
|
|
*
|
|
* .user points to the wizlist entry of the wizard who 'owns' this
|
|
* object. The entry is used to collect several stats for this user.
|
|
* .eff_user describes the rights of this object. .eff_user can be
|
|
* NULL, while .user can't.
|
|
*
|
|
* .ticks and .gigaticks count how much time the interpreter spent
|
|
* in this particular object. The number is kept in two variables
|
|
* to prevent overflows, it can be computed as gigaticks * 1E9 + ticks.
|
|
*
|
|
* .next_all, .prev_all and .next_hash are used to store the object.
|
|
* .next_all and .prev_all are the link pointers in the list of all
|
|
* objects, .next_hash is the link pointer in the object table (see otable.c).
|
|
*
|
|
* The gamedriver implements an environment/inventory system. .super
|
|
* points to an object's surrounding object (and can be NULL), .contains
|
|
* is the head of the list of contained objects. This inventory list
|
|
* is linked by the .next_inv pointer.
|
|
*
|
|
* Related to the environment system is .total_light, which gives
|
|
* total light emitted by the object including all its inventory. The
|
|
* system is very crude and hardly used anymore. There it is completely
|
|
* deactivated if the efun set_light() is not defined.
|
|
*
|
|
* .extra_ref and .extra_num_variables are used by check_a_lot_of_refcounts().
|
|
*
|
|
*
|
|
* A word about swapping: when an object is not in use for longer time,
|
|
* the driver swaps out the program and/or the objects variables, and the
|
|
* swapper assigns an even 'swapnum' each. If an object is swapped, the
|
|
* O_SWAPPED flag is set and the affected pointer (.prog or .variables)
|
|
* is replaced by the assigned swap_num _with the lowest bit set_. Since
|
|
* pointers are assumed to always be even, this allows to distinguish
|
|
* swapped programs/variables from unswapped ones.
|
|
*
|
|
* The exact structure of programs and variables is explained in exec.h .
|
|
*---------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "driver.h"
|
|
#include "typedefs.h"
|
|
|
|
#include "my-alloca.h"
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
|
|
#include "object.h"
|
|
|
|
#include "actions.h"
|
|
#include "array.h"
|
|
#include "backend.h"
|
|
#include "closure.h"
|
|
#include "comm.h"
|
|
#include "filestat.h"
|
|
#include "interpret.h"
|
|
#include "instrs.h"
|
|
#include "lex.h"
|
|
#include "main.h"
|
|
#include "mapping.h"
|
|
#include "mempools.h"
|
|
#include "mstrings.h"
|
|
#include "otable.h"
|
|
#include "prolang.h"
|
|
#include "ptrtable.h"
|
|
#include "random.h"
|
|
#include "sent.h"
|
|
#include "simulate.h"
|
|
#include "simul_efun.h"
|
|
#include "stdstrings.h"
|
|
#include "strfuns.h"
|
|
#ifdef USE_STRUCTS
|
|
#include "structs.h"
|
|
#endif /* USE_STRUCTS */
|
|
#include "swap.h"
|
|
#include "svalue.h"
|
|
#include "wiz_list.h"
|
|
#include "xalloc.h"
|
|
|
|
#include "../mudlib/sys/driver_hook.h"
|
|
#include "../mudlib/sys/functionlist.h"
|
|
#include "../mudlib/sys/include_list.h"
|
|
#include "../mudlib/sys/inherit_list.h"
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
replace_ob_t *obj_list_replace = NULL;
|
|
/* List of scheduled program replacements.
|
|
*/
|
|
|
|
long tot_alloc_object = 0;
|
|
long tot_alloc_object_size = 0;
|
|
/* Total number of allocated object, and the sum of memory they use.
|
|
*/
|
|
|
|
object_t NULL_object = { 0 };
|
|
/* static null object for initialisations. memset() is not sufficient
|
|
* because some machines (e.g. Bull) have a (char*)0 which is not
|
|
* binary zero. Structure assignment otoh works.
|
|
*/
|
|
|
|
Bool dest_last_ref_gone = MY_FALSE;
|
|
/* This flag is set to TRUE if the second-to-last reference to
|
|
* a destructed is removed, as this usually means that it can be removed
|
|
* from the list of destructed objects and deallocated altogether.
|
|
* The flag is used to avoid unnecessary scans of the list of destructed
|
|
* objects by the backend in the meantime.
|
|
*/
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
#ifndef CHECK_OBJECT_REF
|
|
void
|
|
dealloc_object (object_t *ob)
|
|
#else
|
|
void
|
|
dealloc_object ( object_t *ob, const char * file, int line)
|
|
#endif
|
|
|
|
/* Deallocate/dereference all memory and structures held by <ob>.
|
|
* At the time of call, the object must be have at no refcount left,
|
|
* must be destructed and removed from the object table and lists.
|
|
*/
|
|
|
|
{
|
|
#ifdef DEBUG
|
|
|
|
/* Check the reference count */
|
|
|
|
if (ob->ref > 0)
|
|
fatal("Object with %"PRIdPINT" refs passed to _free_object()\n",
|
|
ob->ref);
|
|
|
|
#if 0 && defined(CHECK_OBJECT_REF)
|
|
if (strchr(get_txt(ob->name), '#') == NULL)
|
|
printf("DEBUG: (%s:%d) free_object(%p '%s') ref %"PRIdPINT" flags %x\n"
|
|
, file, line, ob, get_txt(ob->name), ob->ref, ob->flags);
|
|
#elif defined(CHECK_OBJECT_REF)
|
|
# ifdef __MWERKS__
|
|
# pragma unused(file)
|
|
# pragma unused(line)
|
|
# endif
|
|
#endif
|
|
#ifdef DEBUG_REFS
|
|
if (d_flag)
|
|
printf("%s free_object: %s.\n", time_stamp(), get_txt(ob->name));
|
|
#endif
|
|
|
|
/* Freeing a non-destructed object should never happen */
|
|
|
|
if (!(ob->flags & O_DESTRUCTED)) {
|
|
fatal("Object %p %s ref count 0, but not destructed.\n"
|
|
, ob, get_txt(ob->name));
|
|
}
|
|
|
|
#endif /* DEBUG */
|
|
|
|
if (ob->sent)
|
|
fatal("free_object: Object '%s' (ref %"PRIdPINT", flags %08x) "
|
|
"still has sentences.\n"
|
|
, get_txt(ob->name), ob->ref, ob->flags);
|
|
|
|
/* If the program is freed, then we can also free the variable
|
|
* declarations.
|
|
*/
|
|
if (ob->prog)
|
|
{
|
|
program_t *prog = ob->prog;
|
|
#ifdef CHECK_OBJECT_STAT
|
|
if (check_object_stat)
|
|
{
|
|
fprintf(stderr, "DEBUG: OSTAT: (%ld:%ld) free( %p '%s') with %hu vars : %"PRIuPINT" -> (%ld:%ld)\n"
|
|
, tot_alloc_object, tot_alloc_object_size, ob, ob->name ? get_txt(ob->name) : "<null>"
|
|
, prog->num_variables
|
|
, (p_uint)(prog->num_variables * sizeof (svalue_t) + sizeof (object_t))
|
|
, tot_alloc_object
|
|
, (tot_alloc_object_size - (prog->num_variables * sizeof (svalue_t) + sizeof (object_t)))
|
|
);
|
|
}
|
|
#endif
|
|
tot_alloc_object_size -=
|
|
prog->num_variables * sizeof (svalue_t) +
|
|
sizeof (object_t);
|
|
free_prog(prog, MY_TRUE);
|
|
ob->prog = NULL;
|
|
}
|
|
#ifdef CHECK_OBJECT_STAT
|
|
else
|
|
{
|
|
if (check_object_stat)
|
|
{
|
|
fprintf(stderr, "DEBUG: OSTAT: (%ld:%ld) free( %p '%s') has no program\n"
|
|
, tot_alloc_object, tot_alloc_object_size, ob, ob->name ? get_txt(ob->name) : "<null>");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Deallocate the name */
|
|
if (ob->name)
|
|
{
|
|
if (d_flag > 1)
|
|
debug_message("%s Free object %s\n", time_stamp(), get_txt(ob->name));
|
|
if (lookup_object_hash(ob->name) == ob)
|
|
fatal("Freeing object %s but name still in name table\n"
|
|
, get_txt(ob->name));
|
|
#ifdef CHECK_OBJECT_STAT
|
|
if (check_object_stat)
|
|
{
|
|
fprintf(stderr, "DEBUG: OSTAT: (%ld:%ld) free( %p '%s') with name : %zu -> (%ld:%ld)\n"
|
|
, tot_alloc_object, tot_alloc_object_size, ob, get_txt(ob->name)
|
|
, mstrsize(ob->name)
|
|
, tot_alloc_object-1
|
|
, tot_alloc_object_size - (mstrsize(ob->name)+1)
|
|
);
|
|
}
|
|
#endif
|
|
tot_alloc_object_size -= mstrsize(ob->name);
|
|
free_mstring(ob->name);
|
|
ob->name = NULL;
|
|
}
|
|
#ifdef CHECK_OBJECT_STAT
|
|
else
|
|
{
|
|
if (check_object_stat)
|
|
{
|
|
fprintf(stderr, "DEBUG: OSTAT: (%ld:%ld) free( %p ) has no name -> (%ld:%ld)\n"
|
|
, tot_alloc_object, tot_alloc_object_size, ob
|
|
, tot_alloc_object-1
|
|
, tot_alloc_object_size);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Dereference the load_name */
|
|
if (ob->load_name)
|
|
{
|
|
free_mstring(ob->load_name);
|
|
ob->load_name = NULL;
|
|
}
|
|
|
|
/* Free the object structure */
|
|
tot_alloc_object--;
|
|
xfree(ob);
|
|
} /* dealloc_object() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
object_t *
|
|
get_empty_object (int num_var)
|
|
|
|
/* Allocate a new, empty object with <numvar> variables (set to 0)
|
|
* and return it.
|
|
* Return NULL when out of memory.
|
|
*/
|
|
|
|
{
|
|
static mp_int last_time = 0;
|
|
static mp_int last_id = 0;
|
|
|
|
object_t *ob;
|
|
size_t size = sizeof (object_t);
|
|
size_t size2 = num_var * sizeof (svalue_t);
|
|
int i;
|
|
svalue_t *ob_vars;
|
|
|
|
/* Allocate the object structure */
|
|
|
|
if ( !(ob = xalloc(size)) )
|
|
return NULL;
|
|
|
|
ob_vars = NULL;
|
|
|
|
/* Allocated the variable block */
|
|
|
|
if (size2 && !(ob_vars = xalloc(size2)) )
|
|
{
|
|
xfree(ob);
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef CHECK_OBJECT_STAT
|
|
if (check_object_stat)
|
|
{
|
|
fprintf(stderr, "DEBUG: OSTAT: (%ld:%ld) new( %p ) with %d vars : %zu -> (%ld:%"PRIuPINT")\n"
|
|
, tot_alloc_object, tot_alloc_object_size, ob
|
|
, num_var
|
|
, (size2+size)
|
|
, tot_alloc_object+1
|
|
, (p_uint)tot_alloc_object_size + size + size2
|
|
);
|
|
}
|
|
#endif
|
|
tot_alloc_object++;
|
|
tot_alloc_object_size += size + size2;
|
|
|
|
/* Clear and initialise the object (no memset!) */
|
|
|
|
*ob = NULL_object;
|
|
ob->ref = 1;
|
|
ob->load_time = current_time;
|
|
if (last_time == current_time)
|
|
ob->load_id = ++last_id;
|
|
else
|
|
{
|
|
ob->load_id = last_id = 0;
|
|
last_time = current_time;
|
|
}
|
|
#ifdef USE_PARANOIA
|
|
ob->extra_num_variables = num_var;
|
|
#endif
|
|
#ifdef USE_SQLITE
|
|
ob->open_sqlite_db = MY_FALSE;
|
|
#endif
|
|
#ifdef USE_EXPAT
|
|
ob->xml_parser = NULL;
|
|
ob->xml_error = MY_FALSE;
|
|
#endif
|
|
ob->variables = ob_vars;
|
|
|
|
ob->time_cleanup = current_time + ((time_to_cleanup > 0) ? time_to_cleanup
|
|
: 3600);
|
|
|
|
/* Initialize the variables */
|
|
|
|
for (i = num_var; --i >= 0; )
|
|
{
|
|
ob_vars[i] = const0;
|
|
}
|
|
|
|
/* That's it. */
|
|
return ob;
|
|
} /* get_empty_object() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
void
|
|
init_object_variables (object_t *ob, object_t *templ)
|
|
|
|
/* The variables of object <ob> are initialized.
|
|
*
|
|
* First, if <ob> is a clone, all variables marked as !VAR_INITIALIZED are
|
|
* copied over from the <templ>, if given. <templ> MUST be the blueprint
|
|
* object.
|
|
*
|
|
* Then, for all <ob>, __INIT() is called in <ob> which initializes all
|
|
* the variables marked as VAR_INITIALIZED in clones, and all variables
|
|
* in blueprints.
|
|
*/
|
|
|
|
{
|
|
/* For clones, copy the shared variable values */
|
|
if ((ob->flags & O_CLONE))
|
|
{
|
|
int i;
|
|
variable_t *p_vars;
|
|
svalue_t *ob_vars, *templ_vars;
|
|
|
|
if (!templ || templ->flags & O_DESTRUCTED)
|
|
templ_vars = NULL;
|
|
else
|
|
templ_vars = templ->variables;
|
|
|
|
ob_vars = ob->variables;
|
|
p_vars = ob->prog->variables;
|
|
|
|
for (i = ob->prog->num_variables; --i >= 0; )
|
|
{
|
|
if (p_vars[i].type.typeflags & VAR_INITIALIZED)
|
|
continue;
|
|
if (!templ_vars)
|
|
errorf("Can't initialize object '%s': no blueprint given.\n"
|
|
, get_txt(ob->name));
|
|
assign_svalue_no_free(&ob_vars[i], &templ_vars[i]);
|
|
}
|
|
}
|
|
|
|
/* Initialized all other variables programmatically */
|
|
sapply_ign_prot(STR_VARINIT, ob, 0);
|
|
} /* init_object_variables() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
#ifdef DEALLOCATE_MEMORY_AT_SHUTDOWN
|
|
|
|
void
|
|
remove_all_objects (void)
|
|
|
|
/* Call destruct_object() for every object on the object list, then
|
|
* call remove_destructed_objects() to actually remove them.
|
|
*
|
|
* This function is called from simulate.c when the game is shut down.
|
|
*/
|
|
|
|
{
|
|
object_t *ob;
|
|
svalue_t v;
|
|
|
|
v.type = T_OBJECT;
|
|
for (ob = obj_list; ob; ob = ob->next_all)
|
|
{
|
|
#ifdef DEBUG
|
|
if (ob->flags & O_DESTRUCTED) /* TODO: Can't happen */
|
|
continue;
|
|
#endif
|
|
v.u.ob = ob;
|
|
destruct_object(&v);
|
|
if ( !(ob->flags & O_DESTRUCTED) )
|
|
break;
|
|
}
|
|
remove_destructed_objects(MY_TRUE);
|
|
}
|
|
|
|
#endif
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
void
|
|
reference_prog (program_t *progp, char *from)
|
|
|
|
/* Increment the refcount of program <progp>, called from location <from>.
|
|
*/
|
|
|
|
{
|
|
progp->ref++;
|
|
#ifdef DEBUG_REFS
|
|
if (d_flag)
|
|
printf("%s reference_prog: %s ref %"PRIdPINT" (%s)\n"
|
|
, time_stamp(), get_txt(progp->name), progp->ref, from);
|
|
#endif
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
void
|
|
do_free_sub_strings (int num_strings, string_t **strings
|
|
,int num_variables, variable_t *variables
|
|
,int num_includes, include_t *includes
|
|
#ifdef USE_STRUCTS
|
|
,int num_structs, struct_def_t *struct_defs
|
|
#endif /* USE_STRUCTS */
|
|
)
|
|
|
|
/* Free a bunch of shared strings used in connection with an object:
|
|
* the <num_strings> strings in the array <strings>,
|
|
* the <num_variables> names and type objects of the vars in array <variables>,
|
|
* the <num_includes> names of the includes in array <includes>,
|
|
#ifdef USE_STRUCTS
|
|
* the <num_structs> names of the struct defs in array <struct_defs>, and.
|
|
#endif
|
|
*
|
|
* The function is called from free_prog() and from the compiler epilog().
|
|
*/
|
|
|
|
{
|
|
int i;
|
|
|
|
/* Free all strings */
|
|
for (i = 0; i < num_strings; i++)
|
|
free_mstring(strings[i]);
|
|
|
|
/* Free all variable names and types */
|
|
for (i = num_variables; --i >= 0; )
|
|
{
|
|
free_mstring(variables[i].name);
|
|
free_fulltype_data(&variables[i].type);
|
|
}
|
|
|
|
/* Free all include names */
|
|
for (i = num_includes; --i >= 0; )
|
|
{
|
|
free_mstring(includes[i].name);
|
|
free_mstring(includes[i].filename);
|
|
}
|
|
|
|
#ifdef USE_STRUCTS
|
|
/* Free all struct names */
|
|
for (i = num_structs; --i >= 0; )
|
|
{
|
|
free_struct_type(struct_defs[i].type);
|
|
}
|
|
#endif /* USE_STRUCTS */
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
#ifndef CHECK_OBJECT_REF
|
|
void
|
|
free_prog (program_t *progp, Bool free_all)
|
|
#else
|
|
void
|
|
_free_prog (program_t *progp, Bool free_all, const char * file, int line
|
|
)
|
|
#endif
|
|
|
|
/* Decrement the refcount for program <progp>. If it reaches 0, the program
|
|
* is freed.
|
|
*
|
|
* If free_all is TRUE, all object strings and the blueprint reference are
|
|
* freed, and free_prog() is called for all inherited programs.
|
|
*
|
|
* The only case when free_all is not true, is, when the swapper
|
|
* swapped out the program and now attempts to free the memory.
|
|
* This means that the string data is kept in memory all the time.
|
|
* TODO: Swapping the strings is tricky, as they are all shared.
|
|
* TODO:: Maybe swap them together with the variables - this is costly
|
|
* TODO:: enough to make the lookup time needed when swapping in the
|
|
* TODO:: strings look small.
|
|
*/
|
|
|
|
{
|
|
/* Decrement the refcount */
|
|
|
|
progp->ref--;
|
|
if (progp->ref > 0)
|
|
return;
|
|
|
|
#if 0 && defined(CHECK_OBJECT_REF)
|
|
if (strchr(get_txt(progp->name), '#') == NULL)
|
|
printf("DEBUG: (%s:%d) free_prog(%p '%s') ref %"PRIdPINT"\n"
|
|
, file, line, progp, get_txt(progp->name), progp->ref);
|
|
#endif
|
|
#ifdef DEBUG_REFS
|
|
if (d_flag)
|
|
printf("%s free_prog: %s\n", time_stamp(), get_txt(progp->name));
|
|
#endif
|
|
if (progp->ref < 0)
|
|
fatal("Negative ref count (%"PRIdPINT") for prog ref "
|
|
"(program %p '%s').\n",
|
|
progp->ref, progp, get_txt(progp->name));
|
|
|
|
if (free_all && progp->blueprint)
|
|
{
|
|
object_t * blueprint = progp->blueprint;
|
|
progp->blueprint = NULL;
|
|
#ifdef USE_SWAP
|
|
remove_prog_swap(progp, MY_TRUE);
|
|
#endif
|
|
#if 0 && defined(CHECK_OBJECT_REF)
|
|
if (strchr(get_txt(blueprint->name), '#') == NULL)
|
|
printf("DEBUG: (%s:%d) free_prog(%p '%s') ref %"PRIdPINT" : "
|
|
"blueprint (%p '%s') ref %"PRIdPINT", flags %hx\n"
|
|
, file, line, progp, get_txt(progp->name), progp->ref
|
|
, blueprint, get_txt(blueprint->name), blueprint->ref, blueprint->flags);
|
|
#elif defined(CHECK_OBJECT_REF)
|
|
# ifdef __MWERKS__
|
|
# pragma unused(file)
|
|
# pragma unused(line)
|
|
# endif
|
|
#endif
|
|
free_object(blueprint, "free_prog");
|
|
}
|
|
|
|
/* Update the statistics */
|
|
total_prog_block_size -= progp->total_size;
|
|
total_num_prog_blocks -= 1;
|
|
|
|
/* Free the line numbers.
|
|
*
|
|
* This has to be done before the program is removed from the
|
|
* swapper, else the following test would fail.
|
|
*/
|
|
if (progp->line_numbers)
|
|
{
|
|
total_prog_block_size -= progp->line_numbers->size;
|
|
xfree(progp->line_numbers);
|
|
progp->line_numbers = NULL;
|
|
}
|
|
|
|
/* Is it a 'real' free? Then dereference all the
|
|
* things held by the program, too.
|
|
*/
|
|
if (free_all)
|
|
{
|
|
int i;
|
|
bytecode_p program;
|
|
funflag_t *functions;
|
|
|
|
#ifdef USE_SWAP
|
|
/* Remove the swap entry */
|
|
remove_prog_swap(progp, MY_FALSE);
|
|
#endif
|
|
|
|
program = progp->program;
|
|
functions = progp->functions;
|
|
|
|
/* Free all function names. */
|
|
for (i = progp->num_functions; --i >= 0; )
|
|
{
|
|
if ( !(functions[i] & NAME_INHERITED) )
|
|
{
|
|
string_t *name;
|
|
memcpy(
|
|
&name,
|
|
FUNCTION_NAMEP(program + (functions[i] & FUNSTART_MASK)),
|
|
sizeof name
|
|
);
|
|
free_mstring(name);
|
|
}
|
|
}
|
|
|
|
/* Free the strings, variable names and include filenames. */
|
|
do_free_sub_strings( progp->num_strings, progp->strings
|
|
, progp->num_variables, progp->variables
|
|
, progp->num_includes, progp->includes
|
|
#ifdef USE_STRUCTS
|
|
, progp->num_structs, progp->struct_defs
|
|
#endif /* USE_STRUCTS */
|
|
);
|
|
|
|
/* Free all inherited objects */
|
|
for (i = 0; i < progp->num_inherited; i++)
|
|
free_prog(progp->inherit[i].prog, MY_TRUE);
|
|
|
|
/* Free the program name */
|
|
total_prog_block_size -= mstrsize(progp->name);
|
|
free_mstring(progp->name);
|
|
}
|
|
|
|
/* Remove the program structure */
|
|
xfree(progp);
|
|
} /* free_prog() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static string_t *
|
|
function_exists (string_t *fun, object_t *ob, Bool show_hidden
|
|
, string_t ** prog_name, uint32 * prog_line
|
|
, int * num_arg, uint32 * fun_flags
|
|
, vartype_t * fun_type
|
|
)
|
|
|
|
/* Search for the function <fun> in the object <ob>. If existing, return
|
|
* the name of the program (without added reference), if not return NULL.
|
|
*
|
|
* If <prog_name> and <prog_line> are both non-NULL, they are set to
|
|
* the name of the program _file_ and the line where the function is found.
|
|
* The program file name will have one reference added.
|
|
*
|
|
* *<num_arg>, *<fun_flags>, *<fun_type> are set to the number of
|
|
* arguments, the function flags and the function return type respectively.
|
|
*
|
|
* Visibility rules apply: static and protected functions can't be
|
|
* found from the outside unless <show_hidden> is true.
|
|
*/
|
|
|
|
{
|
|
string_t *shared_name;
|
|
fun_hdr_p funstart;
|
|
program_t *progp;
|
|
int ix;
|
|
funflag_t flags;
|
|
|
|
#ifdef DEBUG
|
|
if (ob->flags & O_DESTRUCTED)
|
|
fatal("function_exists() on destructed object\n");
|
|
#endif
|
|
|
|
memset(fun_type, 0, sizeof(*fun_type));
|
|
*num_arg = 0;
|
|
*fun_flags = 0;
|
|
|
|
if (prog_name)
|
|
*prog_name = NULL;
|
|
|
|
#ifdef USE_SWAP
|
|
/* Make the program resident */
|
|
if (O_PROG_SWAPPED(ob))
|
|
{
|
|
ob->time_of_ref = current_time;
|
|
if (load_ob_from_swap(ob) < 0)
|
|
errorf("Out of memory: unswap object '%s'\n", get_txt(ob->name));
|
|
}
|
|
#endif
|
|
|
|
shared_name = find_tabled(fun);
|
|
progp = ob->prog;
|
|
|
|
/* Check if the function exists at all */
|
|
if ( (ix = find_function(shared_name, progp)) < 0)
|
|
return NULL;
|
|
|
|
/* Is it visible for the caller? */
|
|
flags = progp->functions[ix];
|
|
*fun_flags = (flags & ~INHERIT_MASK);
|
|
|
|
if (!show_hidden
|
|
&& ( flags & TYPE_MOD_PRIVATE
|
|
|| (flags & TYPE_MOD_STATIC && current_object != ob))
|
|
)
|
|
return NULL;
|
|
|
|
/* Resolve inheritance */
|
|
while (flags & NAME_INHERITED)
|
|
{
|
|
inherit_t *inheritp;
|
|
|
|
inheritp = &progp->inherit[flags & INHERIT_MASK];
|
|
ix -= inheritp->function_index_offset;
|
|
progp = inheritp->prog;
|
|
flags = progp->functions[ix];
|
|
}
|
|
|
|
funstart = progp->program + (flags & FUNSTART_MASK);
|
|
|
|
/* Set the additional information */
|
|
*num_arg = FUNCTION_NUM_ARGS(funstart) & 0x7f;
|
|
memcpy(fun_type, FUNCTION_TYPEP(funstart), sizeof(*fun_type));
|
|
|
|
/* And after all this, the function may be undefined */
|
|
if (is_undef_function(funstart))
|
|
{
|
|
*fun_flags |= NAME_UNDEFINED;
|
|
return NULL;
|
|
}
|
|
|
|
if(prog_line && prog_name)
|
|
*prog_line = get_line_number(funstart, progp, prog_name);
|
|
|
|
/* We got it. */
|
|
return progp->name;
|
|
} /* function_exists() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
void
|
|
reset_object (object_t *ob, int arg)
|
|
|
|
/* Depending on <arg>, call one of the initialisation functions in <ob>.
|
|
* The actual function is given in <arg> through its hook index.
|
|
* Accepted values are: H_RESET, H_CREATE_SUPER, H_CREATE_OB,
|
|
* H_CREATE_CLONE.
|
|
*
|
|
* The value of the hooks can be function names (strings) or closures.
|
|
*
|
|
* For strings, the name is the function called in <ob>. It gets passed
|
|
* one argument: 0 for H_CREATE_*, 1 for H_RESET. If on a H_RESET call
|
|
* the function can not be found, the object will never be reset again.
|
|
*
|
|
* For closures, the code distinguishes closures which take no arguments
|
|
* (only for H_CREATE_* calls) from those which take at least one argument.
|
|
* In the former case, the closure is bound to <ob> and called; in the
|
|
* latter case, the closure is bound to the current object and gets <ob>
|
|
* passed as argument. If the closure returns a numeric result, it is
|
|
* used as the time delay before the next reset.
|
|
*
|
|
* If the delay to the next (resp. first) reset is not determined by
|
|
* the called function, it is set to a random value between time_to_reset/2
|
|
* and time_to_reset. Upon time of call, the object must not be
|
|
* in the reset table; this function will enter it there.
|
|
*/
|
|
|
|
{
|
|
/* Be sure to update time first ! */
|
|
if (time_to_reset > 0)
|
|
ob->time_reset = current_time + time_to_reset/2
|
|
+ (mp_int)random_number((uint32)time_to_reset/2);
|
|
|
|
if (driver_hook[arg].type == T_CLOSURE)
|
|
{
|
|
lambda_t *l;
|
|
|
|
if (arg == H_RESET)
|
|
previous_ob = current_object = ob;
|
|
|
|
l = driver_hook[arg].u.lambda;
|
|
free_object(l->ob, "reset_object");
|
|
if (l->function.code[1] && arg != H_RESET)
|
|
{
|
|
/* closure accepts arguments, presumably one, so
|
|
* give it the target object and bind to the current
|
|
* object.
|
|
*/
|
|
l->ob = ref_object(current_object, "reset_object");
|
|
push_ref_object(inter_sp, ob, "reset");
|
|
call_lambda(&driver_hook[arg], 1);
|
|
}
|
|
else
|
|
{
|
|
/* no arguments, just bind to target */
|
|
l->ob = ref_object(ob, "reset_object");
|
|
call_lambda(&driver_hook[arg], 0);
|
|
}
|
|
|
|
/* If the call returned a non-zero number, use it as the current
|
|
* reset interval, overwriting the default set above.
|
|
*/
|
|
if (inter_sp->type == T_NUMBER && inter_sp->u.number)
|
|
ob->time_reset = (inter_sp->u.number > 0)
|
|
? current_time + inter_sp->u.number
|
|
: 0;
|
|
|
|
pop_stack();
|
|
}
|
|
else if (driver_hook[arg].type == T_STRING)
|
|
{
|
|
if (arg == H_RESET)
|
|
previous_ob = current_object = ob;
|
|
|
|
push_number(inter_sp, arg == H_RESET);
|
|
if (!sapply_ign_prot(driver_hook[arg].u.str, ob, 1)
|
|
&& arg == H_RESET)
|
|
ob->time_reset = 0;
|
|
}
|
|
|
|
/* Object is reset now */
|
|
ob->flags |= O_RESET_STATE;
|
|
} /* reset_object() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
void
|
|
logon_object (object_t *ob)
|
|
|
|
/* Call the logon() lfun in the object <ob>.
|
|
*
|
|
* current_object is temporarily set to <ob> in order to allow logon()
|
|
* to be static (security measure). Doing so is harmless as there is no
|
|
* previous_object to consider.
|
|
*/
|
|
|
|
{
|
|
svalue_t *ret;
|
|
object_t *save = current_object;
|
|
|
|
current_object = ob;
|
|
ret = apply(STR_LOGON, ob, 0);
|
|
if (ret == 0)
|
|
{
|
|
errorf("Could not find %s() on the player %s\n", get_txt(STR_LOGON),
|
|
get_txt(ob->name));
|
|
/* NOTREACHED */
|
|
}
|
|
current_object = save;
|
|
} /* logon_object() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
void
|
|
replace_programs (void)
|
|
|
|
/* Called from the backend loop, this function
|
|
* performs all pending program replacements listed in obj_list_replace.
|
|
*
|
|
* If the function runs out of memory, the processing ends at that point
|
|
* and will be retried in the next call.
|
|
*
|
|
* Sideeffects of this action are: the objects are marked as 'replaced',
|
|
* and current shadows are removed.
|
|
*/
|
|
|
|
{
|
|
replace_ob_t *r_ob, *r_next; /* List pointers */
|
|
svalue_t *svp;
|
|
int i, j;
|
|
|
|
#ifdef DEBUG
|
|
if (d_flag)
|
|
debug_message("%s start of replace_programs\n", time_stamp());
|
|
#endif
|
|
|
|
for (r_ob = obj_list_replace; r_ob; r_ob = r_next)
|
|
{
|
|
program_t *old_prog;
|
|
|
|
#ifdef USE_SWAP
|
|
/* Swap in the program. This can't fail when called during
|
|
* a garbage collection because then the malloc privilege
|
|
* is MALLOC_SYSTEM.
|
|
*/
|
|
if (r_ob->ob->flags & O_SWAPPED && load_ob_from_swap(r_ob->ob) < 0)
|
|
{
|
|
obj_list_replace = r_ob;
|
|
return; /* Hope for more memory next time... */
|
|
}
|
|
#endif
|
|
|
|
/* If the number of variables changes, allocate a new variables
|
|
* block and copy the old values over as far as possible.
|
|
* Note that the change can only be a reduction, and that
|
|
* the new program may not have variables at all. However, if
|
|
* 'i' is not 0, the old program is guaranteed to have vars.
|
|
*/
|
|
i = r_ob->ob->prog->num_variables - r_ob->new_prog->num_variables;
|
|
if (i)
|
|
{
|
|
svalue_t *new_vars;
|
|
|
|
/* Get the memory */
|
|
|
|
if (r_ob->new_prog->num_variables)
|
|
{
|
|
new_vars = xalloc( r_ob->new_prog->num_variables
|
|
* sizeof *new_vars);
|
|
|
|
if (!new_vars)
|
|
{
|
|
obj_list_replace = r_ob;
|
|
return; /* Hope for more memory next time... */
|
|
}
|
|
}
|
|
else
|
|
new_vars = NULL;
|
|
#ifdef USE_PARANOIA
|
|
if (d_flag)
|
|
debug_message("%s %d less variables\n", time_stamp(), i);
|
|
r_ob->ob->extra_num_variables = r_ob->new_prog->num_variables;
|
|
#endif
|
|
|
|
/* Adjust the statistics */
|
|
#ifdef CHECK_OBJECT_STAT
|
|
if (check_object_stat)
|
|
{
|
|
fprintf(stderr, "DEBUG: OSTAT: (%ld:%ld) rprog( %p '%s') sub %d vars : %"PRIuPINT" -> (%ld:%ld)\n"
|
|
, tot_alloc_object, tot_alloc_object_size, r_ob, r_ob->ob->name ? get_txt(r_ob->ob->name) : "<null>"
|
|
, i
|
|
, (p_uint)(i * sizeof(svalue_t))
|
|
, tot_alloc_object, tot_alloc_object_size - (i * sizeof(svalue_t))
|
|
);
|
|
}
|
|
#endif
|
|
tot_alloc_object_size -= i * sizeof(svalue_t);
|
|
|
|
svp = r_ob->ob->variables; /* the old variables */
|
|
|
|
/* Deref those variables of ob which won't be copied */
|
|
|
|
j = r_ob->var_offset; /* number of unique vars of ob */
|
|
i -= j;
|
|
|
|
#ifdef DEBUG
|
|
if (d_flag)
|
|
debug_message("%s freeing %d variables:\n", time_stamp(), j);
|
|
#endif
|
|
while (--j >= 0)
|
|
{
|
|
free_svalue(svp++);
|
|
}
|
|
#ifdef DEBUG
|
|
if (d_flag)
|
|
debug_message("%s freed.\n", time_stamp());
|
|
#endif
|
|
|
|
/* Copy the others */
|
|
j = r_ob->new_prog->num_variables;
|
|
if (j)
|
|
{
|
|
memcpy(
|
|
(char *)new_vars,
|
|
(char *)svp,
|
|
j * sizeof(svalue_t)
|
|
);
|
|
svp += j;
|
|
}
|
|
#ifdef DEBUG
|
|
if (d_flag)
|
|
debug_message("%s freeing %d variables:\n", time_stamp(), i);
|
|
#endif
|
|
|
|
/* Deref the remaining non-copied variables */
|
|
while (--i >= 0)
|
|
{
|
|
free_svalue(svp++);
|
|
}
|
|
#ifdef DEBUG
|
|
if (d_flag)
|
|
debug_message("%s freed.\n", time_stamp());
|
|
#endif
|
|
|
|
/* Free the old variable block and set the new one */
|
|
xfree(r_ob->ob->variables);
|
|
r_ob->ob->variables = new_vars;
|
|
} /* if (change in vars) */
|
|
|
|
/* If the object modified is a blueprint object, NULL out the pointer
|
|
* in its program, because after the replacement the blueprint nature
|
|
* will be lost.
|
|
*/
|
|
if (r_ob->ob->prog->blueprint == r_ob->ob)
|
|
{
|
|
r_ob->ob->prog->blueprint = NULL;
|
|
#ifdef USE_SWAP
|
|
remove_prog_swap(r_ob->ob->prog, MY_TRUE);
|
|
#endif
|
|
free_object(r_ob->ob, "replace_programs: blueprint reference");
|
|
}
|
|
|
|
/* Replace the old program with the new one */
|
|
old_prog = r_ob->ob->prog;
|
|
r_ob->new_prog->ref++;
|
|
r_ob->ob->prog = r_ob->new_prog;
|
|
r_ob->ob->flags |= O_REPLACED;
|
|
|
|
r_next = r_ob->next; /* remove it from the list */
|
|
|
|
/* Handle a possible lambda adjustment */
|
|
if (r_ob->lambda_rpp)
|
|
{
|
|
obj_list_replace = r_next;
|
|
replace_program_lambda_adjust(r_ob);
|
|
}
|
|
|
|
/* Remove current shadows */
|
|
|
|
#ifdef USE_SHADOWING
|
|
if (r_ob->ob->flags & O_SHADOW)
|
|
{
|
|
shadow_t *shadow_sent;
|
|
|
|
if ((shadow_sent = O_GET_SHADOW(r_ob->ob))->shadowing)
|
|
{
|
|
/* The master couldn't decide if it's a legal shadowing
|
|
* before the program was actually replaced. It is possible
|
|
* that the blueprint to the replacing program is already
|
|
* destructed, and it's source changed.
|
|
* On the other hand, if we called the master now, all kind
|
|
* of volatile data structures could result, even new entries
|
|
* for obj_list_replace. This would eventually require to
|
|
* reference it, and all the lrpp's , in check_a_lot_ref_counts()
|
|
* and garbage_collection() . Being able to use replace_program()
|
|
* in shadows is hardly worth this effort. Thus, we simply
|
|
* stop the shadowing.
|
|
*/
|
|
O_GET_SHADOW(shadow_sent->shadowing)->shadowed_by =
|
|
shadow_sent->shadowed_by;
|
|
if (shadow_sent->shadowed_by)
|
|
{
|
|
O_GET_SHADOW(shadow_sent->shadowed_by)->shadowing =
|
|
shadow_sent->shadowing;
|
|
shadow_sent->shadowed_by = NULL;
|
|
}
|
|
shadow_sent->shadowing = NULL;
|
|
}
|
|
}
|
|
#endif
|
|
xfree(r_ob);
|
|
|
|
/* Free the old program, finally */
|
|
free_prog(old_prog, MY_TRUE);
|
|
|
|
#ifdef DEBUG
|
|
if (d_flag)
|
|
debug_message("%s program freed.\n", time_stamp());
|
|
#endif
|
|
}
|
|
|
|
/* Done with the list */
|
|
obj_list_replace = NULL;
|
|
|
|
#ifdef DEBUG
|
|
if (d_flag)
|
|
debug_message("%s end of replace_programs\n", time_stamp());
|
|
#endif
|
|
|
|
} /* replace_programs() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static replace_ob_t *
|
|
retrieve_replace_program_entry (void)
|
|
|
|
/* Auxiliary function to efun replace_program(): test if a program
|
|
* replacement is already scheduled for the current object. If yes,
|
|
* return the pointer to the replace_ob struct, else return NULL.
|
|
*/
|
|
|
|
{
|
|
replace_ob_t *r_ob;
|
|
|
|
for (r_ob = obj_list_replace; r_ob; r_ob = r_ob->next)
|
|
{
|
|
if (r_ob->ob == current_object)
|
|
return r_ob;
|
|
}
|
|
return NULL;
|
|
} /* retrieve_replace_program_entry() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static program_t *
|
|
search_inherited (string_t *str, program_t *prg, int *offpnt)
|
|
|
|
/* Auxiliary function to efun replace_program(): check if program <str>
|
|
* is inherited by <prg>. If yes, return the originating program and
|
|
* store the (accumulated) variable and function offsets in offpnt[0]
|
|
* and offpnt[1] resp.
|
|
*
|
|
* If the program is not found, return NULL.
|
|
*
|
|
* Nested inherits are handled in a depth search, the function recurses
|
|
* for this.
|
|
*/
|
|
|
|
{
|
|
program_t *tmp;
|
|
int i;
|
|
#ifdef DEBUG
|
|
char *ts;
|
|
#endif
|
|
|
|
#ifdef DEBUG
|
|
ts = NULL;
|
|
if (d_flag)
|
|
{
|
|
ts = time_stamp();
|
|
debug_message("%s search_inherited started\n", ts);
|
|
debug_message("%s searching for PRG(%s) in PRG(%s)\n"
|
|
, ts, get_txt(str), get_txt(prg->name));
|
|
debug_message("%s num_inherited=%hu\n", ts, prg->num_inherited);
|
|
}
|
|
#endif
|
|
|
|
/* Loop through all inherited programs, returning directly when
|
|
* the name program was found.
|
|
*/
|
|
for ( i = 0; i < prg->num_inherited; i++)
|
|
{
|
|
#ifdef DEBUG
|
|
if (d_flag)
|
|
{
|
|
debug_message("%s index %d:\n", ts, i);
|
|
debug_message("%s checking PRG(%s)\n"
|
|
, ts, get_txt(prg->inherit[i].prog->name));
|
|
}
|
|
#endif
|
|
/* Duplicate virtual inherits don't count */
|
|
if ( prg->inherit[i].inherit_type & INHERIT_TYPE_DUPLICATE )
|
|
continue;
|
|
|
|
if (mstreq(str, prg->inherit[i].prog->name ))
|
|
{
|
|
#ifdef DEBUG
|
|
if (d_flag)
|
|
debug_message("%s match found\n", ts);
|
|
#endif
|
|
offpnt[0] = prg->inherit[i].variable_index_offset;
|
|
offpnt[1] = prg->inherit[i].function_index_offset;
|
|
return prg->inherit[i].prog;
|
|
}
|
|
else if ( NULL != (tmp = search_inherited(str, prg->inherit[i].prog,offpnt)) )
|
|
{
|
|
#ifdef DEBUG
|
|
if (d_flag)
|
|
debug_message("%s deferred match found\n", ts);
|
|
#endif
|
|
offpnt[0] += prg->inherit[i].variable_index_offset;
|
|
offpnt[1] += prg->inherit[i].function_index_offset;
|
|
return tmp;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if (d_flag)
|
|
debug_message("%s search_inherited failed\n", ts);
|
|
#endif
|
|
|
|
return NULL;
|
|
} /* search_inherited() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
void
|
|
tell_npc (object_t *ob, string_t *str)
|
|
|
|
/* Call the lfun 'catch_tell()' in object <ob> with <str> as argument.
|
|
*
|
|
* This function is used to talk to non-interactive commandgivers
|
|
* (aka NPCs).
|
|
*/
|
|
|
|
{
|
|
push_ref_string(inter_sp, str);
|
|
(void)sapply(STR_CATCH_TELL, ob, 1);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
void
|
|
tell_npc_str (object_t *ob, const char *str)
|
|
|
|
/* Call the lfun 'catch_tell()' in object <ob> with <str> as argument.
|
|
*
|
|
* This function is used to talk to non-interactive commandgivers
|
|
* (aka NPCs).
|
|
*/
|
|
|
|
{
|
|
push_c_string(inter_sp, str);
|
|
(void)sapply(STR_CATCH_TELL, ob, 1);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
void
|
|
tell_object (object_t *ob, string_t *str)
|
|
|
|
/* Send message <str> to object <ob>. If <ob> is an interactive player,
|
|
* it will go to his screen (unless a shadow catches it - see shadow_catch_
|
|
* message() ). If <ob> is not interactive, the message will go
|
|
* to the lfun 'catch_tell()' via a call to tell_npc().
|
|
*/
|
|
|
|
{
|
|
object_t *save_command_giver;
|
|
interactive_t *ip;
|
|
|
|
if (ob->flags & O_DESTRUCTED)
|
|
return;
|
|
|
|
if (O_SET_INTERACTIVE(ip, ob))
|
|
{
|
|
save_command_giver = command_giver;
|
|
command_giver = ob;
|
|
add_message(FMT_STRING, str);
|
|
command_giver = save_command_giver;
|
|
return;
|
|
}
|
|
tell_npc(ob, str);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
#ifdef USE_SHADOWING
|
|
Bool
|
|
shadow_catch_message (object_t *ob, const char *str)
|
|
|
|
/* Called by comm:add_message() to handle the case that messages <str> sent
|
|
* to interactive objects <ob> are to be delivered to shadows of the
|
|
* OR to a function in the interactive itself.
|
|
*
|
|
* This function checks all shadows of <ob> if they contain the lfun
|
|
* catch_tell(), and calls the lfun in the first shadow where it exists
|
|
* with message <str> as argument.
|
|
*
|
|
* Result is true if there is such a function, and false if not. In
|
|
* the latter case, the flag ob->ip.catch_tell_activ is cleared to
|
|
* speed up later calls.
|
|
*
|
|
* The function returns immediately with false, if ob->ip.catch_tell_activ
|
|
* is cleared, or if ob is the current_object.
|
|
*
|
|
* Beware that one of the shadows may be the originator of the message,
|
|
* which means that we must not send the message to that shadow, or any
|
|
* shadows in the linked list before that shadow.
|
|
*
|
|
* If the interactive user itself contains the lfun catch_tell(), the
|
|
* messages counts as caught, too.
|
|
*/
|
|
|
|
{
|
|
interactive_t *ip;
|
|
|
|
ip = O_GET_INTERACTIVE(ob);
|
|
|
|
if (!ip || !ip->catch_tell_activ || ob == current_object)
|
|
return MY_FALSE;
|
|
|
|
trace_level |= ip->trace_level;
|
|
push_c_string(inter_sp, str);
|
|
if (sapply(STR_CATCH_TELL, ob, 1))
|
|
return MY_TRUE;
|
|
|
|
/* The call failed, thus, current_object wasn't changed
|
|
* (e.g. destructed and set to 0 ) .
|
|
* !current_object is true when a prompt is given.
|
|
*/
|
|
if (!current_object
|
|
|| !(current_object->flags & O_SHADOW)
|
|
|| !O_GET_SHADOW(current_object)->shadowing)
|
|
{
|
|
ip->catch_tell_activ = MY_FALSE;
|
|
}
|
|
return MY_FALSE;
|
|
} /* shadow_catch_message() */
|
|
#endif
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
clear_program_id (program_t *p)
|
|
|
|
/* Clear the id_number of program <p> and all inherited programs.
|
|
*/
|
|
|
|
{
|
|
int i;
|
|
|
|
if (!p->id_number)
|
|
return;
|
|
|
|
p->id_number = 0;
|
|
for (i = 0; i< p->num_inherited; i++)
|
|
{
|
|
clear_program_id(p->inherit[i].prog);
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
renumber_program (program_t *p)
|
|
|
|
/* Renumber program <p> and all inherited programs.
|
|
* Assumes that all id_numbers have been cleared before.
|
|
*/
|
|
|
|
{
|
|
int i;
|
|
|
|
if (p->id_number)
|
|
return;
|
|
p->id_number = ++current_id_number;
|
|
for (i=0; i< p->num_inherited; i++) {
|
|
renumber_program(p->inherit[i].prog);
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
int32
|
|
renumber_programs (void)
|
|
|
|
/* Renumber all programs in the game, recycling number from old
|
|
* objects. Return the first free new id_number and modifies
|
|
* the global current_id_number.
|
|
*
|
|
* The function is called by swap.c and lang.c when current_id_number
|
|
* overflows.
|
|
*/
|
|
|
|
{
|
|
object_t *ob;
|
|
|
|
current_id_number = 0;
|
|
for (ob = obj_list; ob; ob = ob->next_all)
|
|
{
|
|
#ifdef DEBUG
|
|
if (ob->flags & O_DESTRUCTED) /* TODO: Can't happen */
|
|
continue;
|
|
#endif
|
|
#ifdef USE_SWAP
|
|
if ( !O_PROG_SWAPPED(ob) )
|
|
#endif
|
|
clear_program_id(ob->prog);
|
|
}
|
|
|
|
for (ob = obj_list; ob; ob = ob->next_all)
|
|
{
|
|
#ifdef DEBUG
|
|
if (ob->flags & O_DESTRUCTED) /* TODO: Can't happen */
|
|
continue;
|
|
#endif
|
|
#ifdef USE_SWAP
|
|
if ( !O_PROG_SWAPPED(ob) )
|
|
#endif
|
|
renumber_program(ob->prog);
|
|
}
|
|
invalidate_apply_low_cache();
|
|
return ++current_id_number;
|
|
}
|
|
|
|
/*=========================================================================*/
|
|
/* EFUNS */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
v_function_exists (svalue_t *sp, int num_arg)
|
|
|
|
/* EXEC function_exists()
|
|
*
|
|
* mixed function_exists (string str [, int flags])
|
|
* mixed function_exists (string str , object ob, [, int flags])
|
|
*
|
|
* Look up a function <str> in the current object, respectively
|
|
* in the object <ob>. Depending on the value of <flags>, one
|
|
* of the following informations is returned:
|
|
*
|
|
* <flags> == FEXISTS_PROGNAME (0, default):
|
|
* Return the name of the program the function is defined in.
|
|
* This can be either object_name(ob), or the name of an inherited
|
|
* program. If !compat mode, the returned name always begins
|
|
* with a '/'.
|
|
*
|
|
* <flags> == FEXISTS_FILENAME (1):
|
|
* Return the name of the file the function is defined in (this
|
|
* may be an include file). If !compat mode, the returned name
|
|
* always begins with a '/'.
|
|
*
|
|
* <flags> == FEXISTS_LINENO (2):
|
|
* Return the line number within the source file.
|
|
*
|
|
* <flags> == FEXISTS_ALL (3):
|
|
* Return an array with all the above information, plus information
|
|
* about the function type/flags/number of arguments.
|
|
*
|
|
* The returned array contains this information:
|
|
* string [FEXISTS_PROGNAME]: the program name
|
|
* string [FEXISTS_FILENAME]: the filename
|
|
* int [FEXISTS_LINENO]: the linenumber
|
|
* int [FEXISTS_NUMARG]: the number of arguments to the function
|
|
* int [FEXISTS_TYPE]: the return type of the function
|
|
* int [FEXISTS_FLAGS]: the function flags
|
|
*
|
|
* The <flags> value can be or-ed to NAME_HIDDEN to return
|
|
* information about static and protected functions in other objects.
|
|
* It is not possible to return information about private functions.
|
|
*
|
|
* If the function cannot be found (because it doesn't exist or
|
|
* it is not visible to the caller), the result is 0.
|
|
*/
|
|
|
|
{
|
|
string_t *str, *prog_name;
|
|
uint32 prog_line = 0;
|
|
p_int flags;
|
|
svalue_t *argp;
|
|
object_t *ob;
|
|
|
|
uint32 fun_flags;
|
|
int fun_num_arg;
|
|
vartype_t fun_type;
|
|
|
|
/* Evaluate arguments */
|
|
argp = sp - num_arg + 1;
|
|
|
|
ob = NULL;
|
|
flags = 0;
|
|
|
|
if (num_arg < 2)
|
|
{
|
|
ob = current_object;
|
|
flags = 0;
|
|
}
|
|
|
|
if (num_arg >= 2)
|
|
{
|
|
if (argp[1].type == T_NUMBER)
|
|
{
|
|
ob = current_object;
|
|
flags = argp[1].u.number;
|
|
|
|
if ((flags & ~NAME_HIDDEN) < 0
|
|
|| (flags & ~NAME_HIDDEN) > FEXISTS_ALL
|
|
)
|
|
{
|
|
errorf("Bad argument 2 to function_exists(): value %"PRIdPINT
|
|
" (%"PRIdPINT" sans NAME_HIDDEN) out of range %d..%d .\n"
|
|
, flags, (flags & ~NAME_HIDDEN)
|
|
, FEXISTS_ALL, FEXISTS_LINENO);
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
}
|
|
else if (argp[1].type == T_OBJECT)
|
|
{
|
|
ob = argp[1].u.ob;
|
|
flags = 0;
|
|
}
|
|
}
|
|
|
|
if (num_arg >= 3)
|
|
{
|
|
/* The last argument must be a number. On the other
|
|
* side, we can't have two numbers at once.
|
|
*/
|
|
if (argp[1].type != T_OBJECT)
|
|
{
|
|
errorf("Bad argument 2 to function_exists(): got %s, expected object.\n", typename(argp[1].type));
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
|
|
flags = argp[2].u.number;
|
|
|
|
if (((flags & ~NAME_HIDDEN) < 0)
|
|
|| ((flags & ~NAME_HIDDEN) > FEXISTS_ALL)
|
|
)
|
|
{
|
|
errorf("Bad argument 3 to function_exists(): eff. value %"PRIdPINT" (sans NAME_HIDDEN) out of range %d..%d .\n"
|
|
, (flags & ~NAME_HIDDEN)
|
|
, FEXISTS_PROGNAME, FEXISTS_ALL);
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
}
|
|
|
|
if (ob->flags & O_DESTRUCTED)
|
|
{
|
|
errorf("Bad argument to function_exists(): Object is destructed.\n");
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
|
|
/* Get the information */
|
|
prog_name = NULL;
|
|
str = function_exists(argp->u.str, ob, (flags & NAME_HIDDEN)
|
|
, &prog_name, &prog_line
|
|
, &fun_num_arg, &fun_flags, &fun_type);
|
|
sp = pop_n_elems(num_arg, sp);
|
|
|
|
if (str)
|
|
{
|
|
switch (flags & ~NAME_HIDDEN)
|
|
{
|
|
case FEXISTS_ALL:
|
|
{
|
|
string_t *res;
|
|
vector_t *vec;
|
|
|
|
res = cvt_progname(str);
|
|
if (!res)
|
|
{
|
|
errorf("Out of memory\n");
|
|
}
|
|
vec = allocate_uninit_array(FEXISTS_FLAGS+1);
|
|
put_string(vec->item+FEXISTS_PROGNAME, res);
|
|
if (prog_name)
|
|
{
|
|
res = add_slash(prog_name);
|
|
if (!res)
|
|
{
|
|
errorf("Out of memory\n");
|
|
}
|
|
put_string(vec->item+FEXISTS_FILENAME, res);
|
|
}
|
|
else
|
|
put_number(vec->item+FEXISTS_FILENAME, 0);
|
|
put_number(vec->item+FEXISTS_LINENO, prog_line);
|
|
|
|
put_number(vec->item+FEXISTS_NUMARG, fun_num_arg);
|
|
put_number(vec->item+FEXISTS_TYPE, fun_type.type);
|
|
put_number(vec->item+FEXISTS_FLAGS, (p_int)fun_flags);
|
|
|
|
push_array(sp, vec);
|
|
break;
|
|
}
|
|
case FEXISTS_PROGNAME:
|
|
{
|
|
string_t *res;
|
|
|
|
res = cvt_progname(str);
|
|
if (!res)
|
|
{
|
|
errorf("Out of memory\n");
|
|
}
|
|
push_string(sp, res);
|
|
break;
|
|
}
|
|
|
|
case FEXISTS_FILENAME:
|
|
if (prog_name)
|
|
{
|
|
string_t *res;
|
|
|
|
res = add_slash(prog_name);
|
|
if (!res)
|
|
{
|
|
errorf("Out of memory\n");
|
|
}
|
|
push_string(sp, res);
|
|
}
|
|
else
|
|
push_number(sp, 0);
|
|
break;
|
|
|
|
case FEXISTS_LINENO:
|
|
push_number(sp, prog_line);
|
|
break;
|
|
|
|
default:
|
|
fatal("function_exists(): flags value %"PRIdPINT" (from %"PRIdPINT") not implemented.\n"
|
|
, (flags & ~NAME_HIDDEN), flags);
|
|
/* NOTREACHED */
|
|
}
|
|
}
|
|
else
|
|
{
|
|
push_number(sp, 0);
|
|
}
|
|
|
|
/* Clean up */
|
|
if (prog_name)
|
|
free_mstring(prog_name);
|
|
/* str had no ref on its own */
|
|
|
|
return sp;
|
|
} /* v_function_exists() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
f_functionlist (svalue_t *sp)
|
|
|
|
/* EFUN functionlist()
|
|
*
|
|
* mixed *functionlist (object ob, int flags = RETURN_FUNCTION_NAME)
|
|
*
|
|
* Return an array with information about <ob>s lfunctions. For every
|
|
* function, 1 to 4 values (depending on <flags>) are stored in
|
|
* the result array conveying in this order:
|
|
* - the name of the function
|
|
* - the function flags (see below)
|
|
* - the return type (listed in mudlib/sys/lpctypes.h)
|
|
* - the number of accepted argumens
|
|
*
|
|
* <ob> may be given as true object or as a filename. In the latter
|
|
* case, the efun does not try to load the object before proceeding.
|
|
*
|
|
* <flags> determines both which information is returned for every
|
|
* function, and which functions should be considered at all.
|
|
* Its value is created by bin-or'ing together following flags from
|
|
* mudlib/sys/functionlist.h:
|
|
*
|
|
* Control of returned information:
|
|
* RETURN_FUNCTION_NAME include the function name
|
|
* RETURN_FUNCTION_FLAGS include the function flags
|
|
* RETURN_FUNCTION_TYPE include the return type
|
|
* RETURN_FUNCTION_NUMARG include the number of arguments.
|
|
*
|
|
* The name RETURN_FUNCTION_ARGTYPE is defined but not implemented.
|
|
*
|
|
* Control of listed functions:
|
|
* NAME_INHERITED don't list if defined by inheritance
|
|
* TYPE_MOD_STATIC don't list if static function
|
|
* TYPE_MOD_PRIVATE don't list if private
|
|
* TYPE_MOD_PROTECTED don't list if protected
|
|
* NAME_HIDDEN don't list if not visible through inheritance
|
|
*
|
|
* The 'flags' information consists of the bin-or of the list control
|
|
* flags given above, plus the following:
|
|
*
|
|
* TYPE_MOD_VARARGS function takes varargs
|
|
* NAME_UNDEFINED function not defined yet, but referenced.
|
|
* NAME_CROSS_DEFINED function is defined to be in a different program
|
|
* TYPE_MOD_NO_MASK function is nomask
|
|
* TYPE_MOD_PUBLIC function is public
|
|
*
|
|
* All these flags are defined in mudlib/sys/functionlist.h, which
|
|
* should be copied into an accessible place in the mudlib. The
|
|
* return types are defined in mudlib/sys/lpctypes.h which also
|
|
* should be copied into the mudlib.
|
|
*
|
|
* TODO: All these defs are in mudlib/sys/functionlist.h and mudlib/sys/lpctypes.h
|
|
* TODO:: as well as in exec.h and this file. This should be centralized.
|
|
* TODO:: Maybe write the files on mud startup?
|
|
* TODO:: Include mudlib/sys/functionlist.h doesn't help because then
|
|
* TODO:: mkdepend stumbles over the embedded include <sys/lpctypes.h>.
|
|
*/
|
|
|
|
{
|
|
object_t *ob; /* <ob> argument to list */
|
|
mp_int mode_flags; /* <flags> argument */
|
|
program_t *prog; /* <ob>'s program */
|
|
unsigned short num_functions; /* Number of functions to list */
|
|
char *vis_tags;
|
|
/* Bitflag array describing the visibility of every function in prog
|
|
* in relation to the passed <flags>: */
|
|
#define VISTAG_INVIS '\0' /* Function should not be listed */
|
|
#define VISTAG_VIS '\1' /* Function matches the <flags> list criterium */
|
|
#define VISTAG_ALL '\2' /* Function should be listed, no list restrictions */
|
|
#define VISTAG_NAMED '\4' /* Function is neither hidden nor private */
|
|
|
|
vector_t *list; /* Result vector */
|
|
svalue_t *svp; /* Last element in list which was filled in. */
|
|
uint32 *fun; /* Current function under examination */
|
|
program_t *defprog; /* Program which actually defines *fun */
|
|
uint32 flags;
|
|
unsigned short *ixp;
|
|
long i, j;
|
|
|
|
#define FILTERFLAGS (NAME_HIDDEN|TYPE_MOD_PRIVATE|TYPE_MOD_STATIC|TYPE_MOD_PROTECTED|NAME_INHERITED)
|
|
|
|
|
|
inter_sp = sp; /* In case of errors leave a clean stack */
|
|
|
|
/* Extract the arguments from the vm stack.
|
|
*/
|
|
if (sp[-1].type != T_OBJECT)
|
|
{
|
|
if (!(ob = find_object(sp[-1].u.str)))
|
|
errorf("Object '%s' not found.\n", get_txt(sp[-1].u.str));
|
|
}
|
|
else
|
|
ob = sp[-1].u.ob;
|
|
|
|
mode_flags = sp->u.number;
|
|
|
|
#ifdef USE_SWAP
|
|
if (O_PROG_SWAPPED(ob))
|
|
if (load_ob_from_swap(ob) < 0)
|
|
{
|
|
errorf("Out of memory: unswap object '%s'\n", get_txt(ob->name));
|
|
/* NOTREACHED */
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
prog = ob->prog;
|
|
|
|
/* Initialize the vistag[] flag array.
|
|
*/
|
|
num_functions = prog->num_functions;
|
|
vis_tags = alloca(num_functions);
|
|
if (!vis_tags)
|
|
{
|
|
errorf("Stack overflow in functionlist()");
|
|
/* NOTREACHED */
|
|
return NULL;
|
|
}
|
|
|
|
/* Preset the visibility. By default, if there is any listing
|
|
* modifier, the functions are not visible. If there is none, the functions
|
|
* are visible.
|
|
*/
|
|
memset( vis_tags, (mode_flags & FILTERFLAGS) ? VISTAG_INVIS : VISTAG_ALL
|
|
, num_functions);
|
|
|
|
/* Count how many named functions need to be listed in the result.
|
|
* Flag every function to list in vistag[].
|
|
*/
|
|
num_functions = 0;
|
|
|
|
/* First, check all functions for which we have a name */
|
|
flags = mode_flags & (FILTERFLAGS ^ NAME_HIDDEN);
|
|
fun = prog->functions;
|
|
j = prog->num_function_names;
|
|
for (ixp = prog->function_names + j; --j >= 0; ) {
|
|
i = *--ixp;
|
|
if (!(fun[i] & flags) )
|
|
{
|
|
vis_tags[i] = VISTAG_NAMED|VISTAG_VIS;
|
|
num_functions++;
|
|
}
|
|
else
|
|
{
|
|
vis_tags[i] |= VISTAG_NAMED;
|
|
}
|
|
}
|
|
|
|
/* If the user wants to see the hidden or private functions, we loop
|
|
* through the full function table and check all functions not yet
|
|
* touched by the previous 'named' scan.
|
|
* TODO: Due to the dedicated 'find hidden name' loop, this shouldn't
|
|
* TODO:: be necessary, nor the VISTAG_ALL at all.
|
|
*/
|
|
if ((mode_flags & (TYPE_MOD_PRIVATE|NAME_HIDDEN)) == 0)
|
|
{
|
|
fun = prog->functions;
|
|
for (i = prog->num_functions; --i >= 0; )
|
|
{
|
|
if (!(vis_tags[i] & VISTAG_NAMED)
|
|
&& !(fun[i] & flags)
|
|
)
|
|
{
|
|
vis_tags[i] = VISTAG_VIS;
|
|
num_functions++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If <flags> accepts all functions, use the total number of functions
|
|
* instead of the count computed above.
|
|
*/
|
|
if ( !(mode_flags & FILTERFLAGS))
|
|
{
|
|
num_functions = prog->num_functions;
|
|
}
|
|
|
|
/* Compute the size of the result vector to
|
|
* 2**(number of RETURN_FUNCTION_ bits set)
|
|
*/
|
|
for (i = mode_flags & RETURN_FUNCTION_MASK, j = 0; i; i >>= 1) {
|
|
if (i & 1)
|
|
j += num_functions;
|
|
}
|
|
|
|
/* Allocate the result vector and set svp to its end
|
|
*/
|
|
list = allocate_array(j);
|
|
svp = list->item + j;
|
|
|
|
/* Loop backwards through all functions, check their flags if
|
|
* they are to be listed and store the requested data in
|
|
* the result vector.
|
|
*/
|
|
|
|
for (i = prog->num_functions, fun = prog->functions + i; --i >= 0; )
|
|
{
|
|
fun_hdr_p funstart; /* Pointer to function in the executable */
|
|
uint32 active_flags; /* A functions definition status flags */
|
|
|
|
fun--;
|
|
|
|
if ((vis_tags[i] & (VISTAG_ALL|VISTAG_VIS)) == VISTAG_INVIS)
|
|
continue; /* Don't list this one */
|
|
|
|
flags = *fun;
|
|
|
|
active_flags = (flags & ~INHERIT_MASK);
|
|
if (!(vis_tags[i] & VISTAG_NAMED))
|
|
active_flags |= NAME_HIDDEN;
|
|
|
|
defprog = prog;
|
|
|
|
/* If its a cross-defined function, get the flags from
|
|
* real definition and let j point to it.
|
|
*/
|
|
if ( !~(flags | ~(NAME_INHERITED|NAME_CROSS_DEFINED) ) ) {
|
|
active_flags |= NAME_CROSS_DEFINED;
|
|
j = (long)CROSSDEF_NAME_OFFSET(flags);
|
|
flags = fun[j];
|
|
j += i;
|
|
} else {
|
|
j = i;
|
|
}
|
|
|
|
/* If the function is inherited, find the original definition.
|
|
*/
|
|
while (flags & NAME_INHERITED) {
|
|
inherit_t *ip = &defprog->inherit[flags & INHERIT_MASK];
|
|
|
|
defprog = ip->prog;
|
|
j -= ip->function_index_offset;
|
|
flags = defprog->functions[j];
|
|
}
|
|
|
|
/* defprog now points to the program which really defines
|
|
* the function fun.
|
|
*/
|
|
|
|
funstart = defprog->program + (flags & FUNSTART_MASK);
|
|
|
|
/* Add the data to the result vector as <flags> determines.
|
|
*/
|
|
|
|
if (mode_flags & RETURN_FUNCTION_NUMARG) {
|
|
svp--;
|
|
svp->u.number = FUNCTION_NUM_ARGS(funstart) & 0x7f;
|
|
}
|
|
|
|
if (mode_flags & RETURN_FUNCTION_TYPE) {
|
|
vartype_t rtype;
|
|
|
|
memcpy(&rtype, FUNCTION_TYPEP(funstart), sizeof(rtype));
|
|
svp--;
|
|
svp->u.number = rtype.type;
|
|
}
|
|
|
|
if (mode_flags & RETURN_FUNCTION_FLAGS) {
|
|
|
|
/* If the function starts with the bytecodes F_UNDEF,
|
|
* it referenced but undefined. But you know that.
|
|
*/
|
|
if (is_undef_function(funstart))
|
|
{
|
|
active_flags |= NAME_UNDEFINED;
|
|
}
|
|
svp--;
|
|
svp->u.number = (p_int)active_flags;
|
|
}
|
|
|
|
if (mode_flags & RETURN_FUNCTION_NAME) {
|
|
svp--;
|
|
svp->type = T_STRING;
|
|
memcpy( &svp->u.str, FUNCTION_NAMEP(funstart)
|
|
, sizeof svp->u.str);
|
|
(void)ref_mstring(svp->u.str);
|
|
}
|
|
} /* for() */
|
|
|
|
/* Cleanup and return */
|
|
free_svalue(sp);
|
|
sp--;
|
|
free_svalue(sp);
|
|
|
|
put_array(sp, list);
|
|
return sp;
|
|
|
|
#undef VISTAG_INVIS
|
|
#undef VISTAG_VIS
|
|
#undef VISTAG_ALL
|
|
#undef VISTAG_NAMED
|
|
|
|
#undef FILTERFLAGS
|
|
} /* f_functionlist() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
v_variable_exists (svalue_t *sp, int num_arg)
|
|
|
|
/* EXEC variable_exists()
|
|
*
|
|
* string variable_exists (string str [, int flags])
|
|
* string variable_exists (string str , object ob, [, int flags])
|
|
*
|
|
* Look up a variable <str> in the current object, respectively
|
|
* in the object <ob>.
|
|
*
|
|
* The result is the name of the program the variable is defined in. This can
|
|
* be either object_name(ob), or the name of an inherited program. If !compat
|
|
* mode, the returned name always begins with a '/'.
|
|
*
|
|
* If <flags> can be passed as NAME_HIDDEN to return information about static
|
|
* and protected variables in other objects. It is not possible to return
|
|
* information about private variables.
|
|
*
|
|
* If the variable cannot be found (because it doesn't exist or
|
|
* it is not visible to the caller), the result is 0.
|
|
*/
|
|
|
|
{
|
|
string_t *str;
|
|
svalue_t *argp;
|
|
object_t *ob;
|
|
p_int mode_flags;
|
|
|
|
/* Evaluate arguments */
|
|
argp = sp - num_arg + 1;
|
|
|
|
ob = NULL;
|
|
mode_flags = 0;
|
|
|
|
if (num_arg < 2)
|
|
{
|
|
ob = current_object;
|
|
mode_flags = 0;
|
|
}
|
|
|
|
if (num_arg >= 2)
|
|
{
|
|
if (argp[1].type == T_NUMBER)
|
|
{
|
|
ob = current_object;
|
|
mode_flags = argp[1].u.number;
|
|
|
|
if (mode_flags != 0 && mode_flags != NAME_HIDDEN)
|
|
{
|
|
errorf("Bad argument 2 to variable_exists(): "
|
|
"value %"PRIdPINT", expected 0 or %d (NAME_HIDDEN).\n"
|
|
, mode_flags, NAME_HIDDEN
|
|
);
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
}
|
|
else if (argp[1].type == T_OBJECT)
|
|
{
|
|
ob = argp[1].u.ob;
|
|
mode_flags = 0;
|
|
}
|
|
}
|
|
|
|
if (num_arg >= 3)
|
|
{
|
|
/* The last argument must be a number. On the other
|
|
* side, we can't have two numbers at once.
|
|
*/
|
|
if (argp[1].type != T_OBJECT)
|
|
{
|
|
errorf("Bad argument 2 to variable_exists(): "
|
|
"got %s, expected object.\n", typename(argp[1].type));
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
|
|
mode_flags = argp[2].u.number;
|
|
|
|
if (mode_flags != 0 && mode_flags != NAME_HIDDEN)
|
|
{
|
|
errorf("Bad argument 3 to variable_exists(): "
|
|
"value %"PRIdPINT", expected 0 or %d (NAME_HIDDEN).\n"
|
|
, mode_flags, NAME_HIDDEN
|
|
);
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
}
|
|
|
|
|
|
if (ob->flags & O_DESTRUCTED)
|
|
{
|
|
errorf("Bad argument to variable_exists(): Object is destructed.\n");
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
|
|
#ifdef USE_SWAP
|
|
/* Make the program resident */
|
|
if (O_PROG_SWAPPED(ob))
|
|
{
|
|
ob->time_of_ref = current_time;
|
|
if (load_ob_from_swap(ob) < 0)
|
|
errorf("Out of memory: unswap object '%s'\n", get_txt(ob->name));
|
|
}
|
|
#endif
|
|
|
|
/* Get the information */
|
|
str = NULL;
|
|
|
|
do
|
|
{
|
|
string_t *shared_name;
|
|
program_t *progp;
|
|
int ix;
|
|
typeflags_t flags;
|
|
|
|
shared_name = find_tabled(argp->u.str);
|
|
if (!shared_name)
|
|
break;
|
|
|
|
progp = ob->prog;
|
|
|
|
/* Check if the function exists at all */
|
|
for (ix = 0; ix < progp->num_variables; ix++)
|
|
{
|
|
if (mstreq(shared_name, progp->variables[ix].name))
|
|
break;
|
|
}
|
|
|
|
if (ix >= progp->num_variables)
|
|
break;
|
|
|
|
/* Is it visible for the caller? */
|
|
flags = progp->variables[ix].type.typeflags;
|
|
|
|
if (!(mode_flags & NAME_HIDDEN)
|
|
&& ( (flags & TYPE_MOD_PRIVATE)
|
|
|| ((flags & TYPE_MOD_PROTECTED) && current_object != ob))
|
|
)
|
|
break;
|
|
|
|
/* Resolve inheritance */
|
|
while (flags & NAME_INHERITED)
|
|
{
|
|
int ic;
|
|
|
|
for (ic = 0; ic < progp->num_inherited; ic++)
|
|
{
|
|
inherit_t *ip = &progp->inherit[ic];
|
|
|
|
if (ix >= ip->variable_index_offset + ip->prog->num_variables
|
|
|| ix < ip->variable_index_offset
|
|
)
|
|
continue;
|
|
ix -= ip->variable_index_offset;
|
|
progp = ip->prog;
|
|
flags = progp->variables[ix].type.typeflags;
|
|
}
|
|
}
|
|
|
|
/* progp now points to the program which really defines
|
|
* the variable var.
|
|
*/
|
|
|
|
/* We got it. */
|
|
str = progp->name;
|
|
} while(0);
|
|
|
|
/* Put the result onto the stack */
|
|
sp = pop_n_elems(num_arg, sp);
|
|
|
|
if (str)
|
|
{
|
|
string_t *res;
|
|
|
|
res = cvt_progname(str);
|
|
push_string(sp, res);
|
|
}
|
|
else
|
|
{
|
|
push_number(sp, 0);
|
|
}
|
|
|
|
/* str had no ref on its own */
|
|
|
|
return sp;
|
|
} /* v_variable_exists() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
f_variable_list (svalue_t *sp)
|
|
|
|
/* EFUN variable_list()
|
|
*
|
|
* mixed *variable_list (object ob, int flags = RETURN_FUNCTION_NAME)
|
|
*
|
|
* Return an array with information about <ob>s variables. For every
|
|
* variable, 1 to 4 values (depending on <flags>) are stored in
|
|
* the result array conveying in this order:
|
|
* - the name of the variable
|
|
* - the variable flags (see below)
|
|
* - the return type (listed in mudlib/sys/lpctypes.h)
|
|
* - the value of the variable
|
|
*
|
|
* <ob> may be given as true object or as a filename. In the latter
|
|
* case, the efun does not try to load the object before proceeding.
|
|
*
|
|
* If <ob> is not the current object and the value of the variable is
|
|
* requested, a privilege_violation ("variable_list", <ob>) occurs.
|
|
*
|
|
* <flags> determines both which information is returned for every
|
|
* variable, and which variables should be considered at all.
|
|
* Its value is created by bin-or'ing together following flags from
|
|
* mudlib/sys/functionlist.h:
|
|
*
|
|
* Control of returned information:
|
|
* RETURN_FUNCTION_NAME include the variable name
|
|
* RETURN_FUNCTION_FLAGS include the variable flags
|
|
* RETURN_FUNCTION_TYPE include the return type
|
|
* RETURN_VARIABLE_VALUE include the variable value
|
|
*
|
|
* Control of listed variables:
|
|
* NAME_INHERITED don't list if defined by inheritance
|
|
* TYPE_MOD_NOSAVE ==
|
|
* TYPE_MOD_STATIC don't list if nosave ('static') variable
|
|
* TYPE_MOD_PRIVATE don't list if private
|
|
* TYPE_MOD_PROTECTED don't list if protected
|
|
* NAME_HIDDEN don't list if not visible through inheritance
|
|
*
|
|
* The 'flags' information consists of the bin-or of the list control
|
|
* flags given above, plus the following:
|
|
*
|
|
* TYPE_MOD_VIRTUAL variable is inherited virtually
|
|
* TYPE_MOD_NO_MASK variable is nomask
|
|
* TYPE_MOD_PUBLIC variable is public
|
|
*
|
|
* All these flags are defined in mudlib/sys/functionlist.h, which
|
|
* should be copied into an accessible place in the mudlib. The
|
|
* return types are defined in mudlib/sys/lpctypes.h which also
|
|
* should be copied into the mudlib.
|
|
*
|
|
* TODO: All these defs are in mudlib/sys/functionlist.h and mudlib/sys/lpctypes.h
|
|
* TODO:: as well as in exec.h and this file. This should be centralized.
|
|
* TODO:: Maybe write the files on mud startup?
|
|
* TODO:: Include mudlib/sys/functionlist.h doesn't help because then
|
|
* TODO:: mkdepend stumbles over the embedded include <sys/lpctypes.h>.
|
|
*/
|
|
|
|
{
|
|
object_t *ob; /* <ob> argument to list */
|
|
mp_int mode_flags; /* <flags> argument */
|
|
program_t *prog; /* <ob>'s program */
|
|
unsigned short num_variables; /* Number of variables to list */
|
|
char *vis_tags;
|
|
/* Bitflag array describing the visibility of every variable in prog
|
|
* in relation to the passed <flags>: */
|
|
#define VISTAG_INVIS '\0' /* Variable should not be listed */
|
|
#define VISTAG_VIS '\1' /* Variable matches the <flags> list criterium */
|
|
#define VISTAG_ALL '\2' /* Variable should be listed, no list restrictions */
|
|
#define VISTAG_NAMED '\4' /* Variable is neither hidden nor private */
|
|
|
|
#define FILTERFLAGS (NAME_HIDDEN|TYPE_MOD_PRIVATE|TYPE_MOD_STATIC|TYPE_MOD_PROTECTED|NAME_INHERITED)
|
|
|
|
vector_t *list; /* Result vector */
|
|
svalue_t *svp; /* Last element in list which was filled in. */
|
|
variable_t *var; /* Current variable under examination */
|
|
uint32 flags;
|
|
long i, j;
|
|
|
|
inter_sp = sp; /* In case of errors leave a clean stack */
|
|
|
|
/* Extract the arguments from the vm stack.
|
|
*/
|
|
if (sp[-1].type != T_OBJECT)
|
|
{
|
|
if (!(ob = find_object(sp[-1].u.str)))
|
|
errorf("Object '%s' not found.\n", get_txt(sp[-1].u.str));
|
|
}
|
|
else
|
|
ob = sp[-1].u.ob;
|
|
|
|
mode_flags = sp->u.number;
|
|
|
|
if (ob != current_object && (mode_flags & RETURN_VARIABLE_VALUE))
|
|
{
|
|
assert_master_ob_loaded();
|
|
if (privilege_violation(STR_VARIABLE_LIST, sp-1, sp) <= 0)
|
|
{
|
|
free_svalue(sp);
|
|
sp--;
|
|
free_svalue(sp);
|
|
sp->type = T_NUMBER;
|
|
sp->u.number = 0;
|
|
return sp;
|
|
}
|
|
}
|
|
|
|
#ifdef USE_SWAP
|
|
if (O_PROG_SWAPPED(ob))
|
|
if (load_ob_from_swap(ob) < 0)
|
|
{
|
|
errorf("Out of memory: unswap object '%s'\n", get_txt(ob->name));
|
|
/* NOTREACHED */
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
prog = ob->prog;
|
|
|
|
/* Initialize the vistag[] flag array.
|
|
*/
|
|
num_variables = prog->num_variables;
|
|
vis_tags = alloca(num_variables);
|
|
if (!vis_tags)
|
|
{
|
|
errorf("Stack overflow in variable_list()");
|
|
/* NOTREACHED */
|
|
return NULL;
|
|
}
|
|
|
|
/* Preset the visibility. By default, if there is any listing
|
|
* modifier, the variables are not visible. If there is none, the
|
|
* variables are visible.
|
|
*/
|
|
memset( vis_tags, (mode_flags & FILTERFLAGS) ? VISTAG_INVIS : VISTAG_ALL
|
|
, num_variables);
|
|
|
|
/* Count how many named variables need to be listed in the result.
|
|
* Flag every variable to list in vistag[].
|
|
*/
|
|
num_variables = 0;
|
|
|
|
/* First, check all variables for which we have a name */
|
|
flags = mode_flags & (FILTERFLAGS ^ NAME_HIDDEN);
|
|
var = prog->variables;
|
|
i = prog->num_variables;
|
|
while ( --i >= 0 ) {
|
|
if (!(var[i].type.typeflags & flags) )
|
|
{
|
|
vis_tags[i] = VISTAG_NAMED|VISTAG_VIS;
|
|
num_variables++;
|
|
}
|
|
else
|
|
{
|
|
vis_tags[i] |= VISTAG_NAMED;
|
|
}
|
|
}
|
|
|
|
/* If the user wants to see the hidden or private variables, we loop
|
|
* through the full variable table and check all variables not yet
|
|
* touched by the previous 'named' scan.
|
|
* TODO: Due to the dedicated 'find hidden name' loop, this shouldn't
|
|
* TODO:: be necessary, nor the VISTAG_ALL at all.
|
|
*/
|
|
if ((mode_flags & (TYPE_MOD_PRIVATE|NAME_HIDDEN)) == 0)
|
|
{
|
|
var = prog->variables;
|
|
for (i = prog->num_variables; --i >= 0; )
|
|
{
|
|
if (!(vis_tags[i] & VISTAG_NAMED)
|
|
&& !(var[i].type.typeflags & flags)
|
|
)
|
|
{
|
|
vis_tags[i] = VISTAG_VIS;
|
|
num_variables++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If <flags> accepts all variables, use the total number of variables
|
|
* instead of the count computed above.
|
|
*/
|
|
if ( !(mode_flags & FILTERFLAGS))
|
|
{
|
|
num_variables = prog->num_variables;
|
|
}
|
|
|
|
/* Compute the size of the result vector to
|
|
* 2**(number of RETURN_FUNCTION_ bits set)
|
|
*/
|
|
for (i = mode_flags & RETURN_VARIABLE_MASK, j = 0; i; i >>= 1) {
|
|
if (i & 1)
|
|
j += num_variables;
|
|
}
|
|
|
|
/* Allocate the result vector and set svp to its end
|
|
*/
|
|
list = allocate_array(j);
|
|
svp = list->item + j;
|
|
|
|
/* Loop backwards through all variables, check their flags if
|
|
* they are to be listed and store the requested data in
|
|
* the result vector.
|
|
*/
|
|
|
|
for (i = prog->num_variables, var = prog->variables + i; --i >= 0; )
|
|
{
|
|
uint32 active_flags; /* A variable's definition status flags */
|
|
var--;
|
|
|
|
if ((vis_tags[i] & (VISTAG_ALL|VISTAG_VIS)) == VISTAG_INVIS)
|
|
continue; /* Don't list this one */
|
|
|
|
flags = var->type.typeflags;
|
|
|
|
active_flags = (flags & ~INHERIT_MASK);
|
|
if (!(vis_tags[i] & VISTAG_NAMED))
|
|
active_flags |= NAME_HIDDEN;
|
|
|
|
/* Add the data to the result vector as <flags> determines.
|
|
*/
|
|
|
|
if (mode_flags & RETURN_VARIABLE_VALUE)
|
|
{
|
|
svp--;
|
|
assign_svalue_no_free(svp, &ob->variables[i]);
|
|
}
|
|
|
|
if (mode_flags & RETURN_FUNCTION_TYPE)
|
|
{
|
|
svp--;
|
|
svp->u.number = var->type.typeflags & TYPE_MOD_MASK;
|
|
}
|
|
|
|
if (mode_flags & RETURN_FUNCTION_FLAGS)
|
|
{
|
|
svp--;
|
|
svp->u.number = (p_int)active_flags;
|
|
}
|
|
|
|
if (mode_flags & RETURN_FUNCTION_NAME) {
|
|
svp--;
|
|
put_ref_string(svp, var->name);
|
|
}
|
|
} /* for() */
|
|
|
|
/* Cleanup and return */
|
|
free_svalue(sp);
|
|
sp--;
|
|
free_svalue(sp);
|
|
|
|
put_array(sp, list);
|
|
return sp;
|
|
|
|
#undef VISTAG_INVIS
|
|
#undef VISTAG_VIS
|
|
#undef VISTAG_ALL
|
|
#undef VISTAG_NAMED
|
|
|
|
#undef FILTERFLAGS
|
|
} /* f_variable_list() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
v_include_list (svalue_t *sp, int num_arg)
|
|
|
|
/* EFUN include_list()
|
|
*
|
|
* string* include_list ()
|
|
* string* include_list (object ob)
|
|
* string* include_list (object ob, int flags)
|
|
*
|
|
* Return a list with the names of all files included by the program
|
|
* of object <ob>, including <ob>'s program file itself.
|
|
*/
|
|
|
|
{
|
|
object_t *ob; /* Analyzed object */
|
|
vector_t *vec; /* Result vector */
|
|
int count; /* Total number of includes */
|
|
svalue_t *argp; /* Arguments */
|
|
include_t *includes; /* Pointer to the include information */
|
|
p_int flags;
|
|
|
|
/* Get the arguments */
|
|
argp = sp - num_arg + 1;
|
|
|
|
if (num_arg >= 1)
|
|
ob = argp[0].u.ob;
|
|
else
|
|
ob = current_object;
|
|
|
|
if (num_arg >= 2)
|
|
flags = argp[1].u.number;
|
|
else
|
|
flags = 0;
|
|
|
|
#ifdef USE_SWAP
|
|
if (O_PROG_SWAPPED(ob))
|
|
if (load_ob_from_swap(ob) < 0)
|
|
{
|
|
errorf("Out of memory: unswap object '%s'\n", get_txt(ob->name));
|
|
/* NOTREACHED */
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
/* Create the result.
|
|
* Depending on the flags value, this can be a flat list or a tree.
|
|
*/
|
|
|
|
if (!(flags & INCLIST_TREE))
|
|
{
|
|
svalue_t *svp;
|
|
|
|
/* Get the result array */
|
|
vec = allocate_array((ob->prog->num_includes+1) * 3);
|
|
svp = vec->item;
|
|
|
|
/* Walk the includes information and copy it into the result vector
|
|
*/
|
|
for ( svp = vec->item+3
|
|
, count = ob->prog->num_includes
|
|
, includes = ob->prog->includes
|
|
; count > 0
|
|
; count--, includes++, svp += 3
|
|
)
|
|
{
|
|
int depth;
|
|
|
|
put_ref_string(svp, includes->name);
|
|
put_ref_string(svp+1, includes->filename);
|
|
depth = includes->depth;
|
|
if (depth > 0)
|
|
put_number(svp+2, depth);
|
|
else
|
|
put_number(svp+2, -depth);
|
|
}
|
|
}
|
|
else /* Tree-type result */
|
|
{
|
|
/* Local structure to hold the found programs */
|
|
struct iinfo {
|
|
struct iinfo * next; /* Next structure in flat list */
|
|
int depth; /* Include depth */
|
|
include_t * inc; /* The include information */
|
|
/* The following members are used to recreate the inherit tree */
|
|
int count; /* Number of direct includes */
|
|
struct iinfo * parent; /* Parent include, or NULL */
|
|
struct iinfo * child; /* First child include */
|
|
struct iinfo * sibling; /* Next include on same level */
|
|
/* These members are used to create the result tree */
|
|
size_t index; /* # of this include file in the parent
|
|
* vector */
|
|
vector_t * vec; /* Result vector for this include */
|
|
} *begin, *end; /* Flat list of all found includes */
|
|
|
|
struct iinfo * last; /* Last include found on this depth */
|
|
struct iinfo * next; /* Next include to work */
|
|
Mempool pool; /* The memory pool to allocate from */
|
|
|
|
/* Get the memory pool */
|
|
memsafe(pool = new_mempool(size_mempool(sizeof(*begin)))
|
|
, size_mempool(sizeof(*begin)), "memory pool");
|
|
|
|
/* Walk the list of included files and build the tree from it.
|
|
*/
|
|
|
|
begin = mempool_alloc(pool, sizeof(*begin));
|
|
if (NULL == begin)
|
|
{
|
|
mempool_delete(pool);
|
|
outofmem(sizeof(*begin), "allocation from mempool");
|
|
}
|
|
|
|
/* Root node for the object's program itself */
|
|
begin->next = NULL;
|
|
begin->child = NULL;
|
|
begin->sibling = NULL;
|
|
begin->inc = NULL;
|
|
begin->depth = 0;
|
|
begin->count = 0;
|
|
begin->parent = NULL;
|
|
begin->vec = NULL;
|
|
begin->index = 0;
|
|
|
|
end = begin;
|
|
last = begin;
|
|
|
|
includes = ob->prog->includes;
|
|
count = ob->prog->num_includes;
|
|
|
|
for ( ; count > 0; count--, includes++)
|
|
{
|
|
/* Get new node and put it into the flat list */
|
|
end->next = mempool_alloc(pool, sizeof(*end));
|
|
if (NULL == end->next)
|
|
{
|
|
mempool_delete(pool);
|
|
outofmem(sizeof(*end), "allocation from mempool");
|
|
}
|
|
end = end->next;
|
|
end->next = NULL;
|
|
end->inc = includes;
|
|
end->depth = includes->depth > 0 ? includes->depth : - includes->depth;
|
|
|
|
/* Handle the tree-based information */
|
|
end->child = NULL;
|
|
end->sibling = NULL;
|
|
|
|
if (last->depth > end->depth)
|
|
{
|
|
/* We reached a leaf with <last> - this new was included from
|
|
* some parent above.
|
|
*/
|
|
while (last->depth > end->depth)
|
|
last = last->parent;
|
|
|
|
/* Got back up to the right sibling level, no go to the end
|
|
* of the sibling list (just in case - we should already
|
|
* be there).
|
|
*/
|
|
while (last->sibling)
|
|
last = last->sibling;
|
|
}
|
|
/* Now the new file is either a sibling or a child of <last> */
|
|
|
|
if (last->depth == end->depth)
|
|
{
|
|
/* Sibling to <last> */
|
|
last->sibling = end;
|
|
end->parent = last->parent;
|
|
last = end;
|
|
end->parent->count++;
|
|
}
|
|
else /* last->depth < end->depth */
|
|
{
|
|
/* Included from <last> */
|
|
last->child = end;
|
|
last->count++;
|
|
end->parent = last;
|
|
last = end;
|
|
}
|
|
|
|
/* Init the rest */
|
|
end->count = 0;
|
|
end->index = end->parent->count;
|
|
end->vec = NULL;
|
|
}
|
|
|
|
/* Get the top result array and keep a reference to it on the
|
|
* stack so that it will be deallocated on an error.
|
|
*/
|
|
vec = allocate_array((begin->count+1) * 3);
|
|
begin->vec = vec;
|
|
push_array(sp, vec); inter_sp = sp;
|
|
|
|
/* Loop through all the include infos and copy them into
|
|
* their result vector. We create the subvectors when
|
|
* we encounter them.
|
|
* Invariant: <next> points to the next iinfo to work.
|
|
*/
|
|
for (next = begin->child; next != NULL; )
|
|
{
|
|
/* If this child has no includes, we just copy the
|
|
* name into its proper place in the parent vector.
|
|
*
|
|
* Otherwise we create a vector for this include
|
|
* and store the names in there.
|
|
*/
|
|
if (next->child == NULL)
|
|
{
|
|
svalue_t *svp;
|
|
|
|
svp = &next->parent->vec->item[next->index*3];
|
|
put_ref_string(svp, next->inc->name);
|
|
put_ref_string(svp+1, next->inc->filename);
|
|
put_number(svp+2, next->depth);
|
|
|
|
/* If we are in the last sibling, roll back up to
|
|
* the parents.
|
|
*/
|
|
while (next->sibling == NULL && next->parent != NULL)
|
|
next = next->parent;
|
|
|
|
/* Advance to the next sibling. If by */
|
|
next = next->sibling;
|
|
}
|
|
else
|
|
{
|
|
svalue_t *svp;
|
|
|
|
next->vec = allocate_array((next->count+1)*3);
|
|
|
|
svp = &next->parent->vec->item[next->index*3];
|
|
put_array(svp, next->vec);
|
|
/* svp[1] and svp[2] are already 0 */
|
|
|
|
svp = next->vec->item;
|
|
put_ref_string(svp, next->inc->name);
|
|
put_ref_string(svp+1, next->inc->filename);
|
|
put_number(svp+2, next->depth);
|
|
|
|
/* Descend into the first child */
|
|
next = next->child;
|
|
}
|
|
}
|
|
|
|
mempool_delete(pool);
|
|
sp--; /* Remove the temporary storage of vec on the stack */
|
|
}
|
|
|
|
/* Copy the information about the program file itself. */
|
|
|
|
{
|
|
string_t *str;
|
|
size_t slen; /* Also used for error reporting */
|
|
|
|
slen = mstrsize(ob->prog->name);
|
|
|
|
if (compat_mode)
|
|
str = ref_mstring(ob->prog->name);
|
|
else
|
|
str = add_slash(ob->prog->name);
|
|
|
|
if (!str)
|
|
{
|
|
free_array(vec);
|
|
errorf("(include_list) Out of memory: (%zu bytes) for filename\n"
|
|
, slen);
|
|
}
|
|
|
|
put_string(vec->item, str);
|
|
/* vec->item[1] and vec->item[2] are already 0 */
|
|
}
|
|
|
|
/* Done */
|
|
|
|
sp = pop_n_elems(num_arg, sp);
|
|
|
|
sp++;
|
|
put_array(sp, vec);
|
|
return sp;
|
|
} /* v_include_list() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
v_inherit_list (svalue_t *sp, int num_arg)
|
|
|
|
/* EFUN inherit_list()
|
|
*
|
|
* string* inherit_list ()
|
|
* string* inherit_list (object ob)
|
|
* string* inherit_list (object ob, int flags)
|
|
*
|
|
* Return a list with the filenames of all programs inherited by <ob>, include
|
|
* <ob>'s program itself.
|
|
*/
|
|
|
|
{
|
|
/* Local structure to hold the found programs */
|
|
struct iinfo {
|
|
struct iinfo * next; /* Next structure in flat list */
|
|
SBool virtual; /* TRUE: Virtual inherit */
|
|
program_t * prog; /* Program found */
|
|
/* The following members are used to recreate the inherit tree */
|
|
int count; /* Number of direct inherits */
|
|
struct iinfo * parent; /* Parent program, or NULL */
|
|
/* These members are used to create the result tree */
|
|
size_t index; /* # of this inherited program */
|
|
vector_t * vec; /* Result vector for this program */
|
|
} *begin, *end; /* Flat list of all found inherits */
|
|
struct iinfo * next; /* Next program to analyze */
|
|
|
|
Mempool pool; /* The memory pool to allocate from */
|
|
object_t *ob; /* Analyzed object */
|
|
vector_t *vec; /* Result vector */
|
|
svalue_t *svp; /* Pointer to next vec entry to fill in */
|
|
int count; /* Total number of inherits found */
|
|
svalue_t *argp; /* Arguments */
|
|
p_int flags;
|
|
|
|
/* Get the arguments */
|
|
argp = sp - num_arg + 1;
|
|
|
|
if (num_arg >= 1)
|
|
ob = argp[0].u.ob;
|
|
else
|
|
ob = current_object;
|
|
|
|
if (num_arg >= 2)
|
|
flags = argp[1].u.number;
|
|
else
|
|
flags = 0;
|
|
|
|
#ifdef USE_SWAP
|
|
if (O_PROG_SWAPPED(ob))
|
|
if (load_ob_from_swap(ob) < 0) {
|
|
errorf("Out of memory: unswap object '%s'\n", get_txt(ob->name));
|
|
/* NOTREACHED */
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
/* Get the memory pool */
|
|
memsafe(pool = new_mempool(size_mempool(sizeof(*begin)))
|
|
, size_mempool(sizeof(*begin)), "memory pool");
|
|
|
|
/* Perform a breadth search on ob's inherit tree and append the
|
|
* found programs to the iinfo list while counting them.
|
|
*/
|
|
|
|
begin = mempool_alloc(pool, sizeof(*begin));
|
|
if (NULL == begin)
|
|
{
|
|
mempool_delete(pool);
|
|
outofmem(sizeof(*begin), "allocation from mempool");
|
|
}
|
|
|
|
/* Root node for the object's program itself */
|
|
begin->next = NULL;
|
|
begin->prog = ob->prog;
|
|
begin->virtual = MY_FALSE;
|
|
begin->count = 0;
|
|
begin->parent = NULL;
|
|
begin->vec = NULL;
|
|
begin->index = 0;
|
|
|
|
end = begin;
|
|
|
|
count = 1;
|
|
|
|
for (next = begin; next != NULL; next = next->next)
|
|
{
|
|
int cnt;
|
|
inherit_t *inheritp;
|
|
|
|
cnt = next->prog->num_inherited;
|
|
|
|
/* Store the inherited programs in the list.
|
|
*/
|
|
for (inheritp = &next->prog->inherit[0]; cnt--; inheritp++)
|
|
{
|
|
if (inheritp->inherit_type == INHERIT_TYPE_NORMAL
|
|
|| inheritp->inherit_type == INHERIT_TYPE_VIRTUAL
|
|
)
|
|
{
|
|
count++;
|
|
next->count++;
|
|
|
|
end->next = mempool_alloc(pool, sizeof(*end));
|
|
if (NULL == end->next)
|
|
{
|
|
mempool_delete(pool);
|
|
outofmem(sizeof(*end), "allocation from mempool");
|
|
}
|
|
end = end->next;
|
|
end->next = NULL;
|
|
end->prog = inheritp->prog;
|
|
end->virtual = (inheritp->inherit_type == INHERIT_TYPE_VIRTUAL);
|
|
|
|
/* Handle the tree-based information */
|
|
end->parent = next;
|
|
end->count = 0;
|
|
end->index = next->count;
|
|
end->vec = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Create the result.
|
|
* Depending on the flags value, this can be a flat list or a tree.
|
|
*/
|
|
|
|
if (!(flags & INHLIST_TREE))
|
|
{
|
|
/* Get the result array */
|
|
vec = allocate_array(count);
|
|
|
|
/* Take the filenames of the programs and copy them into
|
|
* the result vector.
|
|
*/
|
|
for (svp = vec->item, next = begin; next != NULL; svp++, next = next->next)
|
|
{
|
|
string_t *str;
|
|
size_t slen; /* Also used for error reporting */
|
|
|
|
slen = mstrsize(next->prog->name);
|
|
|
|
if (compat_mode)
|
|
str = ref_mstring(next->prog->name);
|
|
else
|
|
str = add_slash(next->prog->name);
|
|
|
|
if (str && (flags & INHLIST_TAG_VIRTUAL))
|
|
{
|
|
string_t * str2;
|
|
|
|
slen = mstrsize(str) + 2;
|
|
|
|
if (next->virtual)
|
|
str2 = mstr_add_to_txt("v ", 2, str);
|
|
else
|
|
str2 = mstr_add_to_txt(" ", 2, str);
|
|
|
|
free_mstring(str);
|
|
str = str2;
|
|
}
|
|
|
|
if (!str)
|
|
{
|
|
free_array(vec);
|
|
mempool_delete(pool);
|
|
errorf("(inherit_list) Out of memory: (%zu bytes) for filename\n"
|
|
, slen);
|
|
}
|
|
put_string(svp, str);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Get the top result array and keep a reference to it on the
|
|
* stack so that it will be deallocated on an error.
|
|
*/
|
|
vec = allocate_array(begin->count+1);
|
|
begin->vec = vec;
|
|
push_array(sp, vec); inter_sp = sp;
|
|
|
|
/* Loop through all filenames and copy them into their result
|
|
* vector. Since the list in breadth-order, we can create the
|
|
* sub-vectors when we encounter them.
|
|
*/
|
|
for (next = begin; next != NULL; next = next->next)
|
|
{
|
|
string_t *str;
|
|
size_t slen; /* Also used for error reporting */
|
|
|
|
slen = mstrsize(next->prog->name);
|
|
|
|
if (compat_mode)
|
|
str = ref_mstring(next->prog->name);
|
|
else
|
|
str = add_slash(next->prog->name);
|
|
|
|
if (str && (flags & INHLIST_TAG_VIRTUAL))
|
|
{
|
|
string_t * str2;
|
|
|
|
slen = mstrsize(str) + 2;
|
|
|
|
if (next->virtual)
|
|
str2 = mstr_add_to_txt("v ", 2, str);
|
|
else
|
|
str2 = mstr_add_to_txt(" ", 2, str);
|
|
|
|
free_mstring(str);
|
|
str = str2;
|
|
}
|
|
|
|
if (!str)
|
|
{
|
|
mempool_delete(pool);
|
|
errorf("(inherit_list) Out of memory: (%zu bytes) for filename\n"
|
|
, slen);
|
|
}
|
|
|
|
/* If this child has no inherits, we just copy the
|
|
* name into its proper place in the parent vector.
|
|
* Same for the name of the top program.
|
|
*
|
|
* Otherwise we create a vector for this program
|
|
* and store the name in there.
|
|
*/
|
|
if (begin == next)
|
|
{
|
|
put_string(next->vec->item, str);
|
|
}
|
|
else if (next->count == 0)
|
|
{
|
|
put_string(&next->parent->vec->item[next->index], str);
|
|
}
|
|
else
|
|
{
|
|
next->vec = allocate_array(next->count+1);
|
|
put_array(&next->parent->vec->item[next->index], next->vec);
|
|
put_string(next->vec->item, str);
|
|
}
|
|
}
|
|
|
|
sp--; /* Remove the temporary storage of vec on the stack */
|
|
}
|
|
|
|
mempool_delete(pool);
|
|
|
|
sp = pop_n_elems(num_arg, sp);
|
|
|
|
push_array(sp, vec);
|
|
return sp;
|
|
} /* v_inherit_list() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
f_load_name (svalue_t *sp)
|
|
|
|
/* EFUN load_name()
|
|
*
|
|
* string load_name()
|
|
* string load_name(object obj)
|
|
* string load_name(string obj)
|
|
*
|
|
* Return the load name for the object <obj> which may be given
|
|
* directly or by its name.
|
|
*
|
|
* If <obj> is a clone, return the load_name() of <obj>'s blueprint.
|
|
* If <obj> is a blueprint, return the filename from which the
|
|
* blueprint was compiled.
|
|
*
|
|
* If <obj> is given by name but not/no longer existing, the
|
|
* function synthesizes the load name as it should be and returns
|
|
* that. If the given name is illegal, the function returns 0.
|
|
*
|
|
* For virtual objects this efun of course returns the virtual
|
|
* filename. If <obj> is omitted, the name for the current object is
|
|
* returned.
|
|
*
|
|
* As a special case, if <obj> is 0, the function returns 0.
|
|
*
|
|
* In contrast to the object_name(), the load name can not be changed
|
|
* by with rename_object(). However, if an object uses
|
|
* replace_program() the load name no longer reflects the actual
|
|
* behaviour of an object.
|
|
*
|
|
* The returned name starts with a '/', unless the driver is running
|
|
* in COMPAT mode.
|
|
*/
|
|
|
|
{
|
|
string_t *s; /* String argument */
|
|
char *name; /* Result string, maybe 's' itself */
|
|
char *hash; /* Position of the hash in the name */
|
|
char *mem; /* Allocated memory blocks */
|
|
object_t *ob;
|
|
|
|
/* If the argument is 0, just return 0. */
|
|
if (sp->type == T_NUMBER)
|
|
{
|
|
return sp;
|
|
}
|
|
|
|
/* If the argument is an object, we just need to read the name */
|
|
if (sp->type == T_OBJECT)
|
|
{
|
|
s = sp->u.ob->load_name;
|
|
free_object_svalue(sp);
|
|
put_ref_string(sp, s);
|
|
return sp;
|
|
}
|
|
|
|
/* Argument is a string: try to find the object for it */
|
|
s = sp->u.str;
|
|
ob = find_object(s);
|
|
if (ob)
|
|
{
|
|
/* Got it */
|
|
s = ob->load_name;
|
|
free_string_svalue(sp);
|
|
put_ref_string(sp, s);
|
|
return sp;
|
|
}
|
|
|
|
/* There is no object for the string argument: just normalize
|
|
* the string. First check if it ends in #<number>.
|
|
*/
|
|
mem = NULL;
|
|
hash = strchr(get_txt(s), '#');
|
|
if (!hash)
|
|
{
|
|
/* No '#' at all: make the name sane directly */
|
|
name = (char *)make_name_sane(get_txt(s), !compat_mode);
|
|
if (!name)
|
|
name = get_txt(s);
|
|
}
|
|
else
|
|
{
|
|
char *p;
|
|
size_t len;
|
|
|
|
/* All characters after the '#' must be digits */
|
|
for (p = hash+1; '\0' != *p; p++)
|
|
if (*p < '0' || *p > '9')
|
|
/* Illegal name: break to return svalue 0 */
|
|
break;
|
|
|
|
if ('\0' != *p)
|
|
{
|
|
/* Illegal name: break to return svalue 0 */
|
|
free_string_svalue(sp);
|
|
put_number(sp, 0);
|
|
return sp;
|
|
}
|
|
|
|
/* Good, we can slash off the '#<number>' */
|
|
len = (size_t)(hash - get_txt(s));
|
|
p = mem = xalloc(len+1);
|
|
if (!p)
|
|
errorf("(load_name) Out of memory (%zu bytes) for filename."
|
|
, len+1);
|
|
strncpy(p, get_txt(s), len);
|
|
p[len] = '\0';
|
|
|
|
/* Now make the name sane */
|
|
name = (char *)make_name_sane(p, !compat_mode);
|
|
if (!name)
|
|
name = p;
|
|
}
|
|
|
|
/* name now points to the synthesized load_name and
|
|
* may be the argument (== s), allocated (== mem), or
|
|
* points to a static buffer otherwise.
|
|
*/
|
|
|
|
/* '/.c' is a legal object name, so make sure that
|
|
* the result will be '/' (in compat mode).
|
|
*/
|
|
if (compat_mode && '\0' == *name)
|
|
name = "/";
|
|
|
|
/* Now return the result */
|
|
if (get_txt(s) != name)
|
|
{
|
|
free_string_svalue(sp);
|
|
put_c_string(sp, name);
|
|
}
|
|
|
|
if (mem)
|
|
xfree(mem);
|
|
|
|
return sp;
|
|
} /* f_load_name() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
f_object_name (svalue_t *sp)
|
|
|
|
/* EFUN object_name()
|
|
*
|
|
* string object_name()
|
|
* string object_name(object ob)
|
|
*
|
|
* Get the name of an object <ob> or, if no argument is given, of
|
|
* the current object.
|
|
*
|
|
* As a special case, if <obj> is 0, the function returns 0.
|
|
*
|
|
* This name is the name under which the object is stored in the
|
|
* muds object table. It is initialised at the creation of the
|
|
* object such that blueprints are named after the file they are
|
|
* compiled from (without the trailing '.c'), and clones receive
|
|
* the name of their blueprint, extended by '#' followed by
|
|
* a unique non-negative number. These rules also apply to
|
|
* virtual objects - the real name/type of virtual objects
|
|
* is ignored.
|
|
*
|
|
* The name of an object can be changed with rename_object(), and
|
|
* object_name() will reflect any of these changes.
|
|
*
|
|
* The returned name always begins with '/' (absolute path),
|
|
* except when the parser runs in COMPAT mode.
|
|
*/
|
|
|
|
{
|
|
string_t *name, *res;
|
|
|
|
/* If the argument is 0, just return 0. */
|
|
if (sp->type == T_NUMBER)
|
|
{
|
|
return sp;
|
|
}
|
|
|
|
name = sp->u.ob->name;
|
|
if (compat_mode)
|
|
res = ref_mstring(name);
|
|
else
|
|
res = add_slash(name);
|
|
if (!res)
|
|
errorf("Out of memory\n");
|
|
free_object_svalue(sp);
|
|
put_string(sp, res);
|
|
|
|
return sp;
|
|
} /* f_object_name() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
f_object_time (svalue_t *sp)
|
|
|
|
/* EFUN object_time()
|
|
*
|
|
* int object_time()
|
|
* int object_time(object ob)
|
|
*
|
|
* Returns the creation time of the object.
|
|
* Default is this_object(), if no arg is given.
|
|
*/
|
|
|
|
{
|
|
mp_int load_time;
|
|
|
|
load_time = sp->u.ob->load_time;
|
|
|
|
free_object_svalue(sp);
|
|
put_number(sp, load_time);
|
|
|
|
return sp;
|
|
} /* f_object_time() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
f_program_name (svalue_t *sp)
|
|
|
|
/* EFUN program_name()
|
|
*
|
|
* string program_name()
|
|
* string program_name(object obj)
|
|
*
|
|
* Returns the name of the program of <obj>, resp. the name of the
|
|
* program of the current object if <obj> is omitted.
|
|
*
|
|
* The returned name is usually the name from which the blueprint
|
|
* of <obj> was compiled (the 'load name'), but changes if an
|
|
* object replaces its programs with the efun replace_program().
|
|
*
|
|
* As a special case, if <ob> is 0, the function returns 0.
|
|
*
|
|
* The name always ends in '.c'. It starts with a '/' unless the
|
|
* driver is running in COMPAT mode.
|
|
*/
|
|
|
|
{
|
|
string_t *name, *res;
|
|
object_t *ob;
|
|
|
|
/* If the argument is 0, just return 0. */
|
|
if (sp->type == T_NUMBER)
|
|
{
|
|
return sp;
|
|
}
|
|
|
|
ob = sp->u.ob;
|
|
#ifdef USE_SWAP
|
|
if (O_PROG_SWAPPED(ob))
|
|
{
|
|
ob->time_of_ref = current_time;
|
|
if (load_ob_from_swap(ob) < 0)
|
|
{
|
|
errorf("Out of memory: unswap object '%s'\n", get_txt(ob->name));
|
|
}
|
|
}
|
|
#endif
|
|
name = ob->prog->name;
|
|
if (compat_mode)
|
|
res = ref_mstring(name);
|
|
else
|
|
res = add_slash(name);
|
|
if (!res)
|
|
errorf("Out of memory\n");
|
|
free_object_svalue(sp);
|
|
put_string(sp, res);
|
|
|
|
return sp;
|
|
} /* f_program_name() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
f_program_time (svalue_t *sp)
|
|
|
|
/* EFUN program_time()
|
|
*
|
|
* int program_time()
|
|
* int program_time(object ob)
|
|
*
|
|
* Returns the creation (compilation) time of the object's
|
|
* program. Default is this_object(), if no arg is given.
|
|
*/
|
|
|
|
{
|
|
mp_int load_time;
|
|
|
|
#ifdef USE_SWAP
|
|
if (O_PROG_SWAPPED(sp->u.ob))
|
|
{
|
|
sp->u.ob->time_of_ref = current_time;
|
|
if (load_ob_from_swap(sp->u.ob) < 0)
|
|
{
|
|
sp--;
|
|
errorf("Out of memory: unswap object '%s'\n", get_txt(sp->u.ob->name));
|
|
}
|
|
}
|
|
#endif
|
|
load_time = sp->u.ob->prog->load_time;
|
|
|
|
free_object_svalue(sp);
|
|
put_number(sp, load_time);
|
|
|
|
return sp;
|
|
} /* f_program_time() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
f_query_once_interactive (svalue_t *sp)
|
|
|
|
/* EFUN query_once_interactive()
|
|
*
|
|
* int query_once_interactive(object ob)
|
|
*
|
|
* True if the object is or once was interactive.
|
|
*/
|
|
|
|
{
|
|
object_t *obj;
|
|
|
|
obj = sp->u.ob;
|
|
put_number(sp, obj->flags & O_ONCE_INTERACTIVE ? 1 : 0);
|
|
deref_object(obj, "query_once_interactive");
|
|
|
|
return sp;
|
|
} /* f_query_once_interactive() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
f_rename_object (svalue_t *sp)
|
|
|
|
/* EFUN rename_object()
|
|
*
|
|
* void rename_object (object ob, string new_name);
|
|
*
|
|
* Give the object <ob> a new object name <new_name>. Causes a privilege
|
|
* violation. The new name must not contain a # character, except
|
|
* at the end, to avoid confusion with clone numbers.
|
|
*
|
|
* Raises a privilege violation ("rename_object", this_object(), ob, name).
|
|
*/
|
|
|
|
{
|
|
object_t *ob; /* object to be renamed */
|
|
char *name; /* new name as c-string */
|
|
string_t *m_name; /* new name */
|
|
size_t length; /* length of new name */
|
|
Bool freenamebuffer = MY_FALSE; /* free name when not needed */
|
|
|
|
inter_sp = sp; /* this is needed for assert_master_ob_loaded(), and for
|
|
* the possible errors before.
|
|
*/
|
|
ob = sp[-1].u.ob;
|
|
name = get_txt(sp[0].u.str);
|
|
|
|
/* Remove leading '/' if any. */
|
|
while(name[0] == '/')
|
|
name++;
|
|
|
|
/* Truncate possible .c in the object name. */
|
|
length = strlen(name);
|
|
if (name[length-2] == '.' && name[length-1] == 'c') {
|
|
/* A new writeable copy of the name is needed. */
|
|
char *p;
|
|
p = xalloc(length+1);
|
|
if (!p)
|
|
errorf("Out of memory for %zu bytes in rename_object().\n",
|
|
length+1);
|
|
|
|
strcpy(p, name);
|
|
name = p;
|
|
name[length -= 2] = '\0';
|
|
freenamebuffer = MY_TRUE;
|
|
}
|
|
|
|
/* check for any #xxx at the end. */
|
|
{
|
|
char c;
|
|
char *p;
|
|
mp_int i;
|
|
|
|
i = length;
|
|
p = name + length;
|
|
while (--i > 0)
|
|
{
|
|
/* isdigit would need to check isascii first... */
|
|
if ( (c = *--p) < '0' || c > '9' )
|
|
{
|
|
if (c == '#' && length - i > 1) {
|
|
if (freenamebuffer)
|
|
xfree(name);
|
|
errorf("Illegal name to rename_object: '%s'.\n", name);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_name = new_mstring(name);
|
|
if (!m_name)
|
|
{
|
|
if (freenamebuffer)
|
|
xfree(name);
|
|
errorf("Out of memory for object name (%zu bytes)\n", strlen(name));
|
|
}
|
|
/* in case of errors (e.g. in privilege_violation()), push string on the
|
|
* stack. */
|
|
push_string(sp, m_name);
|
|
inter_sp = sp;
|
|
|
|
/* name is not needed anymore. Free it, if it was allocated here. */
|
|
if (freenamebuffer)
|
|
{
|
|
xfree(name);
|
|
/* just to be sure it crashes if somebody uses the pointer by
|
|
* accident. */
|
|
name = NULL;
|
|
}
|
|
|
|
assert_master_ob_loaded();
|
|
if (master_ob == ob)
|
|
{
|
|
errorf("Attempt to rename the master object\n");
|
|
}
|
|
|
|
|
|
if (lookup_object_hash(m_name))
|
|
{
|
|
errorf("Attempt to rename to existing object '%s'\n",
|
|
get_txt(m_name));
|
|
}
|
|
|
|
if (privilege_violation4(STR_RENAME_OBJECT, ob, m_name, 0, sp)
|
|
&& check_object(ob)
|
|
)
|
|
{
|
|
remove_object_hash(ob);
|
|
free_mstring(ob->name);
|
|
ob->name = m_name;
|
|
// m_name needs another reference (one from the stack, one from
|
|
// object->name).
|
|
ref_mstring(m_name);
|
|
enter_object_hash(ob);
|
|
}
|
|
|
|
/* free the string m_name (on the top of the stack) and the 2 arguments */
|
|
sp = pop_n_elems(3, sp);
|
|
|
|
return sp;
|
|
} /* f_rename_object() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
v_replace_program (svalue_t *sp, int num_arg)
|
|
|
|
/* EFUN replace_program()
|
|
*
|
|
* void replace_program()
|
|
* void replace_program(string program)
|
|
*
|
|
* Substitutes a program with the inherited program <program>. If the object
|
|
* inherits only one program, the argument may be omitted and the efun will
|
|
* automatically select the one inherited program.
|
|
*
|
|
* This efun is useful if you consider the performance and memory consumption
|
|
* of the driver. A program which doesn't need any additional variables and
|
|
* functions (except during creation) can call replace_program() to increase
|
|
* the function-cache hit-rate of the driver which decreases with the number
|
|
* of programs in the system. Any object can call replace_program() but looses
|
|
* all extra variables and functions which are not defined by the inherited
|
|
* program.
|
|
*
|
|
* When replace_program() takes effect, shadowing is stopped on the object
|
|
* since 3.2@166.
|
|
*
|
|
* It is not possible to replace the program of an object after (lambda)
|
|
* closures have been bound to it. It is of course possible to first replace
|
|
* the program and then bind lambda closures to it.
|
|
*
|
|
* The program replacement does not take place with the call to the efun, but
|
|
* is merely scheduled to be carried out at the end of the backend cycle. This
|
|
* may cause closures to have references to then vanished lfuns of the object.
|
|
* This poses no problem as long as these references are never executed after
|
|
* they became invalid.
|
|
*/
|
|
|
|
{
|
|
replace_ob_t *tmp;
|
|
program_t *new_prog; /* the replacing program */
|
|
program_t *curprog; /* the current program */
|
|
int offsets[2]; /* the offsets of the replacing prog */
|
|
|
|
if (!current_object)
|
|
errorf("replace_program called with no current object\n");
|
|
if (current_object == simul_efun_object)
|
|
errorf("replace_program on simul_efun object\n");
|
|
|
|
if (current_object->flags & O_LAMBDA_REFERENCED)
|
|
{
|
|
inter_sp = sp;
|
|
warnf("Object '%s', program '%s': Cannot schedule "
|
|
"replace_program() after binding lambda closures.\n"
|
|
, get_txt(current_object->name)
|
|
, get_txt(current_prog->name)
|
|
);
|
|
sp = pop_n_elems(num_arg, sp);
|
|
return sp;
|
|
}
|
|
|
|
curprog = current_object->prog;
|
|
|
|
if (num_arg < 1)
|
|
{
|
|
/* Just take the first inherited program */
|
|
size_t replace_index; /* Inherit index of the replacing program */
|
|
|
|
/* Just take the first normal inherited program */
|
|
if (curprog->num_inherited < 1)
|
|
errorf("replace_program called with no inherited program\n");
|
|
replace_index = 0;
|
|
if (curprog->num_inherited > 1)
|
|
{
|
|
/* The object might have extra inherits caused by virtual
|
|
* variables. Since they preceed the associated 'real'
|
|
* inherit, search forward in the inherit list for the real
|
|
* one.
|
|
*/
|
|
for ( ; replace_index < curprog->num_inherited
|
|
; replace_index++)
|
|
{
|
|
if (!(curprog->inherit[replace_index].inherit_type
|
|
& INHERIT_TYPE_EXTRA))
|
|
break;
|
|
}
|
|
/* replace_index must now be the last inherit for the
|
|
* auto-replace_program to work.
|
|
*/
|
|
if (replace_index + 1 != curprog->num_inherited)
|
|
{
|
|
errorf("replace_program() requires argument for object "
|
|
"with more than one inherit\n");
|
|
/* NOTREACHED */
|
|
}
|
|
}
|
|
|
|
new_prog = curprog->inherit[replace_index].prog;
|
|
offsets[0] = curprog->inherit[replace_index].variable_index_offset;
|
|
offsets[1] = curprog->inherit[replace_index].function_index_offset;
|
|
}
|
|
else
|
|
{
|
|
string_t *sname;
|
|
{ /* block for limiting the scope of name until the xfree. */
|
|
size_t name_len;
|
|
char *name;
|
|
|
|
/* Create the full program name with a trailing '.c' and without
|
|
* a leading '/' to match the internal name representation.
|
|
*/
|
|
name_len = mstrsize(sp->u.str);
|
|
name = xalloc(name_len+3);
|
|
if (!name)
|
|
{
|
|
errorf("Out of memory (%zu bytes) for temporary name buffer in "
|
|
"replace_program.\n", name_len);
|
|
}
|
|
strcpy(name, get_txt(sp->u.str));
|
|
if (name[name_len-2] != '.' || name[name_len-1] != 'c')
|
|
strcat(name,".c");
|
|
if (*name == '/')
|
|
sname = new_mstring(name+1);
|
|
else
|
|
sname = new_mstring(name);
|
|
/* name is not needed anymore, free it first, before throwing any
|
|
* runtime errors. */
|
|
xfree(name);
|
|
|
|
/* now check if we got a string from new_mstring(). */
|
|
if (!sname)
|
|
{
|
|
errorf("Out of memory (%zu bytes) for temporary name in "
|
|
"replace_program().\n", name_len+3);
|
|
}
|
|
} /* scope of name ends here */
|
|
|
|
new_prog = search_inherited(sname, current_object->prog, offsets);
|
|
if (!new_prog)
|
|
{
|
|
/* Given program not inherited, maybe it's the current already.
|
|
*/
|
|
if (mstreq(sname, curprog->name ))
|
|
{
|
|
new_prog = curprog;
|
|
offsets[0] = offsets[1] = 0;
|
|
}
|
|
else
|
|
{
|
|
free_mstring(sname);
|
|
errorf("replacement program '%s' needs to be inherited\n"
|
|
, get_txt(sp->u.str));
|
|
}
|
|
}
|
|
|
|
free_mstring(sname);
|
|
|
|
free_svalue(sp);
|
|
sp--;
|
|
|
|
} /* if (num_arg) */
|
|
|
|
/* Program found, now check if it contains virtual variables.
|
|
* See b-030119 for an explanation.
|
|
*/
|
|
if (offsets[0] != 0)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < new_prog->num_variables; i++)
|
|
{
|
|
if (new_prog->variables[i].type.typeflags & TYPE_MOD_VIRTUAL)
|
|
{
|
|
warnf("Object '%s', program '%s': Cannot schedule "
|
|
"replace_program(): "
|
|
"replacement program '%s' has virtual variables "
|
|
"but is not the first inherited program\n"
|
|
, get_txt(current_object->name)
|
|
, get_txt(current_prog->name)
|
|
, get_txt(new_prog->name)
|
|
);
|
|
return sp;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Program ok, now create a new replace program entry, or
|
|
* change an existing one.
|
|
*/
|
|
if (!(curprog->flags & P_REPLACE_ACTIVE)
|
|
|| !(tmp = retrieve_replace_program_entry()) )
|
|
{
|
|
tmp = xalloc(sizeof *tmp);
|
|
tmp->lambda_rpp = NULL;
|
|
tmp->ob = current_object;
|
|
tmp->next = obj_list_replace;
|
|
obj_list_replace = tmp;
|
|
curprog->flags |= P_REPLACE_ACTIVE;
|
|
}
|
|
|
|
tmp->new_prog = new_prog;
|
|
tmp->var_offset = offsets[0];
|
|
tmp->fun_offset = offsets[1];
|
|
|
|
return sp;
|
|
} /* v_replace_program() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
f_set_next_reset (svalue_t *sp)
|
|
|
|
/* EFUN set_next_reset()
|
|
*
|
|
* int set_next_reset (int delay)
|
|
*
|
|
* Instruct the gamedriver to reset this object not earlier than in
|
|
* <delay> seconds. If a negative value is given as delay, the object
|
|
* will never reset (useful for blueprints). If 0 is given, the
|
|
* object's reset time is not changed.
|
|
*
|
|
* Result is the former delay to the objects next reset (which can be
|
|
* negative if the reset was overdue).
|
|
*/
|
|
|
|
{
|
|
int new_time;
|
|
|
|
new_time = sp->u.number;
|
|
if (current_object->flags & O_DESTRUCTED)
|
|
sp->u.number = 0;
|
|
else
|
|
{
|
|
sp->u.number = current_object->time_reset - current_time;
|
|
if (new_time < 0)
|
|
current_object->time_reset = 0;
|
|
else if (new_time > 0)
|
|
current_object->time_reset = new_time + current_time;
|
|
}
|
|
return sp;
|
|
} /* f_set_next_reset() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
f_tell_object (svalue_t *sp)
|
|
|
|
/* EFUN tell_object()
|
|
*
|
|
* void tell_object(object|string ob, string str)
|
|
* void tell_object(object|string ob, mixed msg)
|
|
*
|
|
* Send a message str to object ob. If it is an interactive
|
|
* object (a user), then the message will go to him (her?),
|
|
* otherwise the lfun catch_tell() of the living will be called
|
|
* with the message as argument.
|
|
* If the object is given as its filename, the driver
|
|
* looks up the object under that name, loading it if necessary.
|
|
*
|
|
* If the second arg is an array, catch_msg() will be called in
|
|
* the receiving living.
|
|
*/
|
|
|
|
{
|
|
object_t * ob = NULL;
|
|
svalue_t *arg = sp - 1;
|
|
|
|
/* Get the arguments */
|
|
if (arg[0].type == T_OBJECT)
|
|
ob = arg[0].u.ob;
|
|
else if (arg[0].type == T_STRING)
|
|
{
|
|
ob = get_object(arg[0].u.str);
|
|
if (!ob)
|
|
errorf("Object not found: %s.\n", get_txt(arg[0].u.str));
|
|
}
|
|
|
|
if (arg[1].type == T_STRING)
|
|
{
|
|
tell_object(ob, sp->u.str);
|
|
free_svalue(sp);
|
|
}
|
|
else
|
|
{
|
|
apply(STR_CATCH_MSG, ob, 1);
|
|
/* Will pop the <msg> at sp from the stack. */
|
|
}
|
|
|
|
sp--;
|
|
free_svalue(sp);
|
|
sp--;
|
|
|
|
return sp;
|
|
} /* f_tell_object() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
f_export_uid (svalue_t *sp)
|
|
|
|
/* EFUN export_uid()
|
|
*
|
|
* void export_uid(object ob)
|
|
*
|
|
* Set the uid of object ob to the current object's effective uid.
|
|
* It is only possible when object ob has an effective uid of 0.
|
|
* TODO: seteuid() goes through the mudlib, why not this one, too?
|
|
* TODO:: Actually, this efun is redundant, archaic and should
|
|
* TODO:: vanish altogether.
|
|
*/
|
|
|
|
{
|
|
object_t *ob;
|
|
|
|
if (!current_object->eff_user)
|
|
errorf("Illegal to export uid 0\n");
|
|
ob = sp->u.ob;
|
|
if (!ob->eff_user) /* Only allowed to export when null */
|
|
ob->user = current_object->eff_user;
|
|
free_object(ob, "export_uid");
|
|
sp--;
|
|
|
|
return sp;
|
|
} /* f_export_uid() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
f_geteuid (svalue_t *sp)
|
|
|
|
/* EFUN geteuid()
|
|
*
|
|
* string geteuid(object ob)
|
|
*
|
|
* Get the effective user-id of the object (mostly a wizard or
|
|
* domain name). Standard objects cloned by this object will get
|
|
* that userid. The effective userid is also used for checking access
|
|
* permissions. If ob is omitted, is this_object() as default.
|
|
*/
|
|
|
|
{
|
|
object_t *ob;
|
|
|
|
ob = sp->u.ob;
|
|
|
|
if (ob->eff_user && ob->eff_user->name)
|
|
{
|
|
string_t *tmp;
|
|
tmp = ref_mstring(ob->eff_user->name);
|
|
free_svalue(sp);
|
|
put_string(sp, tmp);
|
|
}
|
|
else
|
|
{
|
|
free_svalue(sp);
|
|
put_number(sp, 0);
|
|
}
|
|
|
|
return sp;
|
|
} /* f_geteuid() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
f_seteuid (svalue_t *sp)
|
|
|
|
/* EFUN seteuid()
|
|
*
|
|
* int seteuid(string str)
|
|
*
|
|
* Set effective uid to str. The calling object must be
|
|
* privileged to do so by the master object. In most
|
|
* installations it can always be set to the current uid of the
|
|
* object, to the uid of the creator of the object file, or to 0.
|
|
*
|
|
* When this value is 0, the current object's uid can be changed
|
|
* by export_uid(), and only then.
|
|
*
|
|
* Objects with euid 0 cannot load or clone other objects.
|
|
*/
|
|
|
|
{
|
|
svalue_t *ret;
|
|
svalue_t *argp;
|
|
|
|
argp = sp;
|
|
if (argp->type == T_NUMBER)
|
|
{
|
|
/* Clear the euid of this_object */
|
|
|
|
if (argp->u.number != 0)
|
|
efun_arg_error(1, T_STRING, sp->type, sp);
|
|
current_object->eff_user = 0;
|
|
free_svalue(argp);
|
|
put_number(argp, 1);
|
|
return sp;
|
|
}
|
|
|
|
/* Call the master to clear this use of seteuid() */
|
|
|
|
push_ref_valid_object(sp, current_object, "seteuid");
|
|
push_ref_string(sp, argp->u.str);
|
|
inter_sp = sp;
|
|
ret = apply_master(STR_VALID_SETEUID, 2);
|
|
if (!ret || ret->type != T_NUMBER || ret->u.number != 1)
|
|
{
|
|
if (out_of_memory)
|
|
{
|
|
errorf("Out of memory\n");
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
free_svalue(argp);
|
|
put_number(argp, 0);
|
|
}
|
|
else
|
|
{
|
|
current_object->eff_user = add_name(argp->u.str);
|
|
free_svalue(argp);
|
|
put_number(argp, 1);
|
|
}
|
|
|
|
return argp;
|
|
} /* f_seteuid() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
f_getuid (svalue_t *sp)
|
|
|
|
/* EFUN getuid()
|
|
*
|
|
* string getuid(object ob)
|
|
* string creator(object ob)
|
|
*
|
|
* User-ids are not used in compat mode, instead the uid is
|
|
* then called 'creator'.
|
|
* Get user-id of the object, i.e. the name of the wizard or
|
|
* domain that is responsible for the object. This name is also
|
|
* the name used in the wizlist. If no arg is given, use
|
|
* this_object() as default.
|
|
*/
|
|
|
|
{
|
|
object_t *ob;
|
|
string_t *name;
|
|
|
|
ob = sp->u.ob;
|
|
deref_object(ob, "getuid");
|
|
if ( NULL != (name = ob->user->name) )
|
|
put_ref_string(sp, name);
|
|
else
|
|
put_number(sp, 0);
|
|
|
|
return sp;
|
|
} /* f_getuid() == f_creator() */
|
|
|
|
|
|
#ifdef USE_INVENTORIES
|
|
|
|
/*=========================================================================*/
|
|
/* INVENTORIES */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
#ifdef USE_SET_LIGHT
|
|
|
|
void
|
|
add_light (object_t *p, int n)
|
|
|
|
/* The light emission of <p> and all surrounding objects is
|
|
* changed by <n>. This is used by the efun set_light() and when
|
|
* moving and destructing objects.
|
|
*/
|
|
|
|
{
|
|
if (n == 0)
|
|
return;
|
|
do {
|
|
p->total_light += n;
|
|
} while ( NULL != (p = p->super) );
|
|
} /* add_light() */
|
|
#endif /* USE_SET_LIGHT */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
move_object (void)
|
|
|
|
/* Move the object inter_sp[-1] into object inter_sp[0]; both objects
|
|
* are removed from the stack.
|
|
*
|
|
* The actual move performed by the hooks H_MOVE_OBJECT0/1, this
|
|
* function is called to implement the efuns move_object() and transfer().
|
|
*/
|
|
|
|
{
|
|
lambda_t *l;
|
|
object_t *save_command = command_giver;
|
|
|
|
if (NULL != ( l = driver_hook[H_MOVE_OBJECT1].u.lambda) )
|
|
{
|
|
free_object(l->ob, "move_object");
|
|
l->ob = ref_object(inter_sp[-1].u.ob, "move_object");
|
|
call_lambda(&driver_hook[H_MOVE_OBJECT1], 2);
|
|
}
|
|
else if (NULL != ( l = driver_hook[H_MOVE_OBJECT0].u.lambda) )
|
|
{
|
|
free_object(l->ob, "move_object");
|
|
l->ob = ref_object(current_object, "move_object");
|
|
call_lambda(&driver_hook[H_MOVE_OBJECT0], 2);
|
|
}
|
|
else
|
|
errorf("Don't know how to move objects.\n");
|
|
command_giver = check_object(save_command);
|
|
} /* move_object() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
v_all_environment (svalue_t *sp, int num_arg)
|
|
|
|
/* EFUN all_environment()
|
|
*
|
|
* object *all_environment()
|
|
* object *all_environment(object o)
|
|
*
|
|
* Returns an array with all environments object <o> is in. If <o>
|
|
* is omitted, the environments of the current object is returned.
|
|
*
|
|
* If <o> has no environment, or if <o> is destructed, 0 is
|
|
* returned.
|
|
*/
|
|
|
|
{
|
|
object_t *o;
|
|
|
|
/* Get the arg from the stack, if any */
|
|
if (num_arg)
|
|
{
|
|
if (sp->type == T_NUMBER) /* destructed object */
|
|
o = NULL;
|
|
else
|
|
{
|
|
o = ref_object(sp->u.ob, "all_environment");
|
|
free_object_svalue(sp);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
o = current_object;
|
|
sp++;
|
|
}
|
|
|
|
|
|
/* Default return value: 0 */
|
|
put_number(sp, 0);
|
|
|
|
if (o != NULL && !(o->flags & O_DESTRUCTED))
|
|
{
|
|
mp_int num;
|
|
object_t *env;
|
|
vector_t *v;
|
|
svalue_t *svp;
|
|
|
|
/* Count the number of environments */
|
|
for ( num = 0, env = o->super
|
|
; NULL != env
|
|
; num++, env = env->super)
|
|
NOOP;
|
|
|
|
if (num)
|
|
{
|
|
/* Get the array and fill it */
|
|
v = allocate_uninit_array(num);
|
|
for ( svp = v->item, env = o->super
|
|
; NULL != env
|
|
; svp++, env = env->super)
|
|
{
|
|
put_ref_object(svp, env, "all_environment");
|
|
}
|
|
|
|
/* Put the result on the stack and return */
|
|
put_array(sp, v);
|
|
}
|
|
}
|
|
|
|
if (num_arg && o != NULL)
|
|
free_object(o, "all_environment");
|
|
|
|
return sp;
|
|
} /* v_all_environment() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
f_all_inventory (svalue_t *sp)
|
|
|
|
/* EFUN all_inventory()
|
|
*
|
|
* object *all_inventory(object ob = this_object())
|
|
*
|
|
* Returns an array of the objects contained in the inventory
|
|
* of ob.
|
|
*/
|
|
|
|
{
|
|
vector_t *vec;
|
|
object_t *ob;
|
|
object_t *cur; /* Current inventory object */
|
|
int cnt, res;
|
|
|
|
ob = sp->u.ob;
|
|
|
|
/* Count how many inventory objects there are. */
|
|
cnt = 0;
|
|
for (cur = ob->contains; cur; cur = cur->next_inv)
|
|
cnt++;
|
|
|
|
if (!cnt)
|
|
vec = allocate_array(0);
|
|
else
|
|
{
|
|
vec = allocate_array(cnt);
|
|
|
|
/* Copy the object references */
|
|
cur = ob->contains;
|
|
for (res = 0; res < cnt; res++) {
|
|
put_ref_object(vec->item+res, cur, "all_inventory");
|
|
cur = cur->next_inv;
|
|
}
|
|
}
|
|
|
|
free_object_svalue(sp);
|
|
|
|
if (vec == NULL)
|
|
put_number(sp, 0);
|
|
else
|
|
put_array(sp, vec);
|
|
|
|
return sp;
|
|
} /* f_all_inventory() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static int
|
|
deep_inventory_size (object_t *first, p_int level, p_int depth)
|
|
|
|
/* Helper function for deep_inventory()
|
|
*
|
|
* <level> is the current inventory level this function is called for,
|
|
* <depth> is the desired inventory depth (see v_deep_inventory()).
|
|
*
|
|
* Count the size of <ob>'s inventory by counting the contained objects,
|
|
* invoking this function for every object and then returning the sum
|
|
* of all numbers.
|
|
*/
|
|
|
|
{
|
|
int n;
|
|
object_t *ob;
|
|
|
|
n = 0;
|
|
|
|
/* Return immediately if the level exceeds the depth */
|
|
if (depth != 0 && level > (depth > 0 ? depth : -depth))
|
|
return 0;
|
|
|
|
/* Add the objects of this level if depth allows */
|
|
if (depth >= 0 || level == -depth)
|
|
{
|
|
for (ob = first; ob; ob = ob->next_inv)
|
|
n++;
|
|
}
|
|
|
|
/* Recurse into the next level if required */
|
|
if (depth == 0 || (depth > 0 && level < depth) || level < -depth)
|
|
{
|
|
for (ob = first; ob; ob = ob->next_inv)
|
|
if (ob->contains)
|
|
n += deep_inventory_size(ob->contains, level+1, depth);
|
|
}
|
|
|
|
return n;
|
|
} /* deep_inventory_size() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static svalue_t *
|
|
write_deep_inventory (object_t *first, svalue_t *svp, p_int level, p_int depth)
|
|
|
|
/* Helper function for deep_inventory()
|
|
*
|
|
* Copy into <svp> and following a reference to all objects in the
|
|
* inventory chain starting with <first>; then invoke this function
|
|
* for every inventory chain in the found objects.
|
|
*
|
|
* <svp> has to point into a suitably big area of svalue elements, like
|
|
* a vector.
|
|
*
|
|
* <level> is the current inventory level this function is called for,
|
|
* <depth> is the desired inventory depth (see v_deep_inventory()).
|
|
*
|
|
* Result is the updated <svp>, pointing to the next free svalue element
|
|
* in the storage area.
|
|
*/
|
|
|
|
{
|
|
object_t *ob;
|
|
|
|
/* Return immediately if the level exceeds the depth */
|
|
if (depth != 0 && level > (depth > 0 ? depth : -depth))
|
|
return svp;
|
|
|
|
/* Add the objects of this level if depth allows */
|
|
if (depth >= 0 || level == -depth)
|
|
{
|
|
ob = first;
|
|
do {
|
|
put_ref_object(svp, ob, "deep_inventory");
|
|
svp++;
|
|
} while ( NULL != (ob = ob->next_inv) );
|
|
}
|
|
|
|
/* Recurse into the next level if required */
|
|
if (depth == 0 || (depth > 0 && level < depth) || level < -depth)
|
|
{
|
|
ob = first;
|
|
do {
|
|
if (ob->contains)
|
|
svp = write_deep_inventory(ob->contains, svp, level+1, depth);
|
|
} while ( NULL != (ob = ob->next_inv) );
|
|
}
|
|
|
|
return svp;
|
|
} /* write_deep_inventory() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
#if !defined(USE_PARSE_COMMAND)
|
|
static
|
|
#endif
|
|
vector_t *
|
|
deep_inventory (object_t *ob, Bool take_top, p_int depth)
|
|
|
|
/* Return a vector with the full inventory of <ob>, i.e. all objects contained
|
|
* by <ob> and all deep inventories of those objects, too. The resulting
|
|
* vector is created by a recursive breadth search.
|
|
*
|
|
* If <take_top> is true (and <depth> not negative), <ob> itself is included
|
|
* as first element in the result vector.
|
|
*
|
|
* If <depth> is not 0, it determines the depth up to which the inventory
|
|
* is searched (see v_deep_inventory()).
|
|
*
|
|
* The function is used for the efuns deep_inventory() and parse_command().
|
|
*/
|
|
|
|
{
|
|
vector_t *dinv; /* The resulting inventory vector */
|
|
svalue_t *svp; /* Next element to fill in dinv */
|
|
int n; /* Number of elements in dinv */
|
|
|
|
/* Count the contained objects */
|
|
n = (take_top && depth >= 0) ? 1 : 0;
|
|
if (ob->contains) {
|
|
n += deep_inventory_size(ob->contains, 1, depth);
|
|
}
|
|
|
|
/* Get the array */
|
|
dinv = allocate_array(n);
|
|
svp = dinv->item;
|
|
|
|
/* Fill in <ob> if desired */
|
|
if (take_top && depth >= 0) {
|
|
put_ref_object(svp, ob, "deep_inventory");
|
|
svp++;
|
|
}
|
|
|
|
/* Fill in the deep inventory */
|
|
if (ob->contains) {
|
|
write_deep_inventory(ob->contains, svp, 1, depth);
|
|
}
|
|
|
|
return dinv;
|
|
} /* deep_inventory() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
v_deep_inventory (svalue_t *sp, int num_arg)
|
|
|
|
/* EFUN deep_inventory()
|
|
*
|
|
* object *deep_inventory(void)
|
|
* object *deep_inventory(object ob)
|
|
* object *deep_inventory(object ob, int depth)
|
|
*
|
|
* Returns an array of the objects contained in the inventory of
|
|
* ob (or this_object() if no arg given) and in the inventories
|
|
* of these objects, climbing down recursively.
|
|
*
|
|
* If <depth> is given and not 0, the result is limited as follows:
|
|
* <depth> > 0: Only the objects in the first <depth> levels of inventory
|
|
* are returned.
|
|
* <depth> < 0: Only the object in level -<depth> of inventory are returned.
|
|
* In this, level '1' is the inventory of <ob> itself.
|
|
*/
|
|
|
|
{
|
|
vector_t *vec;
|
|
p_int depth = 0;
|
|
|
|
/* Get the depth argument from the stack, if any */
|
|
if (num_arg > 1)
|
|
{
|
|
depth = sp->u.number;
|
|
sp--;
|
|
}
|
|
|
|
/* If no object was given, push the current object onto the stack */
|
|
if (num_arg < 1)
|
|
push_ref_object(sp, current_object, "deep_inventory");
|
|
inter_sp = sp;
|
|
|
|
vec = deep_inventory(sp->u.ob, MY_FALSE, depth);
|
|
|
|
free_object_svalue(sp);
|
|
put_array(sp, vec);
|
|
|
|
return sp;
|
|
} /* f_deep_inventory() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
v_environment (svalue_t *sp, int num_arg)
|
|
|
|
/* EFUN environment()
|
|
*
|
|
* object environment(void)
|
|
* object environment(object obj)
|
|
* object environment(string obj)
|
|
*
|
|
* Returns the surrounding object of obj (which may be specified
|
|
* by name). If no argument is given, it returns the surrounding
|
|
* of the current object.
|
|
*
|
|
* Destructed objects do not have an environment.
|
|
*/
|
|
|
|
{
|
|
object_t *ob;
|
|
|
|
if (num_arg)
|
|
{
|
|
if (sp->type == T_OBJECT)
|
|
{
|
|
ob = sp->u.ob->super;
|
|
free_object_svalue(sp);
|
|
}
|
|
else /* it's a string */
|
|
{
|
|
ob = find_object(sp->u.str);
|
|
if (!ob || ob->super == NULL || (ob->flags & O_DESTRUCTED))
|
|
ob = NULL;
|
|
else
|
|
ob = ob->super;
|
|
free_string_svalue(sp);
|
|
}
|
|
}
|
|
else if (!(current_object->flags & O_DESTRUCTED))
|
|
{
|
|
ob = current_object->super;
|
|
sp++;
|
|
}
|
|
else
|
|
{
|
|
ob = NULL; /* != environment(this_object()) *boggle* */
|
|
sp++;
|
|
}
|
|
|
|
if (ob)
|
|
put_ref_object(sp, ob, "environment");
|
|
else
|
|
put_number(sp, 0);
|
|
|
|
return sp;
|
|
} /* v_environment() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
f_first_inventory (svalue_t *sp)
|
|
|
|
/* EFUN first_inventory()
|
|
*
|
|
* object first_inventory()
|
|
* object first_inventory(string ob)
|
|
* object first_inventory(object ob)
|
|
*
|
|
* Get the first object in the inventory of ob, where ob is
|
|
* either an object or the file name of an object. If ob is not
|
|
* given, the current object is assumed.
|
|
*/
|
|
|
|
{
|
|
object_t *ob;
|
|
|
|
ob = NULL;
|
|
if (sp->type == T_OBJECT)
|
|
{
|
|
ob = sp->u.ob->contains;
|
|
free_object_svalue(sp);
|
|
}
|
|
else if (sp->type == T_STRING)
|
|
{
|
|
ob = get_object(sp->u.str);
|
|
if (!ob)
|
|
errorf("No object '%s' for first_inventory()\n", get_txt(sp->u.str));
|
|
free_string_svalue(sp);
|
|
ob = ob->contains;
|
|
}
|
|
|
|
if (ob)
|
|
put_ref_object(sp, ob, "first_inventory");
|
|
else
|
|
put_number(sp, 0);
|
|
|
|
return sp;
|
|
} /* f_first_inventory() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
f_next_inventory (svalue_t *sp)
|
|
|
|
/* EFUN next_inventory()
|
|
*
|
|
* object next_inventory()
|
|
* object next_inventory(object ob)
|
|
*
|
|
* Get next object in the same inventory as ob. If ob is not
|
|
* given, the current object will be used.
|
|
*
|
|
* This efun is mostly used together with the efun
|
|
* first_inventory().
|
|
*/
|
|
|
|
{
|
|
object_t *ob;
|
|
|
|
ob = sp->u.ob;
|
|
free_object(ob, "next_inventory");
|
|
if (ob->next_inv)
|
|
put_ref_object(sp, ob->next_inv, "next_inventory");
|
|
else
|
|
put_number(sp, 0);
|
|
|
|
return sp;
|
|
} /* f_next_inventory() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
f_move_object (svalue_t *sp)
|
|
|
|
/* EFUN move_object()
|
|
*
|
|
* void move_object(mixed item, mixed dest)
|
|
*
|
|
* The item, which can be a file_name or an object, is moved into
|
|
* it's new environment dest, which can also be file_name or an
|
|
* object.
|
|
*
|
|
* In !compat mode, the only object that can be moved with
|
|
* move_object() is the calling object itself.
|
|
*
|
|
* Since 3.2.1, the innards of move_object() are implemented in
|
|
* the mudlib, using the M_MOVE_OBJECT driver hooks.
|
|
*/
|
|
|
|
{
|
|
object_t *item, *dest;
|
|
|
|
inter_sp = sp;
|
|
|
|
if ((sp-1)->type == T_STRING)
|
|
{
|
|
item = get_object((sp-1)->u.str);
|
|
if (!item)
|
|
errorf("Bad arg 1 to move_object(): object '%s' not found.\n"
|
|
, get_txt(sp[-1].u.str));
|
|
free_string_svalue(sp-1);
|
|
put_ref_object(sp-1, item, "move_object");
|
|
}
|
|
|
|
if (sp->type == T_STRING)
|
|
{
|
|
dest = get_object(sp->u.str);
|
|
if (!dest)
|
|
errorf("Bad arg 2 to move_object(): object '%s' not found.\n"
|
|
, get_txt(sp[0].u.str));
|
|
free_string_svalue(sp);
|
|
put_ref_object(sp, dest, "move_object");
|
|
}
|
|
|
|
/* move_object() reads its arguments directly from the stack */
|
|
move_object();
|
|
sp -= 2;
|
|
|
|
return sp;
|
|
} /* f_move_object() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static object_t *
|
|
object_present_in (string_t *str, object_t *ob, p_int num, p_int * num_matched)
|
|
|
|
/* <ob> is the first object in an environment: test all the objects there
|
|
* if they match the id <str>.
|
|
* If <hasNumber> is false, <str> may be of the form "<id> <num>" - then the
|
|
* <num>th object with this <id> is returned, it it is found.
|
|
* If <hasNumber> is true, the <num>th object with the given id is returned.
|
|
*
|
|
* If the object is not found, *<num_matched> (if not NULL) is set to the
|
|
* number of objects which did match the id.
|
|
*/
|
|
|
|
{
|
|
svalue_t *ret;
|
|
p_int count = 0; /* return the <count+1>th object */
|
|
|
|
if (num_matched)
|
|
*num_matched = 0;
|
|
|
|
count = num-1;
|
|
|
|
/* Now look for the object */
|
|
for (; ob; ob = ob->next_inv)
|
|
{
|
|
push_ref_string(inter_sp, str);
|
|
ret = sapply(STR_ID, ob, 1);
|
|
if (ob->flags & O_DESTRUCTED)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if (ret == NULL || (ret->type == T_NUMBER && ret->u.number == 0))
|
|
continue;
|
|
|
|
if (num_matched)
|
|
(*num_matched)++;
|
|
|
|
if (count-- > 0)
|
|
continue;
|
|
|
|
return ob;
|
|
}
|
|
|
|
/* Not found */
|
|
return NULL;
|
|
} /* object_present_in() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static object_t *
|
|
e_object_present (svalue_t *v, object_t *ob, p_int num)
|
|
|
|
/* Implementation of the efun present().
|
|
*
|
|
* Look for the <num>th object matching <v> in <ob> and return it if found.
|
|
*/
|
|
|
|
{
|
|
svalue_t *ret;
|
|
object_t *ret_ob;
|
|
p_int num_matched = 0;
|
|
Bool specific = MY_FALSE;
|
|
|
|
if (num <= 0)
|
|
num = 1;
|
|
|
|
/* Search where? */
|
|
if (!ob)
|
|
ob = current_object;
|
|
else
|
|
specific = MY_TRUE;
|
|
|
|
if (ob->flags & O_DESTRUCTED)
|
|
return NULL;
|
|
|
|
if (v->type == T_OBJECT)
|
|
{
|
|
/* Oooh, that's easy. */
|
|
|
|
if (specific)
|
|
{
|
|
if (v->u.ob->super == ob)
|
|
return v->u.ob;
|
|
else
|
|
return NULL;
|
|
}
|
|
if (v->u.ob->super == ob
|
|
|| (v->u.ob->super == ob->super && ob->super != 0))
|
|
return v->u.ob;
|
|
return NULL;
|
|
}
|
|
|
|
/* Always search in the object's inventory */
|
|
ret_ob = object_present_in(v->u.str, ob->contains, num, &num_matched);
|
|
if (ret_ob)
|
|
return ret_ob;
|
|
|
|
if (specific)
|
|
return NULL;
|
|
|
|
/* Search in the environment of <ob> if it was not specified */
|
|
if (!specific && ob->super)
|
|
{
|
|
/* Is it _the_ environment? */
|
|
push_ref_string(inter_sp, v->u.str);
|
|
ret = sapply(STR_ID, ob->super, 1);
|
|
if (ob->super->flags & O_DESTRUCTED)
|
|
return NULL;
|
|
if (ret && !(ret->type == T_NUMBER && ret->u.number == 0))
|
|
return ob->super;
|
|
|
|
/* No, search the other objects here. */
|
|
if (num_matched < num)
|
|
return object_present_in(v->u.str, ob->super->contains, num - num_matched, NULL);
|
|
}
|
|
|
|
/* Not found */
|
|
return NULL;
|
|
} /* e_object_present() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
v_present (svalue_t *sp, int num_arg)
|
|
|
|
/* EFUN present()
|
|
*
|
|
* object present(mixed str)
|
|
* object present(mixed str, int n)
|
|
* object present(mixed str, object ob)
|
|
* object present(mixed str, int n, object ob)
|
|
*
|
|
* If an object that identifies (*) to the name ``str'' is present
|
|
* in the inventory or environment of this_object (), then return
|
|
* it. If "str" has the form "<id> <n>" the <n>-th object matching
|
|
* <id> will be returned.
|
|
* it. If "str" has the form "<id> <n>" OR the (str, n) form is used,
|
|
* the <n>-th object matching <id> will be returned.
|
|
*
|
|
* "str" can also be an object, in which case the test is much faster
|
|
* and easier.
|
|
*
|
|
* A second optional argument ob is the enviroment where the search
|
|
* for str takes place. Normally this_player() is a good choice.
|
|
* Only the inventory of ob is searched, not its environment.
|
|
*/
|
|
|
|
{
|
|
svalue_t *arg;
|
|
object_t *ob;
|
|
p_int num = 1;
|
|
Bool hasNumber = MY_FALSE;
|
|
|
|
arg = sp - num_arg + 1;
|
|
|
|
/* Get the arguments */
|
|
ob = NULL;
|
|
if (num_arg == 3)
|
|
{
|
|
ob = arg[2].u.ob;
|
|
free_svalue(sp--);
|
|
num_arg--;
|
|
}
|
|
if (num_arg == 2)
|
|
{
|
|
if (arg[1].type == T_NUMBER)
|
|
{
|
|
num = arg[1].u.number;
|
|
hasNumber = MY_TRUE;
|
|
}
|
|
else if (arg[1].type == T_OBJECT)
|
|
{
|
|
if (ob != NULL)
|
|
{
|
|
/* Two objects? No way. */
|
|
vefun_arg_error(2, T_NUMBER, T_OBJECT, sp);
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
ob = arg[1].u.ob;
|
|
}
|
|
free_svalue(sp--);
|
|
num_arg--;
|
|
}
|
|
|
|
/* If the string is in the form "<id> <number>" and no explicit
|
|
* number was given, parse the <number> out of the string.
|
|
*/
|
|
if (!hasNumber && arg->type == T_STRING)
|
|
{
|
|
int length;
|
|
char *p, *item;
|
|
|
|
length = mstrsize(arg->u.str);
|
|
item = get_txt(arg->u.str);
|
|
|
|
p = item + length - 1;
|
|
if (*p >= '0' && *p <= '9')
|
|
{
|
|
while(p > item && *p >= '0' && *p <= '9')
|
|
p--;
|
|
|
|
if (p > item && *p == ' ')
|
|
{
|
|
num = atoi(p+1);
|
|
length = p - item;
|
|
hasNumber = MY_TRUE;
|
|
}
|
|
}
|
|
|
|
/* If we found a number, replace the "<id> <number>" string on
|
|
* the stack with just "<id>".
|
|
*/
|
|
if (hasNumber)
|
|
{
|
|
string_t * sitem;
|
|
memsafe(sitem = new_n_mstring(item, length), length, "id string");
|
|
free_mstring(arg->u.str);
|
|
arg->u.str = sitem;
|
|
}
|
|
}
|
|
|
|
inter_sp = sp;
|
|
ob = e_object_present(arg, ob, num);
|
|
|
|
free_svalue(arg);
|
|
if (ob)
|
|
put_ref_object(sp, ob, "present");
|
|
else
|
|
put_number(sp, 0);
|
|
|
|
return sp;
|
|
} /* v_present() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
e_say (svalue_t *v, vector_t *avoid)
|
|
|
|
/* Implementation of the EFUN say().
|
|
* <v> is the value to say, <avoid> the array of objects to exclude.
|
|
* If the first element of <avoid> is not an object, the function
|
|
* will store its command_giver object into it.
|
|
*/
|
|
|
|
{
|
|
static svalue_t stmp = { T_OBJECT };
|
|
|
|
object_t *ob;
|
|
object_t *save_command_giver = command_giver;
|
|
object_t *origin;
|
|
string_t *message;
|
|
#define INITIAL_MAX_RECIPIENTS 48
|
|
int max_recipients = INITIAL_MAX_RECIPIENTS;
|
|
/* Current size of the recipients table.
|
|
*/
|
|
object_t *first_recipients[INITIAL_MAX_RECIPIENTS];
|
|
/* Initial table of recipients.
|
|
*/
|
|
object_t **recipients = first_recipients;
|
|
/* Pointer to the current table of recipients.
|
|
* The end is marked with a NULL entry.
|
|
*/
|
|
object_t **curr_recipient = first_recipients;
|
|
/* Next recipient to enter.
|
|
*/
|
|
object_t **last_recipients =
|
|
&first_recipients[INITIAL_MAX_RECIPIENTS-1];
|
|
/* Last entry in the current table.
|
|
*/
|
|
|
|
/* Determine the command_giver to use */
|
|
if (current_object->flags & O_ENABLE_COMMANDS)
|
|
{
|
|
command_giver = current_object;
|
|
}
|
|
#ifdef USE_SHADOWING
|
|
else if (current_object->flags & O_SHADOW
|
|
&& O_GET_SHADOW(current_object)->shadowing)
|
|
{
|
|
command_giver = O_GET_SHADOW(current_object)->shadowing;
|
|
}
|
|
#endif
|
|
|
|
/* Determine the originating object */
|
|
if (command_giver)
|
|
{
|
|
interactive_t *ip;
|
|
|
|
if (O_SET_INTERACTIVE(ip, command_giver))
|
|
{
|
|
trace_level |= ip->trace_level;
|
|
}
|
|
origin = command_giver;
|
|
|
|
/* Save the commandgiver to avoid, if needed */
|
|
if (avoid->item[0].type == T_NUMBER)
|
|
{
|
|
put_ref_object(avoid->item, command_giver, "say");
|
|
}
|
|
}
|
|
else
|
|
origin = current_object;
|
|
|
|
/* Sort the avoid vector for fast lookups.
|
|
* The caller will free the original <avoid>.
|
|
*/
|
|
avoid = order_array(avoid);
|
|
push_array(inter_sp, avoid); /* In case of errors */
|
|
|
|
/* Collect the list of propable recipients.
|
|
* First, look in the environment.
|
|
*/
|
|
if ( NULL != (ob = origin->super) )
|
|
{
|
|
interactive_t *ip;
|
|
|
|
/* The environment itself? */
|
|
if (ob->flags & O_ENABLE_COMMANDS
|
|
|| O_SET_INTERACTIVE(ip, ob))
|
|
{
|
|
*curr_recipient++ = ob;
|
|
}
|
|
|
|
for (ob = ob->contains; ob; ob = ob->next_inv)
|
|
{
|
|
if (ob->flags & O_ENABLE_COMMANDS
|
|
|| O_SET_INTERACTIVE(ip,ob))
|
|
{
|
|
if (curr_recipient >= last_recipients)
|
|
{
|
|
/* Increase the table */
|
|
max_recipients *= 2;
|
|
curr_recipient = alloca(max_recipients * sizeof(object_t *));
|
|
memcpy( curr_recipient, recipients
|
|
, max_recipients * sizeof(object_t *) / 2);
|
|
recipients = curr_recipient;
|
|
last_recipients = &recipients[max_recipients-1];
|
|
curr_recipient += (max_recipients / 2) - 1;
|
|
}
|
|
*curr_recipient++ = ob;
|
|
}
|
|
} /* for() */
|
|
} /* if(environment) */
|
|
|
|
/* Now check this environment */
|
|
for (ob = origin->contains; ob; ob = ob->next_inv)
|
|
{
|
|
interactive_t *ip;
|
|
|
|
if (ob->flags & O_ENABLE_COMMANDS
|
|
|| O_SET_INTERACTIVE(ip, ob))
|
|
{
|
|
if (curr_recipient >= last_recipients)
|
|
{
|
|
/* Increase the table */
|
|
max_recipients *= 2;
|
|
curr_recipient = alloca(max_recipients * sizeof(object_t *));
|
|
memcpy( curr_recipient, recipients
|
|
, max_recipients * sizeof(object_t *) / 2);
|
|
recipients = curr_recipient;
|
|
last_recipients = &recipients[max_recipients-1];
|
|
curr_recipient += (max_recipients / 2) - 1;
|
|
}
|
|
*curr_recipient++ = ob;
|
|
}
|
|
}
|
|
|
|
*curr_recipient = NULL; /* Mark the end of the list */
|
|
|
|
/* Construct the message. */
|
|
|
|
switch(v->type)
|
|
{
|
|
case T_STRING:
|
|
message = v->u.str;
|
|
break;
|
|
|
|
case T_OBJECT:
|
|
case T_POINTER:
|
|
case T_MAPPING:
|
|
#ifdef USE_STRUCTS
|
|
case T_STRUCT:
|
|
#endif /* USE_STRUCTS */
|
|
/* tell_room()'s evil twin: send <v> to all recipients' catch_msg() lfun */
|
|
|
|
for (curr_recipient = recipients; NULL != (ob = *curr_recipient++) ; )
|
|
{
|
|
if (ob->flags & O_DESTRUCTED)
|
|
continue;
|
|
stmp.u.ob = ob;
|
|
if (lookup_key(&stmp, avoid) >= 0)
|
|
continue;
|
|
switch (v->type) {
|
|
case T_OBJECT: push_ref_object(inter_sp, v->u.ob, "say"); break;
|
|
case T_POINTER: push_ref_array(inter_sp, v->u.vec); break;
|
|
case T_MAPPING: psh_ref_mapping(inter_sp, v->u.map); break;
|
|
#ifdef USE_STRUCTS
|
|
case T_STRUCT: push_ref_struct(inter_sp, v->u.strct); break;
|
|
#endif /* USE_STRUCTS */
|
|
}
|
|
push_ref_object(inter_sp, origin, "say");
|
|
sapply(STR_CATCH_MSG, ob, 2);
|
|
}
|
|
pop_stack(); /* free avoid alist */
|
|
command_giver = check_object(save_command_giver);
|
|
return;
|
|
|
|
default:
|
|
errorf("Invalid argument to say(): expected '%s', got '%s'.\n"
|
|
#ifdef USE_STRUCTS
|
|
, efun_arg_typename(T_POINTER|T_MAPPING|T_STRUCT|T_STRING|T_OBJECT)
|
|
#else
|
|
, efun_arg_typename(T_POINTER|T_MAPPING|T_STRING|T_OBJECT)
|
|
#endif /* USE_STRUCTS */
|
|
, typename(v->type));
|
|
}
|
|
|
|
/* Now send the message to all recipients */
|
|
|
|
for (curr_recipient = recipients; NULL != (ob = *curr_recipient++); )
|
|
{
|
|
if (ob->flags & O_DESTRUCTED)
|
|
continue;
|
|
stmp.u.ob = ob;
|
|
if (lookup_key(&stmp, avoid) >= 0)
|
|
continue;
|
|
tell_object (ob, message);
|
|
}
|
|
|
|
pop_stack(); /* free avoid alist */
|
|
command_giver = check_object(save_command_giver);
|
|
} /* e_say() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
v_say (svalue_t *sp, int num_arg)
|
|
|
|
/* EFUN say()
|
|
*
|
|
* void say(string str)
|
|
* void say(string str, object exclude)
|
|
* void say(string str, object *excludes)
|
|
* void say(mixed *arr)
|
|
* void say(mixed *arr, object exclude)
|
|
* void say(mixed *arr, object *excludes)
|
|
*
|
|
* There are two major modes of calling:
|
|
*
|
|
* If the first argument is a string <str>, it will be send to
|
|
* all livings in the current room except to the initiator.
|
|
*
|
|
* If the first argument is an array <arr>, the lfun catch_msg()
|
|
* of all living objects except the initiator will be called.
|
|
* This array will be given as first argument to the lfun, and
|
|
* the initiating object as the second.
|
|
*
|
|
* By specifying a second argument to the efun one can exclude
|
|
* more objects than just the initiator. If the second argument
|
|
* is a single object <exclude>, both the given object and the
|
|
* initiator are excluded from the call. If the second argument
|
|
* is an array <excludes>, all objects and just the objects in
|
|
* this array are excluded from the call.
|
|
*
|
|
* The 'initiator' is determined according to these rules:
|
|
* - if the say() is called from within a living object, this
|
|
* becomes the initiator
|
|
* - if the say() is called from within a dead object as result
|
|
* of a user action (i.e. this_player() is valid), this_player()
|
|
* becomes the initiator.
|
|
* - Else the object calling the say() becomes the initiator.
|
|
*/
|
|
|
|
{
|
|
static LOCAL_VEC2(vtmp, T_NUMBER, T_NUMBER);
|
|
/* Default 'avoid' array passed to say() giving the object
|
|
* to exclude in the second item. The first entry is reserved
|
|
* for e_say() to insert its command_giver object.
|
|
*/
|
|
|
|
if (num_arg == 1)
|
|
{
|
|
/* No objects to exclude */
|
|
|
|
vtmp.v.item[0].type = T_NUMBER;
|
|
/* this marks the place for the command_giver */
|
|
vtmp.v.item[1].type = T_NUMBER;
|
|
/* nothing to exclude... */
|
|
e_say(sp, &vtmp.v);
|
|
}
|
|
else
|
|
{
|
|
/* We have objects to exclude */
|
|
|
|
if ( sp->type == T_POINTER )
|
|
{
|
|
e_say(sp-1, sp->u.vec);
|
|
}
|
|
else /* it's an object */
|
|
{
|
|
vtmp.v.item[0].type = T_NUMBER;
|
|
put_ref_object(vtmp.v.item+1, sp->u.ob, "say");
|
|
e_say(sp-1, &vtmp.v);
|
|
}
|
|
free_svalue(sp--);
|
|
}
|
|
|
|
/* We may have received object references in vtmp - clear them */
|
|
if (vtmp.v.item[0].type != T_NUMBER)
|
|
{
|
|
free_svalue(&(vtmp.v.item[0]));
|
|
vtmp.v.item[0].type = T_NUMBER;
|
|
}
|
|
if (vtmp.v.item[1].type != T_NUMBER)
|
|
{
|
|
free_svalue(&(vtmp.v.item[1]));
|
|
vtmp.v.item[1].type = T_NUMBER;
|
|
}
|
|
|
|
free_svalue(sp--);
|
|
|
|
return sp;
|
|
} /* v_say() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
e_tell_room (object_t *room, svalue_t *v, vector_t *avoid)
|
|
|
|
/* Implementation of the EFUN tell_room().
|
|
*
|
|
* Value <v> is sent to all living objects in <room>, except those
|
|
* in <avoid>. <avoid> has to be in order_array() order.
|
|
*/
|
|
|
|
{
|
|
object_t *ob;
|
|
int num_recipients = 0;
|
|
object_t *some_recipients[20];
|
|
object_t **recipients;
|
|
object_t **curr_recipient;
|
|
string_t *message;
|
|
static svalue_t stmp = { T_OBJECT, } ;
|
|
interactive_t *ip;
|
|
|
|
/* Like in say(), collect the possible recipients.
|
|
* First count how many there are.
|
|
*/
|
|
|
|
for (ob = room->contains; ob; ob = ob->next_inv)
|
|
{
|
|
if ( ob->flags & O_ENABLE_COMMANDS
|
|
|| O_SET_INTERACTIVE(ip, ob))
|
|
{
|
|
num_recipients++;
|
|
}
|
|
}
|
|
/* The room/environment itself? */
|
|
if (room->flags & O_ENABLE_COMMANDS
|
|
|| O_SET_INTERACTIVE(ip, room))
|
|
{
|
|
num_recipients++;
|
|
}
|
|
|
|
/* Allocate the table */
|
|
if (num_recipients < 20)
|
|
recipients = some_recipients;
|
|
else
|
|
recipients =
|
|
alloca( (num_recipients+1) * sizeof(object_t *) );
|
|
|
|
/* Now fill the table */
|
|
curr_recipient = recipients;
|
|
/* The room/environment itself? */
|
|
if (room->flags & O_ENABLE_COMMANDS
|
|
|| O_SET_INTERACTIVE(ip, room))
|
|
{
|
|
*curr_recipient++ = room;
|
|
}
|
|
/* now the objects in the room/container */
|
|
for (ob = room->contains; ob; ob = ob->next_inv)
|
|
{
|
|
if ( ob->flags & O_ENABLE_COMMANDS
|
|
|| O_SET_INTERACTIVE(ip, ob))
|
|
{
|
|
*curr_recipient++ = ob;
|
|
}
|
|
}
|
|
|
|
*curr_recipient = NULL; /* Mark the end of the table */
|
|
|
|
/* Construct the message */
|
|
switch(v->type)
|
|
{
|
|
case T_STRING:
|
|
message = v->u.str;
|
|
break;
|
|
|
|
case T_OBJECT:
|
|
case T_POINTER:
|
|
case T_MAPPING:
|
|
#ifdef USE_STRUCTS
|
|
case T_STRUCT:
|
|
#endif /* USE_STRUCTS */
|
|
{
|
|
/* say()s evil brother: send <v> to all recipients'
|
|
* catch_msg() lfun
|
|
*/
|
|
object_t *origin = command_giver;
|
|
|
|
if (!origin)
|
|
origin = current_object;
|
|
|
|
for (curr_recipient = recipients; NULL != (ob = *curr_recipient++); )
|
|
{
|
|
if (ob->flags & O_DESTRUCTED)
|
|
continue;
|
|
stmp.u.ob = ob;
|
|
if (lookup_key(&stmp, avoid) >= 0)
|
|
continue;
|
|
switch (v->type) {
|
|
case T_OBJECT: push_ref_object(inter_sp, v->u.ob, "tell_room"); break;
|
|
case T_POINTER: push_ref_array(inter_sp, v->u.vec); break;
|
|
case T_MAPPING: psh_ref_mapping(inter_sp, v->u.map); break;
|
|
#ifdef USE_STRUCTS
|
|
case T_STRUCT: push_ref_struct(inter_sp, v->u.strct); break;
|
|
#endif /* USE_STRUCTS */
|
|
}
|
|
push_ref_object(inter_sp, origin, "tell_room");
|
|
sapply(STR_CATCH_MSG, ob, 2);
|
|
}
|
|
return;
|
|
}
|
|
|
|
default:
|
|
errorf("Invalid argument to tell_room(): expected '%s', got '%s'.\n"
|
|
#ifdef USE_STRUCTS
|
|
, efun_arg_typename(T_POINTER|T_MAPPING|T_STRUCT|T_STRING|T_OBJECT)
|
|
#else
|
|
, efun_arg_typename(T_POINTER|T_MAPPING|T_STRING|T_OBJECT)
|
|
#endif /* USE_STRUCTS */
|
|
, typename(v->type));
|
|
}
|
|
|
|
/* Now send the message to all recipients */
|
|
|
|
for (curr_recipient = recipients; NULL != (ob = *curr_recipient++); )
|
|
{
|
|
if (ob->flags & O_DESTRUCTED) continue;
|
|
stmp.u.ob = ob;
|
|
if (lookup_key(&stmp, avoid) >= 0) continue;
|
|
tell_object(ob, message);
|
|
}
|
|
} /* e_tell_room() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
v_tell_room (svalue_t *sp, int num_arg)
|
|
|
|
/* EFUN tell_room()
|
|
*
|
|
* void tell_room(string|object ob, string str)
|
|
* void tell_room(string|object ob, string str, object *exclude)
|
|
* void tell_room(string|object ob, mixed *msg)
|
|
* void tell_room(string|object ob, mixed *msg, object *exclude)
|
|
*
|
|
* Send a message str to all living objects in the room ob. ob
|
|
* can also be the name of the room given as a string. If a
|
|
* receiving object is not a interactive user the lfun
|
|
* catch_tell() of the object will be invoked with the message as
|
|
* argument. If living objects define catch_tell(), the string
|
|
* will also be sent to that instead of being written to the
|
|
* user. If the object is given as its filename, the driver
|
|
* looks up the object under that name, loading it if necessary.
|
|
* If array *exclude is given, all objects contained in
|
|
* *exclude are excluded from the message str.
|
|
*
|
|
* If the second arg is an array, catch_msg() will be called in
|
|
* all listening livings.
|
|
*/
|
|
|
|
{
|
|
svalue_t *arg;
|
|
vector_t *avoid;
|
|
object_t *ob;
|
|
|
|
arg = sp- num_arg + 1;
|
|
|
|
/* Test the arguments */
|
|
if (arg[0].type == T_OBJECT)
|
|
ob = arg[0].u.ob;
|
|
else /* it's a string */
|
|
{
|
|
ob = get_object(arg[0].u.str);
|
|
if (!ob)
|
|
errorf("Object '%s' not found.\n", get_txt(arg[0].u.str));
|
|
}
|
|
|
|
if (num_arg == 2)
|
|
{
|
|
avoid = &null_vector;
|
|
}
|
|
else
|
|
{
|
|
/* Sort the list of objects to exclude for faster
|
|
* operation.
|
|
*/
|
|
avoid = order_array(arg[2].u.vec);
|
|
free_array(arg[2].u.vec);
|
|
sp->u.vec = avoid; /* in case of an error, this will be freed. */
|
|
}
|
|
|
|
e_tell_room(ob, arg+1, avoid);
|
|
|
|
if (num_arg > 2)
|
|
{
|
|
free_svalue(sp--);
|
|
}
|
|
free_svalue(sp--);
|
|
free_svalue(sp--);
|
|
|
|
return sp;
|
|
} /* v_tell_room() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
#ifdef USE_SET_LIGHT
|
|
|
|
svalue_t *
|
|
f_set_light (svalue_t *sp)
|
|
|
|
/* EFUN set_light()
|
|
*
|
|
* int set_light(int n)
|
|
*
|
|
* An object is by default dark. It can be set to not dark by
|
|
* calling set_light(1). The environment will then also get this
|
|
* light. The returned value is the total number of lights in
|
|
* this room. So if you call set_light(0) it will return the
|
|
* light level of the current object.
|
|
*
|
|
* Note that the value of the argument is added to the light of
|
|
* the current object.
|
|
*/
|
|
|
|
{
|
|
object_t *o1;
|
|
|
|
add_light(current_object, sp->u.number);
|
|
o1 = current_object;
|
|
while (o1->super)
|
|
o1 = o1->super;
|
|
sp->u.number = o1->total_light;
|
|
|
|
return sp;
|
|
} /* f_set_light() */
|
|
|
|
#endif /* USE_SET_LIGHT */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
f_set_environment (svalue_t *sp)
|
|
|
|
/* EFUN set_environment()
|
|
*
|
|
* void set_environment(object item, object env)
|
|
*
|
|
* The item is moved into its new environment env, which may be 0.
|
|
* This efun is to be used in the H_MOVE_OBJECTx hook, as it does
|
|
* nothing else than moving the item - no calls to init() or such.
|
|
*/
|
|
|
|
{
|
|
object_t *item, *dest, *ob;
|
|
object_t *save_cmd = command_giver;
|
|
#ifdef USE_SHADOWING
|
|
object_t **pp;
|
|
#endif
|
|
|
|
/* Get and test the arguments */
|
|
|
|
item = sp[-1].u.ob;
|
|
|
|
#ifdef USE_SHADOWING
|
|
if (item->flags & O_SHADOW && O_GET_SHADOW(item)->shadowing)
|
|
errorf("Can't move an object that is shadowing.\n");
|
|
#endif
|
|
|
|
if (sp->type != T_OBJECT)
|
|
{
|
|
dest = NULL;
|
|
}
|
|
else
|
|
{
|
|
dest = sp->u.ob;
|
|
/* Recursive moves are not allowed. */
|
|
for (ob = dest; ob; ob = ob->super)
|
|
if (ob == item)
|
|
errorf("Can't move object inside itself.\n");
|
|
|
|
# ifdef USE_SET_LIGHT
|
|
add_light(dest, item->total_light);
|
|
# endif
|
|
dest->flags &= ~O_RESET_STATE;
|
|
}
|
|
|
|
item->flags &= ~O_RESET_STATE; /* touch it */
|
|
|
|
if (item->super)
|
|
{
|
|
/* First remove the item out of its current environment */
|
|
Bool okey = MY_FALSE;
|
|
|
|
item->super->flags &= ~O_RESET_STATE;
|
|
|
|
#ifdef USE_SHADOWING
|
|
if (item->sent)
|
|
{
|
|
remove_environment_sent(item);
|
|
}
|
|
|
|
if (item->super->sent)
|
|
remove_action_sent(item, item->super);
|
|
#endif
|
|
|
|
# ifdef USE_SET_LIGHT
|
|
add_light(item->super, - item->total_light);
|
|
# endif
|
|
|
|
#ifdef USE_SHADOWING
|
|
for (pp = &item->super->contains; *pp;)
|
|
{
|
|
if (*pp != item)
|
|
{
|
|
if ((*pp)->sent)
|
|
remove_action_sent(item, *pp);
|
|
pp = &(*pp)->next_inv;
|
|
continue;
|
|
}
|
|
*pp = item->next_inv;
|
|
okey = MY_TRUE;
|
|
}
|
|
#endif
|
|
|
|
if (!okey)
|
|
fatal("Failed to find object %s in super list of %s.\n",
|
|
get_txt(item->name), get_txt(item->super->name));
|
|
}
|
|
|
|
/* Now put it into its new environment (if any) */
|
|
item->super = dest;
|
|
if (!dest)
|
|
{
|
|
item->next_inv = NULL;
|
|
}
|
|
else
|
|
{
|
|
item->next_inv = dest->contains;
|
|
dest->contains = item;
|
|
}
|
|
|
|
command_giver = check_object(save_cmd);
|
|
free_svalue(sp);
|
|
sp--;
|
|
free_svalue(sp);
|
|
return sp - 1;
|
|
} /* f_set_environment() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
#ifdef USE_DEPRECATED
|
|
|
|
svalue_t *
|
|
f_transfer (svalue_t *sp)
|
|
|
|
/* EFUN transfer()
|
|
*
|
|
* int transfer(object item, object dest)
|
|
*
|
|
* This efun is for backward compatibility only. It is only
|
|
* available in compat mode.
|
|
*
|
|
* Move the object "item" to the object "dest". All kinds of
|
|
* tests are done, and a number is returned specifying the
|
|
* result:
|
|
*
|
|
* 0: Success.
|
|
* 1: To heavy for destination.
|
|
* 2: Can't be dropped.
|
|
* 3: Can't take it out of it's container.
|
|
* 4: The object can't be inserted into bags etc.
|
|
* 5: The destination doesn't allow insertions of objects.
|
|
* 6: The object can't be picked up.
|
|
*
|
|
* If an object is transfered to a newly created object, make
|
|
* sure that the new object first is transfered to it's
|
|
* destination.
|
|
*
|
|
* The efun calls add_weight(), drop(), get(), prevent_insert(),
|
|
* add_weight(), and can_put_and_get() where needed.
|
|
*/
|
|
|
|
{
|
|
object_t *ob, *to;
|
|
svalue_t *v_weight, *ret;
|
|
int weight;
|
|
object_t *from;
|
|
int result;
|
|
|
|
/* Get and test the arguments */
|
|
ob = sp[-1].u.ob;
|
|
|
|
if (sp->type == T_OBJECT)
|
|
to = sp->u.ob;
|
|
else /* it's a string */
|
|
{
|
|
to = get_object(sp->u.str);
|
|
if (!to)
|
|
errorf("Object %s not found.\n", get_txt(sp->u.str));
|
|
free_string_svalue(sp);
|
|
put_ref_object(sp, to, "transfer"); /* for move_object() below */
|
|
}
|
|
|
|
from = ob->super;
|
|
result = 0; /* Default: success result */
|
|
|
|
/* Perform the transfer step by step */
|
|
switch(0){default:
|
|
|
|
/* Get the weight of the object
|
|
*/
|
|
weight = 0;
|
|
v_weight = sapply(STR_QUERY_WEIGHT, ob, 0);
|
|
if (v_weight && v_weight->type == T_NUMBER)
|
|
weight = v_weight->u.number;
|
|
|
|
if (ob->flags & O_DESTRUCTED)
|
|
{
|
|
result = 3;
|
|
break;
|
|
}
|
|
|
|
/* If the original place of the object is a living object,
|
|
* then we must call drop() to check that the object can be dropped.
|
|
*/
|
|
if (from && (from->flags & O_ENABLE_COMMANDS))
|
|
{
|
|
ret = sapply(STR_DROP, ob, 0);
|
|
if (ret && (ret->type != T_NUMBER || ret->u.number != 0))
|
|
{
|
|
result = 2;
|
|
break;
|
|
}
|
|
|
|
/* This should not happen, but we can not trust LPC hackers. :-) */
|
|
if (ob->flags & O_DESTRUCTED)
|
|
{
|
|
result = 2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If 'from' is not a room and not a player, check that we may
|
|
* remove things out of it.
|
|
*/
|
|
if (from && from->super && !(from->flags & O_ENABLE_COMMANDS))
|
|
{
|
|
ret = sapply(STR_CANPUTGET, from, 0);
|
|
if (!ret
|
|
|| (ret->type == T_NUMBER && ret->u.number == 0)
|
|
|| (from->flags & O_DESTRUCTED))
|
|
{
|
|
result = 3;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If the destination is not a room, and not a player,
|
|
* Then we must test 'prevent_insert', and 'can_put_and_get'.
|
|
*/
|
|
if (to->super && !(to->flags & O_ENABLE_COMMANDS))
|
|
{
|
|
ret = sapply(STR_PREVENT_INSERT, ob, 0);
|
|
if (ret && (ret->type != T_NUMBER || ret->u.number != 0))
|
|
{
|
|
result = 4;
|
|
break;
|
|
}
|
|
|
|
ret = sapply(STR_CANPUTGET, to, 0);
|
|
if (!ret
|
|
|| (ret->type == T_NUMBER && ret->u.number == 0)
|
|
|| (to->flags & O_DESTRUCTED)
|
|
|| (ob->flags & O_DESTRUCTED))
|
|
{
|
|
result = 5;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If the destination is a player, check that he can pick it up.
|
|
*/
|
|
if (to->flags & O_ENABLE_COMMANDS)
|
|
{
|
|
ret = sapply(STR_GET, ob, 0);
|
|
if (!ret
|
|
|| (ret->type == T_NUMBER && ret->u.number == 0)
|
|
|| (ob->flags & O_DESTRUCTED))
|
|
{
|
|
result = 6;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If it is not a room, correct the total weight in
|
|
* the destination.
|
|
*/
|
|
if (to->super && weight)
|
|
{
|
|
/* Check if the destination can carry that much.
|
|
*/
|
|
push_number(inter_sp, weight);
|
|
ret = sapply(STR_ADD_WEIGHT, to, 1);
|
|
if (ret && ret->type == T_NUMBER && ret->u.number == 0)
|
|
{
|
|
result = 1;
|
|
break;
|
|
}
|
|
|
|
if (to->flags & O_DESTRUCTED)
|
|
{
|
|
result = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If it is not a room, correct the weight in
|
|
* the 'from' object.
|
|
*/
|
|
if (from && from->super && weight)
|
|
{
|
|
push_number(inter_sp, -weight);
|
|
(void)sapply(STR_ADD_WEIGHT, from, 1);
|
|
}
|
|
|
|
/* When we come here, the move is ok */
|
|
} /* pseudo-switch() */
|
|
|
|
if (result)
|
|
{
|
|
/* All the applys might have changed these */
|
|
free_svalue(sp);
|
|
free_svalue(sp-1);
|
|
}
|
|
else
|
|
{
|
|
/* The move is ok: do it (and use up both arguments) */
|
|
inter_sp = sp;
|
|
move_object();
|
|
}
|
|
|
|
put_number(sp-1, result);
|
|
return sp-1;
|
|
} /* f_transfer() */
|
|
|
|
#endif /* USE_DEPRECATED */
|
|
|
|
#endif /* USE_INVENTORIES */
|
|
|
|
/*=========================================================================*/
|
|
|
|
/* Save/Restore an Object */
|
|
|
|
/*
|
|
* TODO: The functions don't work properly if an object contains several
|
|
* TODO:: variables of the same name, and their order/location in the
|
|
* TODO:: variable block change between save and restore.
|
|
* TODO: The functions should push an error-handler-svalue on the stack so
|
|
* TODO:: that in case of errors everything (memory, files, svalues) can
|
|
* TODO:: be deallocated properly. Right now, some stuff may be left behind.
|
|
*/
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
/* The 'version' of each savefile is given in the first line as
|
|
* # <version>:<host>
|
|
*
|
|
* <version> is currently 1
|
|
* Version 0 didn't allow the saving of non-lambda closures, symbols
|
|
* or quoted arrays.
|
|
* <host> is 1 for Atari ST and Amiga, and 0 for everything else.
|
|
* The difference lies in the handling of float numbers (see datatypes.h).
|
|
*/
|
|
|
|
#define SAVE_OBJECT_VERSION '1'
|
|
#define CURRENT_VERSION 1
|
|
/* Current version of new save files, expressed as char and as int.
|
|
*/
|
|
|
|
#ifdef FLOAT_FORMAT_0
|
|
# define SAVE_OBJECT_HOST '0'
|
|
# define CURRENT_HOST 0
|
|
#endif
|
|
|
|
#ifdef FLOAT_FORMAT_1
|
|
# define SAVE_OBJECT_HOST '1'
|
|
# define CURRENT_HOST 1
|
|
#endif
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
/* Forward Declarations */
|
|
|
|
static Bool save_svalue(svalue_t *, char, Bool);
|
|
static int restore_size(char **str);
|
|
static Bool restore_svalue(svalue_t *, char **, char);
|
|
static void register_svalue(svalue_t *);
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
#define SAVE_OBJECT_BUFSIZE 4096
|
|
/* Size of the read/write buffer.
|
|
*/
|
|
|
|
static int save_version = -1;
|
|
/* The version of the savefile to write.
|
|
*/
|
|
|
|
static const char save_file_suffix[] = ".o";
|
|
/* The suffix of the save file, in an array for easier computations.
|
|
* (sizeof() vs. strlen()+1.
|
|
*/
|
|
|
|
static struct pointer_table *ptable = NULL;
|
|
/* The pointer_table used to register all arrays and mappings.
|
|
* If an error happens during the save, this table probably won't
|
|
* be deallocated.
|
|
*/
|
|
|
|
static char number_buffer[36];
|
|
/* Buffer to create numbers in - big enough for 32 Bit uints.
|
|
*/
|
|
|
|
static char *save_object_bufstart;
|
|
/* Start of the write buffer (which lives on the stack).
|
|
*/
|
|
|
|
static char *buf_pnt;
|
|
/* Current position in the write buffer.
|
|
*/
|
|
|
|
static int buf_left;
|
|
/* Space left in the write buffer.
|
|
*/
|
|
|
|
static mp_int bytes_written;
|
|
/* Number of bytes so far written to the file or strbuf.
|
|
*/
|
|
|
|
static Bool failed;
|
|
/* An IO error occured.
|
|
*/
|
|
|
|
static int save_object_descriptor = -1;
|
|
/* FD of the savefile, -1 if not assigned.
|
|
*/
|
|
|
|
static strbuf_t save_string_buffer;
|
|
/* When saving to a string: the string buffer.
|
|
*/
|
|
|
|
static mp_int current_sv_id_number;
|
|
/* The highest ID number so far assigned to a shared value when
|
|
* writing a savefile.
|
|
*/
|
|
|
|
static int restored_host = -1;
|
|
/* Type of the host which wrote the savefile being restored.
|
|
*/
|
|
|
|
static long current_shared_restored;
|
|
/* ID of the shared value currently restored
|
|
*/
|
|
|
|
static svalue_t *shared_restored_values = NULL;
|
|
/* Array of restored shared values, so that later references
|
|
* can do a simple lookup by ID-1 (IDs start at 1).
|
|
*/
|
|
|
|
static long max_shared_restored;
|
|
/* Current size of shared_restored_values.
|
|
*/
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
/* Macros */
|
|
|
|
#define MY_PUTC(ch) {\
|
|
*buf_pnt++ = ch;\
|
|
if (!-- buf_left) {\
|
|
buf_pnt = write_buffer();\
|
|
buf_left = SAVE_OBJECT_BUFSIZE;\
|
|
}\
|
|
}
|
|
/* Put <ch> into the write buffer, flushing the buffer to
|
|
* the file if necessary.
|
|
*/
|
|
|
|
|
|
/* The following three macros handle the write buffer access
|
|
* through local variables to achieve a greater speed:
|
|
*/
|
|
|
|
#define L_PUTC_PROLOG char *l_buf_pnt = buf_pnt;\
|
|
int l_buf_left = buf_left;
|
|
/* Declare and initialize the local variables.
|
|
*/
|
|
|
|
#define L_PUTC(ch) {\
|
|
*l_buf_pnt++ = ch;\
|
|
if (!--l_buf_left) {\
|
|
l_buf_pnt = write_buffer();\
|
|
l_buf_left = SAVE_OBJECT_BUFSIZE;\
|
|
}\
|
|
}
|
|
/* Put <ch> into the write buffer, flushing the buffer to
|
|
* the file if necessary.
|
|
*/
|
|
|
|
#define L_PUTC_EPILOG buf_pnt = l_buf_pnt; buf_left = l_buf_left;
|
|
/* Update the global buffer variables with the local values.
|
|
*/
|
|
|
|
#define CTRLZ 30
|
|
/* MS-DOS and Windows files sometimes have this character :-(
|
|
*/
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
void
|
|
free_save_object_buffers(void)
|
|
|
|
/* Deallocate all lingering buffers from previous save operations, preferably
|
|
* before the GC does it.
|
|
*/
|
|
|
|
{
|
|
if (ptable)
|
|
free_pointer_table(ptable);
|
|
ptable = NULL;
|
|
} /* free_save_object_buffers() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static char*
|
|
write_buffer (void)
|
|
|
|
/* Write the current content of the write buffer to the savefile
|
|
* resp. to the string buffer and return a pointer to its start.
|
|
*
|
|
* On an error, set failed to TRUE.
|
|
*/
|
|
|
|
{
|
|
char *start;
|
|
|
|
start = save_object_bufstart;
|
|
if (save_object_descriptor >= 0)
|
|
{
|
|
|
|
if (write( save_object_descriptor, start, SAVE_OBJECT_BUFSIZE )
|
|
!= SAVE_OBJECT_BUFSIZE )
|
|
failed = MY_TRUE;
|
|
}
|
|
else
|
|
strbuf_addn(&save_string_buffer, start, SAVE_OBJECT_BUFSIZE);
|
|
|
|
bytes_written += SAVE_OBJECT_BUFSIZE;
|
|
return start;
|
|
} /* write_buffer() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static Bool
|
|
recall_pointer (void *pointer)
|
|
|
|
/* Lookup the (known to be registered) <pointer> in the pointertable and
|
|
* check the number of registrations.
|
|
*
|
|
* If it was registered just once, just return FALSE.
|
|
* If it was registered several times (ie. it is a shared array/mapping),
|
|
* write its ID number (which is assigned if necessary) as '<id>'
|
|
* to the write buffer. If this is not the first time this particular
|
|
* pointer was recalled, add a '=' and return FALSE, else return TRUE.
|
|
*
|
|
* If the function returns FALSE, the caller has to write the actual
|
|
* data of the array/mapping.
|
|
*/
|
|
|
|
{
|
|
struct pointer_record *record;
|
|
|
|
/* We know for sure that we will find the key, because it has been
|
|
* registered before.
|
|
*/
|
|
record = lookup_pointer(ptable, pointer);
|
|
|
|
if (!record->ref_count)
|
|
/* Used only once. No need for special treatment. */
|
|
return MY_FALSE;
|
|
|
|
if (pointer == (char*)&null_vector)
|
|
/* Sharing enforced by the game driver */
|
|
return MY_FALSE;
|
|
|
|
/* Write the '<id>' text */
|
|
|
|
{
|
|
long old_id, id;
|
|
char *source, c;
|
|
L_PUTC_PROLOG
|
|
|
|
/* If this pointer was recalled the first time, assign
|
|
* an ID number.
|
|
*/
|
|
if ( !(old_id = id = record->id_number) )
|
|
{
|
|
id = ++current_sv_id_number;
|
|
record->id_number = id;
|
|
}
|
|
|
|
/* Write '<id>' */
|
|
|
|
L_PUTC('<')
|
|
source = number_buffer;
|
|
(void)sprintf(source, "%ld", id);
|
|
c = *source++;
|
|
do L_PUTC(c) while ( '\0' != (c = *source++) );
|
|
L_PUTC('>')
|
|
|
|
if (old_id)
|
|
{
|
|
/* has been written before */
|
|
L_PUTC_EPILOG
|
|
return MY_TRUE;
|
|
}
|
|
|
|
/* First encounter: add a '=' */
|
|
L_PUTC('=')
|
|
L_PUTC_EPILOG
|
|
return MY_FALSE;
|
|
}
|
|
|
|
/* NOTREACHED */
|
|
} /* recall_pointer() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
save_string (string_t *src)
|
|
|
|
/* Write string <src> to the write buffer, but escape all funny
|
|
* characters.
|
|
*/
|
|
|
|
{
|
|
register char c, *cp;
|
|
size_t len;
|
|
|
|
L_PUTC_PROLOG
|
|
|
|
L_PUTC('\"')
|
|
len = mstrsize(src);
|
|
cp = get_txt(src);
|
|
while ( len-- )
|
|
{
|
|
c = *cp++;
|
|
if (isescaped(c))
|
|
{
|
|
switch(c) {
|
|
case '\007': c = 'a'; break;
|
|
case '\b' : c = 'b'; break;
|
|
case '\t' : c = 't'; break;
|
|
case '\n' : c = 'n'; break;
|
|
case '\013': c = 'v'; break;
|
|
case '\014': c = 'f'; break;
|
|
case '\r' : c = 'r'; break;
|
|
}
|
|
L_PUTC('\\')
|
|
}
|
|
else if (c == '\0')
|
|
{
|
|
c = '0';
|
|
L_PUTC('\\')
|
|
}
|
|
L_PUTC(c)
|
|
}
|
|
L_PUTC('\"')
|
|
L_PUTC_EPILOG
|
|
} /* save_string() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
save_mapping_filter (svalue_t *key, svalue_t *data, void *extra)
|
|
|
|
/* Filter used by save_mapping: write <key> and (p_int)<extra> values
|
|
* in <data>[] to the write buffer.
|
|
*/
|
|
|
|
{
|
|
int i;
|
|
|
|
i = (p_int)extra;
|
|
#if 0 /* #ifndef LDMUD_COMPAT */
|
|
/* This forces zeros to be written for object keys
|
|
* in mappings and suchlike. Just in case you need it.
|
|
* For a moment I thought I did, but then, no.
|
|
*/
|
|
if (save_svalue(key, (char)(i ? ':' : ','), MY_FALSE))
|
|
#else
|
|
/* The official LDMud behaviour is to forget about
|
|
* mapping entries which have objects as key. A bit weird
|
|
* and inconsequent but.. it's okay.
|
|
*/
|
|
if (save_svalue(key, (char)(i ? ':' : ','), MY_TRUE))
|
|
#endif
|
|
{
|
|
while (--i >= 0)
|
|
save_svalue(data++, (char)(i ? ';' : ','), MY_FALSE );
|
|
}
|
|
} /* save_mapping_filter() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
save_mapping (mapping_t *m)
|
|
|
|
/* Write the mapping <m> to the write buffer.
|
|
* Empty mappings with width != 1 are written as '([:<width>])'.
|
|
*/
|
|
|
|
{
|
|
mp_int old_written;
|
|
|
|
/* If it is shared, write its ID, and maybe we're already
|
|
* done then.
|
|
*/
|
|
if ( recall_pointer(m) )
|
|
return;
|
|
|
|
/* Nope, write it */
|
|
|
|
MY_PUTC('(')
|
|
MY_PUTC('[')
|
|
check_map_for_destr(m);
|
|
old_written = bytes_written - buf_left;
|
|
walk_mapping(m, save_mapping_filter, (void *)(p_int)m->num_values);
|
|
|
|
/* If the mapping is empty and has width other than 1,
|
|
* use a special format
|
|
*/
|
|
if (m->num_values != 1 && old_written == bytes_written - buf_left)
|
|
{
|
|
char *source, c;
|
|
|
|
MY_PUTC(':')
|
|
source = number_buffer;
|
|
(void)sprintf(source, "%"PRIdPINT, m->num_values);
|
|
c = *source++;
|
|
do MY_PUTC(c) while ( '\0' != (c = *source++) );
|
|
}
|
|
|
|
MY_PUTC(']')
|
|
MY_PUTC(')')
|
|
} /* save_mapping() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
save_array (vector_t *v)
|
|
|
|
/* Encode the array <v> and write it to the write buffer.
|
|
*/
|
|
|
|
{
|
|
p_int i;
|
|
svalue_t *val;
|
|
|
|
/* Recall the array from the pointer table.
|
|
* If it is a shared one, there's nothing else to do.
|
|
*/
|
|
if (recall_pointer(v))
|
|
return;
|
|
|
|
/* Write the '(<'... */
|
|
{
|
|
L_PUTC_PROLOG
|
|
L_PUTC('(')
|
|
L_PUTC('{')
|
|
L_PUTC_EPILOG
|
|
}
|
|
|
|
/* ... the values ... */
|
|
for (i = VEC_SIZE(v), val = v->item; --i >= 0; )
|
|
{
|
|
save_svalue(val++, ',', MY_FALSE);
|
|
}
|
|
|
|
/* ... and the '>)' */
|
|
{
|
|
L_PUTC_PROLOG
|
|
L_PUTC('}')
|
|
L_PUTC(')')
|
|
L_PUTC_EPILOG
|
|
}
|
|
} /* save_array() */
|
|
|
|
#ifdef USE_STRUCTS
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
save_struct (struct_t *st)
|
|
|
|
/* Encode the struct <st> and write it to the write buffer.
|
|
*/
|
|
|
|
{
|
|
long i;
|
|
svalue_t *val;
|
|
|
|
/* Recall the struct from the pointer table.
|
|
* If it is a shared one, there's nothing else to do.
|
|
*/
|
|
if (recall_pointer(st))
|
|
return;
|
|
|
|
/* Write the '(<'... */
|
|
{
|
|
L_PUTC_PROLOG
|
|
L_PUTC('(')
|
|
L_PUTC('<')
|
|
L_PUTC_EPILOG
|
|
}
|
|
|
|
/* The unique name (struct_name prog_name #id) as fake member */
|
|
if (save_version < 1 || !recall_pointer(struct_unique_name(st)))
|
|
{
|
|
save_string(struct_unique_name(st));
|
|
}
|
|
{
|
|
L_PUTC_PROLOG
|
|
L_PUTC(',')
|
|
L_PUTC_EPILOG
|
|
}
|
|
|
|
/* ... the values ... */
|
|
for (i = (long)struct_size(st), val = st->member; --i >= 0; )
|
|
{
|
|
save_svalue(val++, ',', MY_FALSE);
|
|
}
|
|
|
|
/* ... and the '>)' */
|
|
{
|
|
L_PUTC_PROLOG
|
|
L_PUTC('>')
|
|
L_PUTC(')')
|
|
L_PUTC_EPILOG
|
|
}
|
|
} /* save_struct() */
|
|
#endif /* USE_STRUCTS */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static Bool
|
|
save_closure (svalue_t *cl, Bool writable)
|
|
|
|
/* Encode the struct <st> and write it to the write buffer.
|
|
* If <writable> is false, unwritable closure are written
|
|
* as '0'. If <writable> is true, unwritable closures are not written at all.
|
|
*
|
|
* Return is true if something was written, and false otherwise.
|
|
*/
|
|
|
|
{
|
|
Bool rc = MY_TRUE;
|
|
int type;
|
|
|
|
switch(type = cl->x.closure_type)
|
|
{
|
|
case CLOSURE_LFUN:
|
|
{
|
|
if (recall_pointer(cl->u.lambda))
|
|
break;
|
|
|
|
if (cl->u.lambda->function.lfun.ob == current_object
|
|
&& cl->u.lambda->ob == current_object
|
|
)
|
|
{
|
|
lambda_t *l;
|
|
program_t *prog;
|
|
program_t *inhProg = 0;
|
|
int ix;
|
|
funflag_t flags;
|
|
string_t *function_name;
|
|
char *source, c;
|
|
object_t *ob;
|
|
|
|
l = cl->u.lambda;
|
|
ob = l->function.lfun.ob;
|
|
ix = l->function.lfun.index;
|
|
inhProg = l->function.lfun.inhProg;
|
|
|
|
prog = ob->prog;
|
|
|
|
if (inhProg)
|
|
{
|
|
/* An inherited lfun closure. Go to the inherit. */
|
|
while (prog != inhProg)
|
|
{
|
|
inherit_t *inheritp;
|
|
|
|
SEARCH_FUNCTION_INHERIT(inheritp, prog, ix);
|
|
ix -= inheritp->function_index_offset;
|
|
prog = inheritp->prog;
|
|
}
|
|
}
|
|
|
|
flags = prog->functions[ix];
|
|
|
|
while (flags & NAME_INHERITED)
|
|
{
|
|
inherit_t *inheritp;
|
|
|
|
inheritp = &prog->inherit[flags & INHERIT_MASK];
|
|
ix -= inheritp->function_index_offset;
|
|
prog = inheritp->prog;
|
|
flags = prog->functions[ix];
|
|
}
|
|
|
|
memcpy(&function_name
|
|
, FUNCTION_NAMEP(prog->program + (flags & FUNSTART_MASK))
|
|
, sizeof function_name
|
|
);
|
|
source = get_txt(function_name);
|
|
|
|
{
|
|
L_PUTC_PROLOG
|
|
L_PUTC('#');
|
|
#ifndef USE_NEW_INLINES
|
|
L_PUTC('l');
|
|
#else
|
|
if (l->function.lfun.context_size)
|
|
{
|
|
L_PUTC('c');
|
|
}
|
|
else
|
|
{
|
|
L_PUTC('l');
|
|
}
|
|
#endif /* USE_NEW_INLINES */
|
|
L_PUTC(':');
|
|
c = *source++;
|
|
do L_PUTC(c) while ( '\0' != (c = *source++) );
|
|
L_PUTC_EPILOG
|
|
}
|
|
|
|
/* For inherited lfun closures, add the '|<inheritpath>' */
|
|
if (inhProg)
|
|
{
|
|
prog = ob->prog;
|
|
ix = l->function.lfun.index;
|
|
|
|
while(prog != inhProg)
|
|
{
|
|
inherit_t *inheritp;
|
|
string_t *progName;
|
|
|
|
SEARCH_FUNCTION_INHERIT(inheritp, prog, ix);
|
|
ix -= inheritp->function_index_offset;
|
|
prog = inheritp->prog;
|
|
progName = del_dotc(prog->name);
|
|
|
|
{
|
|
L_PUTC_PROLOG
|
|
source = get_txt(progName);
|
|
L_PUTC('|');
|
|
c = *source++;
|
|
do
|
|
{
|
|
if (issavedel(c))
|
|
L_PUTC('\\');
|
|
L_PUTC(c)
|
|
} while ( '\0' != (c = *source++) );
|
|
L_PUTC_EPILOG
|
|
}
|
|
free_mstring(progName);
|
|
}
|
|
}
|
|
|
|
#ifdef USE_NEW_INLINES
|
|
if (l->function.lfun.context_size)
|
|
{
|
|
int i;
|
|
svalue_t * val;
|
|
|
|
{
|
|
L_PUTC_PROLOG
|
|
L_PUTC(':');
|
|
L_PUTC_EPILOG
|
|
}
|
|
/* Save the context size.
|
|
* It has to be saved separately because it is needed
|
|
* to allocated the lambda structure to the right size
|
|
* before the restore of the context can be done.
|
|
*/
|
|
{
|
|
svalue_t num;
|
|
|
|
put_number(&num, l->function.lfun.context_size);
|
|
save_svalue(&num, ':', MY_FALSE);
|
|
}
|
|
|
|
/* Save the actual context */
|
|
{
|
|
L_PUTC_PROLOG
|
|
L_PUTC('(')
|
|
L_PUTC('{')
|
|
L_PUTC_EPILOG
|
|
}
|
|
|
|
for (i = l->function.lfun.context_size
|
|
, val = l->context
|
|
; --i >= 0; )
|
|
{
|
|
(void)save_svalue(val++, ',', MY_FALSE);
|
|
}
|
|
|
|
{
|
|
L_PUTC_PROLOG
|
|
L_PUTC('}')
|
|
L_PUTC(')')
|
|
L_PUTC_EPILOG
|
|
}
|
|
}
|
|
#endif /* USE_NEW_INLINES */
|
|
}
|
|
else
|
|
{
|
|
L_PUTC_PROLOG
|
|
L_PUTC('0');
|
|
L_PUTC_EPILOG
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CLOSURE_IDENTIFIER:
|
|
{
|
|
lambda_t *l;
|
|
char * source, c;
|
|
|
|
if (recall_pointer(cl->u.lambda))
|
|
break;
|
|
|
|
l = cl->u.lambda;
|
|
if (l->function.var_index == VANISHED_VARCLOSURE_INDEX)
|
|
{
|
|
rc = MY_FALSE;
|
|
break;
|
|
}
|
|
if (l->ob->flags & O_DESTRUCTED
|
|
|| l->ob != current_object
|
|
)
|
|
{
|
|
rc = MY_FALSE;
|
|
break;
|
|
}
|
|
|
|
source = get_txt(l->ob->prog->variables[l->function.var_index].name);
|
|
|
|
{
|
|
L_PUTC_PROLOG
|
|
|
|
L_PUTC('#');
|
|
L_PUTC('v');
|
|
L_PUTC(':');
|
|
c = *source++;
|
|
do L_PUTC(c) while ( '\0' != (c = *source++) );
|
|
|
|
L_PUTC_EPILOG
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
if (type < 0)
|
|
{
|
|
switch(type & -0x0800)
|
|
{
|
|
case CLOSURE_OPERATOR:
|
|
{
|
|
const char *s = closure_operator_to_string(type);
|
|
|
|
if (s)
|
|
{
|
|
L_PUTC_PROLOG
|
|
char c;
|
|
|
|
L_PUTC('#');
|
|
L_PUTC('e');
|
|
L_PUTC(':');
|
|
|
|
c = *s++;
|
|
do L_PUTC(c) while ( '\0' != (c = *s++) );
|
|
|
|
L_PUTC_EPILOG
|
|
break;
|
|
}
|
|
type += CLOSURE_EFUN - CLOSURE_OPERATOR;
|
|
}
|
|
/* default action for operators: FALLTHROUGH */
|
|
|
|
case CLOSURE_EFUN:
|
|
{
|
|
const char *source = closure_efun_to_string(type);
|
|
L_PUTC_PROLOG
|
|
char c;
|
|
|
|
L_PUTC('#');
|
|
L_PUTC('e');
|
|
L_PUTC(':');
|
|
|
|
c = *source++;
|
|
do L_PUTC(c) while ( '\0' != (c = *source++) );
|
|
|
|
L_PUTC_EPILOG
|
|
break;
|
|
}
|
|
|
|
case CLOSURE_SIMUL_EFUN:
|
|
{
|
|
L_PUTC_PROLOG
|
|
char * source, c;
|
|
|
|
source = get_txt(simul_efunp[type - CLOSURE_SIMUL_EFUN].name);
|
|
|
|
L_PUTC('#');
|
|
L_PUTC('s');
|
|
L_PUTC(':');
|
|
|
|
c = *source++;
|
|
do L_PUTC(c) while ( '\0' != (c = *source++) );
|
|
|
|
L_PUTC_EPILOG
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
else /* type >= 0: one of the lambda closures */
|
|
{
|
|
rc = MY_FALSE;
|
|
}
|
|
break;
|
|
|
|
} /* switch(closure type) */
|
|
|
|
/* We come here, we could write the closure */
|
|
/* 'rc' at this point signifies whether the closure could be written.
|
|
* If it couldn't, maybe write a default '0', and also adjust rc
|
|
* to serve as function result.
|
|
*/
|
|
if (!rc)
|
|
{
|
|
if (writable)
|
|
rc = MY_FALSE;
|
|
else
|
|
{
|
|
L_PUTC_PROLOG
|
|
|
|
rc = MY_TRUE; /* Writing a default '0' counts */
|
|
L_PUTC('0');
|
|
L_PUTC_EPILOG
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
} /* save_closure() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static Bool
|
|
save_svalue (svalue_t *v, char delimiter, Bool writable)
|
|
|
|
/* Encode the value <v> and write it to the write buffer, terminate
|
|
* the output with <delimiter>.
|
|
* If <writable> is false, unwritable svalues like objects are written
|
|
* as '0'. If <writable> is true, unwritable svalues are not written at all.
|
|
*
|
|
* Return is true if something was written, and false otherwise.
|
|
*/
|
|
|
|
{
|
|
Bool rc = MY_TRUE;
|
|
|
|
assert_stack_gap();
|
|
|
|
switch(v->type)
|
|
{
|
|
case T_STRING:
|
|
save_string(v->u.str);
|
|
break;
|
|
|
|
case T_QUOTED_ARRAY:
|
|
{
|
|
L_PUTC_PROLOG
|
|
char * source, c;
|
|
|
|
source = number_buffer;
|
|
(void)sprintf(source, "#%"PRIdPHINT":", v->x.quotes);
|
|
c = *source++;
|
|
do L_PUTC(c) while ( '\0' != (c = *source++) );
|
|
L_PUTC_EPILOG
|
|
/* FALLTHROUGH to T_POINTER */
|
|
}
|
|
|
|
case T_POINTER:
|
|
save_array(v->u.vec);
|
|
break;
|
|
|
|
#ifdef USE_STRUCTS
|
|
case T_STRUCT:
|
|
save_struct(v->u.strct);
|
|
break;
|
|
#endif /* USE_STRUCTS */
|
|
|
|
case T_NUMBER:
|
|
{
|
|
L_PUTC_PROLOG
|
|
char *source, c;
|
|
|
|
source = number_buffer;
|
|
(void)sprintf(source, "%"PRIdPINT, v->u.number);
|
|
c = *source++;
|
|
do L_PUTC(c) while ( '\0' != (c = *source++) );
|
|
L_PUTC(delimiter);
|
|
L_PUTC_EPILOG
|
|
return rc;
|
|
}
|
|
|
|
#ifdef USE_RESTORED_OBJECTS
|
|
/* no change in save_version for this as all old
|
|
* applications shouldn't be trying to save objects
|
|
* and if they do they won't try using them
|
|
*/
|
|
case T_OBJECT: {
|
|
char *source, c;
|
|
|
|
L_PUTC_PROLOG
|
|
source = v->u.ob->name->txt;
|
|
c = *source++;
|
|
L_PUTC('`');
|
|
do L_PUTC(c) while ( '\0' != (c = *source++) );
|
|
L_PUTC('`');
|
|
L_PUTC_EPILOG
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
case T_FLOAT:
|
|
{
|
|
/* To minimize rounding losses, the floats are written
|
|
* in two forms: the nominal value, and the internal
|
|
* representation.
|
|
*/
|
|
|
|
L_PUTC_PROLOG
|
|
char *source, c;
|
|
|
|
source = number_buffer;
|
|
(void)sprintf(source, "%.12e=%"PRIxPHINT":%"PRIxPINT
|
|
, READ_DOUBLE(v), v->x.exponent & 0xffff
|
|
, v->u.mantissa);
|
|
c = *source++;
|
|
do L_PUTC(c) while ( '\0' != (c = *source++) );
|
|
L_PUTC(delimiter);
|
|
L_PUTC_EPILOG
|
|
return rc;
|
|
}
|
|
|
|
case T_MAPPING:
|
|
save_mapping(v->u.map);
|
|
break;
|
|
|
|
case T_SYMBOL:
|
|
{
|
|
L_PUTC_PROLOG
|
|
char * source, c;
|
|
|
|
source = number_buffer;
|
|
(void)sprintf(source, "#%hd:", v->x.quotes);
|
|
c = *source++;
|
|
do L_PUTC(c) while ( '\0' != (c = *source++) );
|
|
L_PUTC_EPILOG
|
|
save_string(v->u.str);
|
|
break;
|
|
}
|
|
|
|
case T_CLOSURE:
|
|
if (save_version > 0)
|
|
{
|
|
rc = save_closure(v, writable);
|
|
break;
|
|
}
|
|
/* else: FALLTHROUGH */
|
|
|
|
default:
|
|
{
|
|
/* Objects can't be saved */
|
|
if (writable)
|
|
rc = MY_FALSE;
|
|
else
|
|
{
|
|
L_PUTC_PROLOG
|
|
L_PUTC('0');
|
|
L_PUTC(delimiter);
|
|
L_PUTC_EPILOG
|
|
}
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
if (rc)
|
|
MY_PUTC(delimiter);
|
|
|
|
return rc;
|
|
} /* save_svalue() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
register_array (vector_t *vec)
|
|
|
|
/* Register the array <vec> in the pointer table. If it was not
|
|
* in there, also register all array/mapping values.
|
|
*/
|
|
|
|
{
|
|
svalue_t *v;
|
|
p_int i;
|
|
|
|
if (NULL == register_pointer(ptable, vec))
|
|
return;
|
|
|
|
v = vec->item;
|
|
for (i = VEC_SIZE(vec); --i >= 0; v++)
|
|
{
|
|
register_svalue(v);
|
|
}
|
|
} /* register_array() */
|
|
|
|
#ifdef USE_STRUCTS
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
register_struct (struct_t *st)
|
|
|
|
/* Register the struct <st> in the pointer table. If it was not
|
|
* in there, also register all struct/array/mapping values.
|
|
*/
|
|
|
|
{
|
|
svalue_t *v;
|
|
long i;
|
|
|
|
if (NULL == register_pointer(ptable, st))
|
|
return;
|
|
|
|
(void)register_pointer(ptable, struct_unique_name(st));
|
|
|
|
v = st->member;
|
|
for (i = (long)struct_size(st); --i >= 0; v++)
|
|
{
|
|
register_svalue(v);
|
|
}
|
|
} /* register_struct() */
|
|
#endif /* USE_STRUCTS */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
register_mapping_filter (svalue_t *key, svalue_t *data, void *extra)
|
|
|
|
/* Callback to register one mapping entry of (p_int)<extra> values.
|
|
*/
|
|
|
|
{
|
|
p_int i;
|
|
|
|
register_svalue(key);
|
|
|
|
for (i = (p_int)extra; --i >= 0; data++)
|
|
{
|
|
register_svalue(data);
|
|
}
|
|
} /* register_mapping_filter() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
register_mapping (mapping_t *map)
|
|
|
|
/* Register the mapping <map> in the pointer table. If it was not
|
|
* in there, also register all array/mapping values.
|
|
*/
|
|
|
|
{
|
|
if (NULL == register_pointer(ptable, map))
|
|
return;
|
|
walk_mapping(map, register_mapping_filter, (void *)map->num_values);
|
|
} /* register_mapping() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
register_closure (svalue_t *cl)
|
|
|
|
/* Register closure <cl> in the pointer table. If it was not
|
|
* in there, also register all associated svalues (if any).
|
|
*/
|
|
|
|
{
|
|
int type;
|
|
|
|
switch(type = cl->x.closure_type)
|
|
{
|
|
case CLOSURE_LFUN:
|
|
case CLOSURE_IDENTIFIER:
|
|
if (NULL == register_pointer(ptable, cl->u.lambda))
|
|
return;
|
|
break;
|
|
|
|
default:
|
|
/* Operator- or an unsaveable lambda closure */
|
|
return;
|
|
}
|
|
|
|
#ifdef USE_NEW_INLINES
|
|
if (type == CLOSURE_LFUN
|
|
&& cl->u.lambda->function.lfun.ob == current_object
|
|
&& cl->u.lambda->ob == current_object
|
|
&& cl->u.lambda->function.lfun.context_size
|
|
)
|
|
{
|
|
lambda_t *l;
|
|
svalue_t *val;
|
|
long i;
|
|
|
|
l = cl->u.lambda;
|
|
for (i = l->function.lfun.context_size
|
|
, val = l->context
|
|
; --i >= 0; )
|
|
{
|
|
register_svalue(val++);
|
|
}
|
|
}
|
|
#endif /* USE_NEW_INLINES */
|
|
} /* register_closure() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
register_svalue (svalue_t *svp)
|
|
|
|
/* If <svp> is a struct, array, or mapping, register it in the pointer
|
|
* table, and also register all sub structures.
|
|
*/
|
|
|
|
{
|
|
switch (svp->type)
|
|
{
|
|
case T_STRING:
|
|
(void)register_pointer(ptable, svp->u.str);
|
|
break;
|
|
|
|
case T_POINTER:
|
|
case T_QUOTED_ARRAY:
|
|
register_array(svp->u.vec);
|
|
break;
|
|
|
|
#ifdef USE_STRUCTS
|
|
case T_STRUCT:
|
|
register_struct(svp->u.strct);
|
|
break;
|
|
#endif /* USE_STRUCTS */
|
|
|
|
case T_MAPPING:
|
|
register_mapping(svp->u.map);
|
|
break;
|
|
|
|
case T_CLOSURE:
|
|
register_closure(svp);
|
|
break;
|
|
} /* switch() */
|
|
} /* register_svalue() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
v_save_object (svalue_t *sp, int numarg)
|
|
|
|
/* EFUN save_object()
|
|
*
|
|
* int save_object (string file, [int version])
|
|
* string save_object ([int version])
|
|
*
|
|
* Save the variables of the current object to the file <file> (the suffix
|
|
* ".o" will be appended. Returns 0 if the save file could be created,
|
|
* and non-zero otherwise (file could not be written, or current object
|
|
* is destructed).
|
|
*
|
|
* The <file>.o will not be written immediately: first the savefile will
|
|
* be created as <file>.o.tmp, which is after completion renamed to <file>.o.
|
|
*
|
|
* The validity of the filename is checked with a call to check_valid_path().
|
|
*
|
|
* In the second form, the a string with all variables and values is
|
|
* returned directly, or 0 if an error occurs. This string can be used
|
|
* with restore_object() to restore the variable values.
|
|
*
|
|
* In both forms, the optional argument <version> determines the format
|
|
* of the save file. A value of '-1' creates the format native to the
|
|
* driver. Currently the formats 0 and 1 are supported.
|
|
*
|
|
* TODO: "save_object()" looks nice, but maybe call that "save_variables()"?
|
|
*/
|
|
|
|
{
|
|
static char save_object_header[]
|
|
= { '#', SAVE_OBJECT_VERSION, ':', SAVE_OBJECT_HOST, '\n'
|
|
};
|
|
/* The version string to write
|
|
*/
|
|
|
|
object_t *ob;
|
|
/* The object to save - just a local copy of current_object.
|
|
*/
|
|
char *file;
|
|
/* The filename read from the stack, NULL if not saving
|
|
* to a file.
|
|
*/
|
|
char *name;
|
|
/* Buffer for the final and the temporary filename.
|
|
* name itself points to the final filename.
|
|
*/
|
|
char *tmp_name;
|
|
/* Pointer to the temporary filename in the buffer of name.
|
|
*/
|
|
char save_buffer[SAVE_OBJECT_BUFSIZE];
|
|
/* The write buffer
|
|
*/
|
|
long len;
|
|
int i;
|
|
int f;
|
|
svalue_t *v;
|
|
variable_t *names;
|
|
|
|
f = -1;
|
|
file = NULL;
|
|
name = NULL;
|
|
tmp_name = NULL;
|
|
save_version = CURRENT_VERSION;
|
|
|
|
/* Test the arguments */
|
|
switch (numarg)
|
|
{
|
|
case 0:
|
|
strbuf_zero(&save_string_buffer);
|
|
break;
|
|
|
|
case 1:
|
|
if (sp->type == T_STRING)
|
|
{
|
|
file = get_txt(sp->u.str);
|
|
}
|
|
else if (sp->type == T_NUMBER)
|
|
{
|
|
if (sp->u.number < -1 || sp->u.number > CURRENT_VERSION)
|
|
{
|
|
errorf("Illegal value for arg 1 to save_object(): %"PRIdPINT", "
|
|
"expected -1..%d\n"
|
|
, sp->u.number, CURRENT_VERSION
|
|
);
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
|
|
strbuf_zero(&save_string_buffer);
|
|
save_version = sp->u.number >= 0 ? sp->u.number
|
|
: CURRENT_VERSION;
|
|
}
|
|
else
|
|
{
|
|
vefun_gen_arg_error(1, sp->type, sp);
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
if (sp[-1].type != T_STRING)
|
|
vefun_arg_error(1, T_STRING, sp[-1].type, sp);
|
|
if (sp->type != T_NUMBER)
|
|
vefun_arg_error(2, T_NUMBER, sp->type, sp);
|
|
|
|
file = get_txt(sp[-1].u.str);
|
|
|
|
if (sp->u.number < -1 || sp->u.number > CURRENT_VERSION)
|
|
{
|
|
errorf("Illegal value for arg 1 to save_object(): %"PRIdPINT", "
|
|
"expected -1..%d\n"
|
|
, sp->u.number, CURRENT_VERSION
|
|
);
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
|
|
save_version = sp->u.number >= 0 ? sp->u.number
|
|
: CURRENT_VERSION;
|
|
|
|
/* The main code wants sp == filename (T_NUMBER svalues need no free.)
|
|
*/
|
|
sp--;
|
|
numarg--;
|
|
break;
|
|
|
|
default:
|
|
fatal("Too many arguments to save_object(): %d, expected 0..2\n"
|
|
, numarg);
|
|
} /* switch(numarg) */
|
|
|
|
save_object_header[1] = '0' + save_version;
|
|
|
|
/* No need in saving destructed objects */
|
|
|
|
ob = current_object;
|
|
if (ob->flags & O_DESTRUCTED)
|
|
{
|
|
if (numarg)
|
|
sp = pop_n_elems(numarg, sp);
|
|
sp++;
|
|
put_number(sp, 0);
|
|
return sp;
|
|
}
|
|
|
|
/* If saving to a file, get the proper name and open it
|
|
* The code assumes that sp is the filename argument.
|
|
*/
|
|
if (file)
|
|
{
|
|
string_t *sfile;
|
|
|
|
/* Get a valid filename */
|
|
|
|
sfile = check_valid_path(sp->u.str, ob, STR_SAVE_OBJECT, MY_TRUE);
|
|
if (sfile == NULL)
|
|
{
|
|
errorf("Illegal use of save_object('%s')\n", get_txt(sp->u.str));
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
|
|
/* Remove any trailing '.c' */
|
|
{
|
|
string_t *tmp = del_dotc(sfile);
|
|
if (!tmp)
|
|
outofmem(mstrsize(sfile), "filename");
|
|
free_mstring(sfile);
|
|
sfile = tmp;
|
|
}
|
|
|
|
|
|
/* Create the final and the temporary filename */
|
|
len = (long)mstrsize(sfile);
|
|
inter_sp = sp;
|
|
name = xalloc_with_error_handler(len + (sizeof save_file_suffix) +
|
|
len + (sizeof save_file_suffix) + 4);
|
|
if (!name)
|
|
{
|
|
free_mstring(sfile);
|
|
errorf("Out of memory (%ld bytes) in save_object('%s')\n",
|
|
2*len+2*sizeof(save_file_suffix)+4, get_txt(sp->u.str));
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
sp = inter_sp;
|
|
|
|
tmp_name = name + len + sizeof save_file_suffix;
|
|
strcpy(name, get_txt(sfile));
|
|
|
|
#ifndef MSDOS_FS
|
|
strcpy(name+len, save_file_suffix);
|
|
#endif
|
|
sprintf(tmp_name, "%s.tmp", name);
|
|
#ifdef MSDOS_FS
|
|
strcpy(name+len, save_file_suffix);
|
|
#endif
|
|
|
|
free_mstring(sfile);
|
|
|
|
/* Open the file */
|
|
|
|
#ifdef MSDOS_FS
|
|
/* We have to use O_BINARY with cygwin to be sure we have no CRs
|
|
* the file. See cygwin-faq for more information.
|
|
*/
|
|
f = ixopen3(tmp_name, O_CREAT|O_TRUNC|O_WRONLY|O_BINARY, 0640);
|
|
#else
|
|
f = ixopen3(tmp_name, O_CREAT|O_TRUNC|O_WRONLY|O_TEXT, 0640);
|
|
#endif
|
|
|
|
if (f < 0) {
|
|
char * emsg, * buf;
|
|
|
|
emsg = strerror(errno);
|
|
buf = alloca(strlen(emsg)+1);
|
|
if (buf)
|
|
{
|
|
strcpy(buf, emsg);
|
|
errorf("Could not open %s for a save: %s.\n", tmp_name, buf);
|
|
}
|
|
else
|
|
{
|
|
perror("save object");
|
|
errorf("Could not open %s for a save: errno %d.\n"
|
|
, tmp_name, errno);
|
|
}
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
FCOUNT_SAVE(tmp_name);
|
|
} /* if (file) */
|
|
|
|
/* Publish where we are going to save the data (-1 means using
|
|
* the string buffer.
|
|
*/
|
|
save_object_descriptor = f;
|
|
|
|
/* First pass through the variables to identify arrays/mappings
|
|
* that are used more than once.
|
|
*/
|
|
|
|
if (ptable)
|
|
{
|
|
debug_message("%s (save_object) Freeing lost pointertable\n", time_stamp());
|
|
free_pointer_table(ptable);
|
|
}
|
|
|
|
ptable = new_pointer_table();
|
|
if (!ptable)
|
|
{
|
|
if (file)
|
|
{
|
|
close(f);
|
|
unlink(tmp_name);
|
|
}
|
|
errorf("(save_object) Out of memory for pointer table.\n");
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
|
|
v = ob->variables;
|
|
names = ob->prog->variables;
|
|
for (i = ob->prog->num_variables; --i >= 0; v++, names++)
|
|
{
|
|
if (names->type.typeflags & TYPE_MOD_STATIC)
|
|
continue;
|
|
|
|
register_svalue(v);
|
|
}
|
|
|
|
/* Prepare the actual save */
|
|
|
|
failed = MY_FALSE;
|
|
current_sv_id_number = 0;
|
|
bytes_written = 0;
|
|
save_object_bufstart = save_buffer;
|
|
memcpy(save_buffer, save_object_header, sizeof(save_object_header));
|
|
buf_left = SAVE_OBJECT_BUFSIZE - sizeof(save_object_header);
|
|
buf_pnt = save_buffer + sizeof(save_object_header);
|
|
|
|
/* Second pass through the variables, actually saving them */
|
|
|
|
v = ob->variables;
|
|
names = ob->prog->variables;
|
|
for (i = ob->prog->num_variables; --i >= 0; v++, names++)
|
|
{
|
|
if (names->type.typeflags & TYPE_MOD_STATIC)
|
|
continue;
|
|
|
|
/* Write the variable name */
|
|
{
|
|
char *var_name, c;
|
|
L_PUTC_PROLOG
|
|
|
|
var_name = get_txt(names->name);
|
|
c = *var_name++;
|
|
do {
|
|
L_PUTC(c)
|
|
} while ( '\0' != (c = *var_name++) );
|
|
L_PUTC(' ')
|
|
L_PUTC_EPILOG
|
|
}
|
|
save_svalue(v, '\n', MY_FALSE);
|
|
}
|
|
|
|
free_pointer_table(ptable);
|
|
ptable = NULL;
|
|
|
|
if (file)
|
|
{
|
|
/* Finish up the file */
|
|
|
|
len = write( save_object_descriptor
|
|
, save_object_bufstart
|
|
, (size_t)(SAVE_OBJECT_BUFSIZE-buf_left));
|
|
if (len != SAVE_OBJECT_BUFSIZE-buf_left )
|
|
failed = MY_TRUE;
|
|
|
|
|
|
/* On failure, delete the temporary file and return */
|
|
|
|
if (failed)
|
|
{
|
|
close(f);
|
|
unlink(tmp_name);
|
|
add_message("Failed to save to file '%s'. Disk could be full.\n", file);
|
|
/* free the error handler and the arguments (numarg + 1 from sp).
|
|
*/
|
|
sp = pop_n_elems(numarg + 1, sp);
|
|
sp++;
|
|
put_number(sp, 1);
|
|
return sp;
|
|
}
|
|
|
|
/* Delete any existing savefile, then rename the temporary
|
|
* file to the real name.
|
|
*/
|
|
|
|
i = 0; /* Result from efun */
|
|
|
|
unlink(name);
|
|
#if !defined(MSDOS_FS) && !defined(AMIGA) && !(defined(OS2) || defined(__EMX__)) && !defined(__BEOS__)
|
|
if (link(tmp_name, name) == -1)
|
|
#else
|
|
close(f);
|
|
if (rename(tmp_name,name) < 0)
|
|
#endif
|
|
{
|
|
perror(name);
|
|
printf("%s Failed to link %s to %s\n"
|
|
, time_stamp(), tmp_name, name);
|
|
add_message("Failed to save object !\n");
|
|
i = 1;
|
|
}
|
|
#if !defined(MSDOS_FS) && !defined(AMIGA) && !(defined(__EMX__) || defined(OS2)) && !defined(__BEOS__)
|
|
close(f);
|
|
unlink(tmp_name);
|
|
#endif
|
|
|
|
/* free the error handler and the arguments (numarg + 1 from sp) and
|
|
* push result on the stack.
|
|
*/
|
|
sp = pop_n_elems(numarg + 1, sp);
|
|
sp++;
|
|
put_number(sp, i);
|
|
} /* if (file) */
|
|
else
|
|
{
|
|
/* Finish up the operation. Note that there propably is some
|
|
* data pending in the save_buffer.
|
|
*/
|
|
|
|
/* free the error handler and the arguments (numarg + 1 from sp).
|
|
*/
|
|
sp = pop_n_elems(numarg + 1, sp);
|
|
|
|
sp++; /* for the result */
|
|
if (failed)
|
|
put_number(sp, 0); /* Shouldn't happen */
|
|
else if (buf_left != SAVE_OBJECT_BUFSIZE)
|
|
{
|
|
/* Data pending in the save_buffer. */
|
|
if (!bytes_written)
|
|
{
|
|
/* Less than SAVE_OBJECT_BUFSIZE bytes generated
|
|
* we bypass the strbuf for speed.
|
|
*/
|
|
len = SAVE_OBJECT_BUFSIZE-buf_left;
|
|
save_object_bufstart[len] = '\0';
|
|
put_c_string(sp, save_object_bufstart);
|
|
strbuf_free(&save_string_buffer);
|
|
}
|
|
else
|
|
{
|
|
/* More than SAVE_OBJECT_BUFSIZE of data generated
|
|
* Fill up the stringbuffer and create the result.
|
|
*/
|
|
strbuf_addn(&save_string_buffer, save_object_bufstart
|
|
, SAVE_OBJECT_BUFSIZE-buf_left);
|
|
strbuf_store(&save_string_buffer, sp);
|
|
}
|
|
}
|
|
else
|
|
/* The save_buffer[] is empty, what means
|
|
* that at least one buffer full was written into
|
|
* the strbuf.
|
|
*/
|
|
strbuf_store(&save_string_buffer, sp);
|
|
} /* if (file or not file) */
|
|
|
|
return sp;
|
|
} /* v_save_object() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
v_save_value (svalue_t *sp, int numarg)
|
|
|
|
/* EFUN save_value()
|
|
*
|
|
* string save_value(mixed value, [int version])
|
|
*
|
|
* Encode the <value> into a string suitable for restoration with
|
|
* restore_value() and return it.
|
|
*
|
|
* The created string consists of two lines, each terminated with a newline
|
|
* character: the first line describes the format used to save the value in
|
|
* the '#x:y' notation; the second line is the representation of the value
|
|
* itself.
|
|
*
|
|
* The optional argument <version> determines the format
|
|
* of the save file. A value of '-1' creates the format native to the
|
|
* driver. Currently the formats 0 and 1 are supported.
|
|
*/
|
|
|
|
{
|
|
static char save_value_header[]
|
|
= { '#', SAVE_OBJECT_VERSION, ':', SAVE_OBJECT_HOST, '\n'
|
|
};
|
|
/* The version string to write
|
|
*/
|
|
|
|
char save_buffer[SAVE_OBJECT_BUFSIZE];
|
|
/* The write buffer.
|
|
*/
|
|
|
|
/* Set up the globals */
|
|
if (ptable)
|
|
{
|
|
debug_message("%s (save_value) Freeing lost pointer table.\n", time_stamp());
|
|
free_pointer_table(ptable);
|
|
}
|
|
ptable = new_pointer_table();
|
|
if (!ptable)
|
|
{
|
|
errorf("(save_value) Out of memory for pointer table.\n");
|
|
return sp; /* flow control hint */
|
|
}
|
|
|
|
strbuf_zero(&save_string_buffer);
|
|
save_object_descriptor = -1;
|
|
save_version = CURRENT_VERSION;
|
|
|
|
/* Evaluate the arguments */
|
|
switch (numarg)
|
|
{
|
|
case 1:
|
|
/* Ok */
|
|
break;
|
|
|
|
case 2:
|
|
if (sp->type == T_NUMBER)
|
|
{
|
|
if (sp->u.number < -1 || sp->u.number > CURRENT_VERSION)
|
|
{
|
|
errorf("Illegal value for arg 1 to save_object(): %"PRIdPINT", "
|
|
"expected -1..%d\n"
|
|
, sp->u.number, CURRENT_VERSION
|
|
);
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
|
|
save_version = sp->u.number >= 0 ? sp->u.number
|
|
: CURRENT_VERSION;
|
|
|
|
sp--;
|
|
}
|
|
else
|
|
{
|
|
vefun_gen_arg_error(2, sp->type, sp);
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
fatal("Illegal number of arguments to save_value(): %d, expected 1..2\n"
|
|
, numarg);
|
|
} /* switch(numarg) */
|
|
|
|
save_value_header[1] = '0' + save_version;
|
|
|
|
/* First look at the value for arrays and mappings
|
|
*/
|
|
register_svalue(sp);
|
|
|
|
/* Prepare the actual save */
|
|
|
|
failed = MY_FALSE;
|
|
current_sv_id_number = 0;
|
|
bytes_written = 0;
|
|
save_object_bufstart = save_buffer;
|
|
memcpy(save_buffer, save_value_header, sizeof(save_value_header));
|
|
buf_left = SAVE_OBJECT_BUFSIZE - sizeof(save_value_header);
|
|
buf_pnt = save_buffer + sizeof(save_value_header);
|
|
|
|
/* Save the value */
|
|
save_svalue(sp, '\n', MY_FALSE);
|
|
|
|
/* Finish up the operation. Note that there propably is some
|
|
* data pending in the save_buffer.
|
|
*/
|
|
|
|
free_svalue(sp); /* No longer needed */
|
|
|
|
if (failed)
|
|
put_number(sp, 0); /* Shouldn't happen */
|
|
else if (buf_left != SAVE_OBJECT_BUFSIZE)
|
|
{
|
|
/* Data pending in the save_buffer. */
|
|
if (!bytes_written)
|
|
{
|
|
/* Less than SAVE_OBJECT_BUFSIZE bytes generated
|
|
* we bypass the strbuf for speed.
|
|
*/
|
|
size_t len = SAVE_OBJECT_BUFSIZE-buf_left;
|
|
|
|
save_object_bufstart[len] = '\0';
|
|
put_c_string(sp, save_object_bufstart);
|
|
strbuf_free(&save_string_buffer);
|
|
}
|
|
else
|
|
{
|
|
/* More than SAVE_OBJECT_BUFSIZE of data generated
|
|
* Fill up the stringbuffer and create the result.
|
|
*/
|
|
strbuf_addn(&save_string_buffer, save_object_bufstart
|
|
, SAVE_OBJECT_BUFSIZE-buf_left);
|
|
strbuf_store(&save_string_buffer, sp);
|
|
}
|
|
}
|
|
else
|
|
/* The save_buffer[] is empty, what means
|
|
* that at least one buffer full was written into
|
|
* the strbuf.
|
|
*/
|
|
strbuf_store(&save_string_buffer, sp);
|
|
|
|
/* Clean up */
|
|
free_pointer_table(ptable);
|
|
ptable = NULL;
|
|
|
|
return sp;
|
|
} /* v_save_value() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
/* Structure used by restore_mapping() and restore_map_size()
|
|
* to exchange data.
|
|
*/
|
|
|
|
struct rms_parameters
|
|
{
|
|
char *str; /* Current position in the input stream */
|
|
int num_values; /* Recognized number of values per key */
|
|
};
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static int
|
|
restore_map_size (struct rms_parameters *parameters)
|
|
|
|
/* Determine the size of a mapping to be restored.
|
|
* The mapping text starts at parameters->str, which points after the
|
|
* initial '(['.
|
|
*
|
|
* The recognized width of the mapping is returned in parameters->num_values,
|
|
* parameters->str is set to the character after the mapping text, and
|
|
* the size (number of entries) is returned directly.
|
|
* If the mapping text is ill formed, the function returns -1.
|
|
*
|
|
* The function calls itself and restore_size() recursively
|
|
* for embedded arrays and mappings.
|
|
*
|
|
* TODO: this function assumes that num_values and num_entries of mappings
|
|
* TODO::are 'int'. Should be changed to p_int.
|
|
*/
|
|
|
|
{
|
|
char *pt; /* Read pointer */
|
|
int siz; /* Number of entries (so far) */
|
|
int num_values = -1; /* Last recognized width of the mapping */
|
|
int current_num_values = 0; /* Width of current entry */
|
|
|
|
pt = parameters->str;
|
|
siz = 0;
|
|
|
|
/* Saveguard */
|
|
if (!pt)
|
|
return -1;
|
|
|
|
/* The parse loop */
|
|
|
|
while (MY_TRUE)
|
|
{
|
|
/* Parse the next element */
|
|
switch (*pt)
|
|
{
|
|
|
|
case ']': /* End of mapping */
|
|
{
|
|
if (pt[1] != ')')
|
|
return -1;
|
|
parameters->str = &pt[2];
|
|
parameters->num_values = siz ? num_values : 1;
|
|
return siz;
|
|
}
|
|
|
|
case ':': /* Special case: ([:<width>]) */
|
|
{
|
|
if (siz || current_num_values)
|
|
return -1;
|
|
pt++;
|
|
num_values = atoi(pt);
|
|
pt = strchr(pt,']');
|
|
if (!pt || pt[1] != ')' || num_values < 0)
|
|
return -1;
|
|
parameters->str = &pt[2];
|
|
parameters->num_values = num_values;
|
|
return siz;
|
|
}
|
|
|
|
#ifdef USE_RESTORED_OBJECTS
|
|
case '`': /* An object, count it like a string */
|
|
{
|
|
pt = strchr(&pt[1],'`');
|
|
if (!pt)
|
|
return -1;
|
|
pt++;
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
case '\"': /* A string */
|
|
{
|
|
int backslashes;
|
|
|
|
do {
|
|
pt = strchr(&pt[1],'\"');
|
|
if (!pt)
|
|
return -1;
|
|
/* the quote is escaped if and only
|
|
* if the number of slashes is odd. */
|
|
for (backslashes = -1; pt[backslashes] == '\\'; backslashes--) ;
|
|
} while ( !(backslashes & 1) ) ;
|
|
pt++;
|
|
break;
|
|
}
|
|
|
|
case '(': /* An embedded mapping/array/struct */
|
|
{
|
|
int tsiz;
|
|
|
|
parameters->str = pt + 2;
|
|
if (pt[1] == '{'
|
|
#ifdef USE_STRUCTS
|
|
|| pt[1] == '<'
|
|
#endif /* USE_STRUCTS */
|
|
)
|
|
tsiz = restore_size(¶meters->str);
|
|
else if (pt[1] == '[')
|
|
tsiz = restore_map_size(parameters);
|
|
else return -1;
|
|
pt = parameters->str;
|
|
if (tsiz < 0)
|
|
return -1;
|
|
break;
|
|
}
|
|
|
|
case '<': /* A shared mapping/array/struct */
|
|
{
|
|
pt = strchr(pt, '>');
|
|
if (!pt)
|
|
return -1;
|
|
pt++;
|
|
if (pt[0] == '=')
|
|
{
|
|
pt++;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case '#': /* A closure: skip the header and restart this check
|
|
* again from the data part.
|
|
*/
|
|
{
|
|
const char * end;
|
|
|
|
if (pt[1] == 'c')
|
|
{
|
|
pt = strchr(pt, ':');
|
|
if (!pt)
|
|
return -1;
|
|
pt++;
|
|
}
|
|
pt = strchr(pt, ':');
|
|
if (!pt)
|
|
return -1;
|
|
pt++;
|
|
|
|
/* Try parsing the closure as operator closure.
|
|
* If it is, restart the scanning from the end
|
|
* of the string (which is likely to contain magic
|
|
* characters like '<' or '-').
|
|
*/
|
|
if (symbol_operator(pt, &end) >= 0)
|
|
{
|
|
pt = (char *)end;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
case '-': /* A negative number */
|
|
pt++;
|
|
if (!*pt)
|
|
return -1;
|
|
/* FALL THROUGH */
|
|
|
|
case '0': case '1': case '2': case '3': case '4': /* A number */
|
|
case '5': case '6': case '7': case '8': case '9':
|
|
if (pt[1] == '.')
|
|
{
|
|
/* A float: test for the <float>=<exp>:<mantissa> syntax */
|
|
|
|
char *pt2;
|
|
|
|
pt2 = strpbrk(pt, "=:;,");
|
|
if (!pt2)
|
|
return -1;
|
|
if (*pt2 != '=')
|
|
break;
|
|
pt = strchr(pt2, ':');
|
|
if (!pt)
|
|
return -1;
|
|
pt++;
|
|
}
|
|
/* FALL THROUGH */
|
|
|
|
/* Numbers and default: advance pt to the next terminal */
|
|
default:
|
|
{
|
|
pt = strpbrk(pt, ":;,");
|
|
if (!pt)
|
|
return -1;
|
|
break;
|
|
}
|
|
} /* switch() */
|
|
|
|
/* At this point, pt points just after the preceeding
|
|
* non-terminal
|
|
*/
|
|
switch (*pt)
|
|
{
|
|
case ':':
|
|
/* current_num_values is 0 on the first encounter */
|
|
|
|
if (current_num_values)
|
|
return -1;
|
|
|
|
/* FALL THROUGH */
|
|
|
|
case ';':
|
|
current_num_values++;
|
|
break;
|
|
|
|
case ',': /* End of entry */
|
|
siz++;
|
|
if (current_num_values != num_values)
|
|
{
|
|
if (num_values >= 0)
|
|
return -1;
|
|
num_values = current_num_values;
|
|
}
|
|
current_num_values = 0;
|
|
break;
|
|
|
|
default:
|
|
return -1;
|
|
}
|
|
pt++;
|
|
}
|
|
|
|
/* NOTREACHED */
|
|
|
|
return -1;
|
|
} /* restore_map_size() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
INLINE static void
|
|
free_shared_restored_values (void)
|
|
|
|
/* Deref all svalues in shared_restored_values[] up to
|
|
* current_shared_restored, then deallocate the array itself.
|
|
*/
|
|
|
|
{
|
|
while (current_shared_restored > 0)
|
|
free_svalue(&shared_restored_values[--current_shared_restored]);
|
|
xfree(shared_restored_values);
|
|
shared_restored_values = NULL;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
INLINE static Bool
|
|
restore_mapping (svalue_t *svp, char **str)
|
|
|
|
/* Restore a mapping from the text starting at *<str> (which points
|
|
* just after the leading '([') and store it into *<svp>.
|
|
* Return TRUE if the restore was successful, FALSE else (*<svp> is
|
|
* set to const0 in that case).
|
|
* On a successful return, *<str> is set to point after the mapping
|
|
* restored.
|
|
*
|
|
* TODO: this function assumes that num_values and num_entries of mappings
|
|
* TODO::are 'int'. Should be changed to p_int.
|
|
*/
|
|
|
|
{
|
|
mapping_t *z;
|
|
svalue_t key, *data;
|
|
int i;
|
|
struct rms_parameters tmp_par;
|
|
int siz;
|
|
|
|
/* Determine the size and width of the mapping */
|
|
|
|
tmp_par.str = *str;
|
|
siz = restore_map_size(&tmp_par);
|
|
if (siz < 0)
|
|
{
|
|
*svp = const0;
|
|
return MY_FALSE;
|
|
}
|
|
|
|
if (max_mapping_size && siz * (1+tmp_par.num_values) > (p_int)max_mapping_size)
|
|
{
|
|
*svp = const0;
|
|
errorf("Illegal mapping size: %ld elements (%d x %d).\n"
|
|
, (long)siz * (1+tmp_par.num_values)
|
|
, siz
|
|
, 1+tmp_par.num_values );
|
|
return MY_FALSE;
|
|
}
|
|
|
|
/* Allocate the mapping */
|
|
z = allocate_mapping(siz, tmp_par.num_values);
|
|
|
|
if (!z)
|
|
{
|
|
*svp = const0;
|
|
errorf("(restore) Out of memory: mapping[%d, %d]\n"
|
|
, siz, tmp_par.num_values);
|
|
return MY_FALSE;
|
|
}
|
|
|
|
svp->type = T_MAPPING;
|
|
svp->u.map = z;
|
|
|
|
/* Loop through size and width, restoring the values */
|
|
while (--siz >= 0)
|
|
{
|
|
i = tmp_par.num_values;
|
|
key.type = T_NUMBER;
|
|
if (!restore_svalue(&key, str, (char)(i ? ':' : ',') ))
|
|
{
|
|
free_svalue(&key);
|
|
return MY_FALSE;
|
|
}
|
|
data = get_map_lvalue_unchecked(z, &key);
|
|
if (!data)
|
|
{
|
|
outofmemory("restored mapping entry");
|
|
/* NOTREACHED */
|
|
return MY_FALSE;
|
|
}
|
|
free_svalue(&key);
|
|
while (--i >= 0) {
|
|
if (data->type != T_INVALID && data->type != T_NUMBER)
|
|
{
|
|
/* Duplicate key: this shouldn't happen - but it did */
|
|
free_svalue(data);
|
|
}
|
|
if (!restore_svalue(data++, str, (char)(i ? ';' : ',') ))
|
|
return MY_FALSE;
|
|
}
|
|
}
|
|
*str = tmp_par.str;
|
|
return MY_TRUE;
|
|
} /* restore_mapping() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static int
|
|
restore_size (char **str)
|
|
|
|
/* Determine the size of an array/struct to be restored.
|
|
* The array/struct text starts at *str, which points after the initial '({'
|
|
* resp. '(<'.
|
|
*
|
|
* The recognized size of the array/struct is returned, or -1 if the text
|
|
* is ill formed. *<str> is set to point to the character after the
|
|
* array/struct text.
|
|
*
|
|
* The function calls itself and restore_map_size() recursively
|
|
* for embedded arrays and mappings.
|
|
*
|
|
* TODO: this function assumes that the size of arrays and mappings is
|
|
* TODO::< INT_MAX. Should be changed to p_int.
|
|
*/
|
|
|
|
{
|
|
char *pt, *pt2;
|
|
int siz;
|
|
|
|
pt = *str;
|
|
siz = 0;
|
|
|
|
while (pt && *pt)
|
|
{
|
|
switch(*pt)
|
|
{
|
|
case '}': /* End of array */
|
|
#ifdef USE_STRUCTS
|
|
case '>': /* End of struct */
|
|
#endif /* USE_STRUCTS */
|
|
{
|
|
if (pt[1] != ')')
|
|
return -1;
|
|
*str = &pt[2];
|
|
return siz;
|
|
}
|
|
|
|
case '\"': /* String */
|
|
{
|
|
int backslashes;
|
|
|
|
do {
|
|
pt = strchr(&pt[1],'\"');
|
|
if (!pt)
|
|
return -1;
|
|
/* the quote is escaped if and only
|
|
* if the number of slashes is odd.
|
|
*/
|
|
for (backslashes = -1; pt[backslashes] == '\\'; backslashes--)
|
|
NOOP;
|
|
} while ( !(backslashes & 1) ) ;
|
|
|
|
if (pt[1] != ',')
|
|
return -1;
|
|
siz++;
|
|
pt += 2;
|
|
break;
|
|
}
|
|
|
|
#ifdef USE_RESTORED_OBJECTS
|
|
case '`': /* An object, count it like a string */
|
|
{
|
|
pt = strchr(&pt[1],'`');
|
|
if (!pt)
|
|
return -1;
|
|
pt++;
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
case '(': /* Embedded array, struct or mapping */
|
|
{
|
|
/* Lazy way of doing it, a bit inefficient */
|
|
struct rms_parameters tmp_par;
|
|
int tsiz;
|
|
|
|
tmp_par.str = pt + 2;
|
|
if (pt[1] == '{'
|
|
#ifdef USE_STRUCTS
|
|
|| pt[1] == '<'
|
|
#endif /* USE_STRUCTS */
|
|
)
|
|
tsiz = restore_size(&tmp_par.str);
|
|
else if (pt[1] == '[')
|
|
tsiz = restore_map_size(&tmp_par);
|
|
else
|
|
return -1;
|
|
|
|
pt = tmp_par.str;
|
|
if (tsiz < 0)
|
|
return -1;
|
|
|
|
pt++;
|
|
siz++;
|
|
|
|
break;
|
|
}
|
|
|
|
case '<': /* A shared array or mapping */
|
|
{
|
|
pt = strchr(pt, '>');
|
|
if (!pt)
|
|
return -1;
|
|
if (pt[1] == ',')
|
|
{
|
|
siz++;
|
|
pt += 2;
|
|
}
|
|
else if (pt[1] == '=')
|
|
{
|
|
pt += 2;
|
|
}
|
|
else
|
|
return -1;
|
|
break;
|
|
}
|
|
|
|
case '#': /* A closure: skip the header and restart this check
|
|
* again from the data part.
|
|
*/
|
|
{
|
|
const char * end;
|
|
|
|
if (pt[1] == 'c')
|
|
{
|
|
pt2 = strchr(pt, ':');
|
|
if (!pt2)
|
|
return -1;
|
|
pt = &pt2[1];
|
|
}
|
|
pt2 = strchr(pt, ':');
|
|
if (!pt2)
|
|
return -1;
|
|
pt = &pt2[1];
|
|
|
|
/* Try parsing the closure as operator closure.
|
|
* If it is, restart the scanning from the end
|
|
* of the string (which is likely to contain magic
|
|
* characters like '<' or '-').
|
|
*/
|
|
if (symbol_operator(pt, &end) >= 0)
|
|
{
|
|
pt = (char *)end;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
pt2 = strchr(pt, ',');
|
|
if (!pt2)
|
|
return -1;
|
|
siz++;
|
|
pt = &pt2[1];
|
|
break;
|
|
|
|
} /* switch() */
|
|
} /* while() */
|
|
|
|
return -1;
|
|
} /* restore_size() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static INLINE Bool
|
|
restore_array (svalue_t *svp, char **str)
|
|
|
|
/* Restore an array from the text starting at *<str> (which points
|
|
* just after the leading '({' and store it into *<svp>.
|
|
* Return TRUE if the restore was successful, FALSE else (*<svp> is
|
|
* set to const0 in that case).
|
|
* On a successful return, *<str> is set to point after the array text
|
|
* restored.
|
|
*
|
|
* TODO: this function assumes that the size of arrays is < INT_MAX. Should
|
|
* TODO::be changed to p_int.
|
|
*/
|
|
|
|
{
|
|
vector_t *v;
|
|
char *pt, *end;
|
|
int siz;
|
|
|
|
end = *str;
|
|
|
|
/* Get the size of the array */
|
|
|
|
siz = restore_size(&end);
|
|
if (siz < 0)
|
|
{
|
|
*svp = const0;
|
|
return MY_FALSE;
|
|
}
|
|
|
|
if (max_array_size && siz > (p_int)max_array_size)
|
|
{
|
|
*svp = const0;
|
|
errorf("Illegal array size: %ld.\n", (long int)siz);
|
|
return MY_FALSE;
|
|
}
|
|
|
|
/* Allocate the array */
|
|
|
|
*svp = const0; /* in case allocate_array throws an error */
|
|
v = allocate_array(siz);
|
|
put_array(svp, v);
|
|
|
|
/* Restore the values */
|
|
|
|
for ( svp = v->item; --siz >= 0; svp++)
|
|
{
|
|
if (!restore_svalue(svp, str, ','))
|
|
{
|
|
return MY_FALSE;
|
|
}
|
|
}
|
|
|
|
/* Check for the trailing '})' */
|
|
|
|
pt = *str;
|
|
if (*pt++ != '}' || *pt++ != ')' ) {
|
|
return MY_FALSE;
|
|
}
|
|
|
|
|
|
*str = pt;
|
|
return MY_TRUE;
|
|
} /* restore_array() */
|
|
|
|
#ifdef USE_STRUCTS
|
|
/*-------------------------------------------------------------------------*/
|
|
static INLINE Bool
|
|
restore_struct (svalue_t *svp, char **str)
|
|
|
|
/* Restore a struct from the text starting at *<str> (which points
|
|
* just after the leading '(<') and store it into *<svp>.
|
|
* Return TRUE if the restore was successful, FALSE else (*<svp> is
|
|
* set to const0 in that case).
|
|
* On a successful return, *<str> is set to point after the struct text
|
|
* restored.
|
|
*/
|
|
|
|
{
|
|
struct_t *st;
|
|
struct_type_t *stt;
|
|
char *pt, *end;
|
|
int siz, extra;
|
|
|
|
end = *str;
|
|
|
|
*svp = const0; /* In case of errors */
|
|
|
|
/* Get the size of the array */
|
|
|
|
siz = restore_size(&end);
|
|
if (siz < 1)
|
|
{
|
|
return MY_FALSE;
|
|
}
|
|
|
|
extra = 0;
|
|
|
|
/* Get the name of the struct, and from it the type pointer */
|
|
{
|
|
svalue_t name;
|
|
string_t * struct_name;
|
|
string_t * prog_name;
|
|
long pos;
|
|
|
|
if (!restore_svalue(&name, str, ','))
|
|
return MY_FALSE;
|
|
if (name.type != T_STRING)
|
|
{
|
|
free_svalue(&name);
|
|
return MY_FALSE;
|
|
}
|
|
siz--;
|
|
|
|
/* Accept both 'struct_name' and 'struct_name prog_name #id'
|
|
* as formats.
|
|
*/
|
|
pos = mstrchr(name.u.str, ' ');
|
|
if (pos < 0)
|
|
{
|
|
struct_name = ref_mstring(name.u.str);
|
|
prog_name = NULL;
|
|
}
|
|
else
|
|
{
|
|
long pos2;
|
|
|
|
pos2 = mstrchr(name.u.str, '#');
|
|
if (pos2 < 0)
|
|
{
|
|
free_mstring(name.u.str);
|
|
return MY_FALSE;
|
|
}
|
|
struct_name = mstr_extract(name.u.str, 0, pos-1);
|
|
prog_name = mstr_extract(name.u.str, pos+1, pos2-2);
|
|
if (!compat_mode)
|
|
{
|
|
string_t * tmp;
|
|
tmp = add_slash(prog_name);
|
|
if (tmp)
|
|
{
|
|
free_mstring(prog_name);
|
|
prog_name = tmp;
|
|
}
|
|
}
|
|
}
|
|
free_mstring(name.u.str);
|
|
|
|
/* First, search the struct in the current program.
|
|
* This allows to move inherited structs between modules without
|
|
* breaking the savefiles.
|
|
*/
|
|
stt = struct_find(struct_name, current_object->prog);
|
|
if (!stt && prog_name != NULL)
|
|
{
|
|
do {
|
|
/* Alternatively try to find the struct by its program name.
|
|
*/
|
|
object_t *obj = get_object(prog_name);
|
|
|
|
if (!obj)
|
|
break;
|
|
|
|
#ifdef USE_SWAP
|
|
if (O_PROG_SWAPPED(obj)
|
|
&& load_ob_from_swap(obj) < 0
|
|
)
|
|
break;
|
|
#endif
|
|
|
|
stt = struct_find(struct_name, obj->prog);
|
|
} while(0);
|
|
}
|
|
|
|
/* Now stt is either NULL or the struct type */
|
|
|
|
free_mstring(struct_name);
|
|
if (prog_name)
|
|
free_mstring(prog_name);
|
|
|
|
if (!stt)
|
|
return MY_FALSE;
|
|
|
|
if (struct_t_size(stt) < siz)
|
|
{
|
|
extra = siz - struct_t_size(stt);
|
|
siz = struct_t_size(stt);
|
|
}
|
|
}
|
|
/* Allocate the struct */
|
|
st = struct_new(stt);
|
|
put_struct(svp, st);
|
|
|
|
/* Restore the values */
|
|
|
|
for ( svp = st->member; --siz >= 0; svp++)
|
|
{
|
|
if (!restore_svalue(svp, str, ','))
|
|
{
|
|
return MY_FALSE;
|
|
}
|
|
}
|
|
|
|
/* If there are more values in the savefile than the struct
|
|
* has members, read and ignore the others.
|
|
*/
|
|
while (extra-- > 0)
|
|
{
|
|
svalue_t tmp;
|
|
|
|
if (!restore_svalue(&tmp, str, ','))
|
|
{
|
|
return MY_FALSE;
|
|
}
|
|
free_svalue(&tmp);
|
|
}
|
|
|
|
/* Check for the trailing '>)' */
|
|
|
|
pt = *str;
|
|
if (*pt++ != '>' || *pt++ != ')' ) {
|
|
return MY_FALSE;
|
|
}
|
|
|
|
|
|
*str = pt;
|
|
return MY_TRUE;
|
|
} /* restore_struct() */
|
|
#endif /* USE_STRUCTS */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static INLINE Bool
|
|
restore_closure (svalue_t *svp, char **str, char delimiter)
|
|
|
|
/* Restore a closure from the text starting at *<str> (which points
|
|
* just after the leading '#') and store it into *<svp>.
|
|
* Return TRUE if the restore was successful, FALSE else (*<svp> is
|
|
* set to const0 in that case).
|
|
* On a successful return, *<str> is set to point after the closure text
|
|
* restored.
|
|
*/
|
|
|
|
{
|
|
char *pt;
|
|
char ct;
|
|
char * name;
|
|
char * nameend, name_delim; /* Holds the name delimiter while the name
|
|
* is terminated by '\0' for processing.
|
|
*/
|
|
|
|
pt = *str;
|
|
switch(ct = *pt)
|
|
{
|
|
default:
|
|
fatal("Unsupported closure-type '%c'\n", ct);
|
|
break;
|
|
|
|
case 'e': /* An efun closure */
|
|
case 's': /* A sefun closure */
|
|
case 'v': /* A variable closure */
|
|
case 'c': /* A context-lfun closure */
|
|
case 'l': /* A lfun closure */
|
|
{
|
|
char c;
|
|
|
|
/* Parse the name of the closure item */
|
|
if (*++pt != ':')
|
|
{
|
|
*svp = const0;
|
|
return MY_FALSE;
|
|
}
|
|
|
|
name = ++pt;
|
|
for(;;)
|
|
{
|
|
if ( !(c = *pt++) )
|
|
{
|
|
*svp = const0;
|
|
return MY_FALSE;
|
|
}
|
|
|
|
/* Break at the delimiter, but make sure that it's not
|
|
* part of an operator closure ('#e:,' for example).
|
|
*/
|
|
if (( c == delimiter
|
|
&& !(pt[-4] == '#' && pt[-3] == 'e' && pt[-2] == ':')
|
|
)
|
|
|| (ct == 'c' && (c == ':' || c=='|' || c=='-'))
|
|
|| (ct == 'l' && (c == '|' || c=='-'))
|
|
) break;
|
|
}
|
|
|
|
/* Save the delimiter, then replace it by '\0' */
|
|
nameend = pt-1;
|
|
name_delim = *nameend;
|
|
pt[-1] = '\0';
|
|
|
|
*str = pt;
|
|
/* Note: for non-context closures, str now points one
|
|
* char too far. For context closures, str now points
|
|
* to the first character of the context size value.
|
|
*/
|
|
}
|
|
} /* switch(ct) */
|
|
|
|
/* Create the proper closure */
|
|
switch (ct)
|
|
{
|
|
case 'e': /* An efun closure */
|
|
case 's': /* A sefun closure */
|
|
{
|
|
symbol_efun_str(name, strlen(name), svp, ct == 'e');
|
|
break;
|
|
}
|
|
|
|
case 'v': /* A variable closure */
|
|
{
|
|
string_t *s;
|
|
object_t *ob;
|
|
variable_t *var;
|
|
program_t *prog;
|
|
int num_var;
|
|
int n;
|
|
|
|
ob = current_object;
|
|
if (!current_variables
|
|
|| !ob->variables
|
|
|| current_variables < ob->variables
|
|
|| current_variables >= ob->variables + ob->prog->num_variables)
|
|
{
|
|
/* efun closures are called without changing current_prog
|
|
* nor current_variables. This keeps the program scope for
|
|
* variables for calls inside this_object(), but would
|
|
* give trouble with calling from other ones if it were
|
|
* not for this test.
|
|
*/
|
|
current_prog = ob->prog;
|
|
current_variables = ob->variables;
|
|
}
|
|
|
|
/* If the variable exists, it must exist as shared
|
|
* string.
|
|
*/
|
|
s = find_tabled_str(name);
|
|
if (!s)
|
|
{
|
|
*svp = const0;
|
|
break; /* switch(ct) */
|
|
}
|
|
|
|
prog = current_prog;
|
|
var = prog->variables;
|
|
num_var = prog->num_variables;
|
|
for (n = num_var; --n >= 0; var++)
|
|
{
|
|
if (mstreq(var->name, s)
|
|
&& !(var->type.typeflags & NAME_HIDDEN))
|
|
break;
|
|
}
|
|
if (n < 0)
|
|
{
|
|
*svp = const0;
|
|
break; /* switch(ct) */
|
|
}
|
|
|
|
n = num_var - n - 1;
|
|
closure_identifier(svp, current_object
|
|
, (unsigned short)(n + (current_variables - current_object->variables))
|
|
, /* raise_error: */ MY_FALSE);
|
|
if (svp->type != T_CLOSURE)
|
|
{
|
|
/* Out of memory: abort restoring this closure. */
|
|
break; /* switch(ct) */
|
|
}
|
|
|
|
break;
|
|
} /* case 'v' */
|
|
|
|
case 'c': /* A context closure */
|
|
case 'l': /* A lfun closure */
|
|
{
|
|
string_t *s;
|
|
int i;
|
|
program_t *inhProg = NULL;
|
|
int fun_ix_offs = 0;
|
|
#ifdef USE_NEW_INLINES
|
|
size_t context_size = 0;
|
|
#endif
|
|
|
|
if (name_delim == '|' || name_delim == '-')
|
|
{
|
|
char progname_delim;
|
|
int last_fun_ix_offs = 0;
|
|
int last_num_functions = 0;
|
|
|
|
/* An inherited lfun closure */
|
|
inhProg = current_object->prog;
|
|
|
|
do
|
|
{
|
|
char *progname_start, *progname_dest;
|
|
int progname_length;
|
|
inherit_t *inheritp;
|
|
unsigned short inhCount;
|
|
|
|
progname_start = pt;
|
|
progname_dest = pt;
|
|
|
|
for(;;)
|
|
{
|
|
char c;
|
|
|
|
if ( !(c = *pt++) )
|
|
{
|
|
*svp = const0;
|
|
return MY_FALSE;
|
|
}
|
|
|
|
if (c == '\\')
|
|
c = *pt++;
|
|
else if (c==delimiter
|
|
|| (name_delim=='|' && c=='|')
|
|
|| (ct=='c' && c==':'))
|
|
break;
|
|
|
|
*progname_dest++ = c;
|
|
}
|
|
|
|
progname_delim = pt[-1];
|
|
|
|
if (inhProg) /* Not yet aborted. */
|
|
{
|
|
last_fun_ix_offs = fun_ix_offs;
|
|
last_num_functions = inhProg->num_functions;
|
|
|
|
*progname_dest = '\0';
|
|
progname_length = progname_dest - progname_start;
|
|
|
|
/* Lookup the inherit */
|
|
inheritp = inhProg->inherit;
|
|
|
|
for ( inhCount = inhProg->num_inherited;
|
|
inhCount > 0; inheritp++, inhCount--)
|
|
{
|
|
int l;
|
|
|
|
if (inheritp->inherit_type & INHERIT_TYPE_DUPLICATE)
|
|
continue;
|
|
|
|
l = mstrsize(inheritp->prog->name)-2;
|
|
if (l != progname_length)
|
|
continue;
|
|
|
|
if (strncmp(progname_start,
|
|
get_txt(inheritp->prog->name),
|
|
progname_length) != 0)
|
|
continue;
|
|
|
|
/* Found the inherit. */
|
|
inhProg = inheritp->prog;
|
|
fun_ix_offs += inheritp->function_index_offset;
|
|
|
|
break;
|
|
}
|
|
|
|
if (!inhCount)
|
|
{
|
|
/* No inherit found. Let the while loop go
|
|
* to the end of the string. */
|
|
inhProg = NULL;
|
|
fun_ix_offs = -1;
|
|
}
|
|
}
|
|
}
|
|
while (progname_delim == '|');
|
|
|
|
/* Restore delimiter. */
|
|
pt[-1] = progname_delim;
|
|
*str = pt;
|
|
|
|
if(inhProg)
|
|
{
|
|
/* Security check. We only allow closures that
|
|
* can be built by the current program and by the
|
|
* child programs. That means, the child program
|
|
* of inhProg must be or inherits the current program
|
|
* somehow. This is checked using the function index
|
|
* offset.
|
|
*/
|
|
|
|
if (function_index_offset < last_fun_ix_offs
|
|
|| function_index_offset + current_prog->num_functions
|
|
> last_fun_ix_offs + last_num_functions)
|
|
{
|
|
inhProg = NULL;
|
|
fun_ix_offs = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef USE_NEW_INLINES
|
|
if (ct == 'c')
|
|
{
|
|
svalue_t num = const0;
|
|
|
|
/* Parse the context size information */
|
|
if (!restore_svalue(&num, str, ':')
|
|
|| num.type != T_NUMBER
|
|
|| num.u.number <= 0
|
|
)
|
|
{
|
|
free_svalue(&num);
|
|
*svp = const0;
|
|
return MY_FALSE;
|
|
}
|
|
context_size = num.u.number;
|
|
}
|
|
#endif
|
|
|
|
/* If the function exists, it must exist as shared
|
|
* string.
|
|
*/
|
|
if (fun_ix_offs < 0) /* No need to lookup in case of an error. */
|
|
s = NULL;
|
|
else
|
|
s = find_tabled_str(name);
|
|
/* Although s is NULL, we parse to the end. */
|
|
|
|
if (s)
|
|
i = find_function(s, inhProg?inhProg:current_object->prog);
|
|
else
|
|
i = -1;
|
|
|
|
/* If the function exists and is visible, create the closure.
|
|
*/
|
|
if (i >= 0)
|
|
{
|
|
closure_lfun(svp, current_object, inhProg
|
|
, (unsigned short)i + fun_ix_offs
|
|
#ifdef USE_NEW_INLINES
|
|
, context_size
|
|
#endif /* USE_NEW_INLINES */
|
|
, /* raise_error: */ MY_FALSE);
|
|
|
|
if (svp->type != T_CLOSURE)
|
|
{
|
|
/* Out of memory: abort restoring this closure. */
|
|
break; /* switch(ct) */
|
|
}
|
|
|
|
/* Note: the *svp must be set up before any context
|
|
* values are restored, otherwise context values
|
|
* referring to this very closure will be restored
|
|
* as '0'.
|
|
*/
|
|
#ifdef USE_NEW_INLINES
|
|
if (context_size > 0)
|
|
{
|
|
svalue_t context = const0;
|
|
int j;
|
|
lambda_t * l = svp->u.lambda;
|
|
|
|
/* Parse the context information */
|
|
if (!restore_svalue(&context, str, delimiter)
|
|
|| context.type != T_POINTER
|
|
|| VEC_SIZE(context.u.vec) != context_size
|
|
)
|
|
{
|
|
l->function.lfun.context_size = 0;
|
|
free_svalue(svp);
|
|
free_svalue(&context);
|
|
*svp = const0;
|
|
return MY_FALSE;
|
|
}
|
|
|
|
for (j = 0; (size_t)j < context_size; j++)
|
|
assign_svalue_no_free(l->context+j, context.u.vec->item+j);
|
|
free_array(context.u.vec);
|
|
}
|
|
#endif /* USE_NEW_INLINES */
|
|
}
|
|
else /* (i < 0) */
|
|
{
|
|
*svp = const0;
|
|
|
|
if (context_size > 0)
|
|
{
|
|
svalue_t context = const0;
|
|
|
|
/* Parse the string to its end. */
|
|
if (!restore_svalue(&context, str, delimiter))
|
|
return MY_FALSE;
|
|
else
|
|
free_svalue(&context);
|
|
}
|
|
}
|
|
break;
|
|
} /* case 'c', 'l' */
|
|
} /* switch(ct) */
|
|
|
|
/* Regardless of the restored closure, *str at this point
|
|
* points to the character after the delimiter.
|
|
* Make the delimiter visible again, and also restore the
|
|
* 'name' delimiter to its original setting.
|
|
*/
|
|
*str = *str - 1;
|
|
*nameend = name_delim;
|
|
return MY_TRUE;
|
|
} /* restore_closure() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static Bool
|
|
restore_svalue (svalue_t *svp, char **pt, char delimiter)
|
|
|
|
/* Restore an svalue from the text starting at *<pt> up to the <delimiter>,
|
|
* storing the value in *<svp>.
|
|
* On success, set *<pt> to the character after the <delimiter> and return
|
|
* TRUE, else return FALSE.
|
|
*/
|
|
|
|
{
|
|
char *cp;
|
|
|
|
assert_stack_gap();
|
|
|
|
switch( *(cp = *pt) )
|
|
{
|
|
case '#': /* A closure or quoted thing */
|
|
{
|
|
*pt = ++cp;
|
|
switch (*cp)
|
|
{
|
|
case 'e': /* An efun closure */
|
|
case 's': /* A sefun closure */
|
|
case 'v': /* A variable closure */
|
|
case 'c': /* A lfun closure */
|
|
case 'l': /* A lfun closure */
|
|
if ( !restore_closure(svp, pt, delimiter) )
|
|
{
|
|
return MY_FALSE;
|
|
}
|
|
break;
|
|
|
|
case '0': case '1': case '2': case '3': case '4':
|
|
case '5': case '6': case '7': case '8': case '9':
|
|
{
|
|
long quotes;
|
|
char * end;
|
|
Bool rc;
|
|
|
|
quotes = strtol(cp, &end, 10);
|
|
if (!end || end == cp || *end != ':')
|
|
{
|
|
*svp = const0;
|
|
return MY_FALSE;
|
|
}
|
|
*pt = end+1;
|
|
rc = restore_svalue(svp, pt, delimiter);
|
|
if (rc)
|
|
{
|
|
svp->x.quotes = (ph_int)quotes;
|
|
if (svp->type == T_STRING)
|
|
svp->type = T_SYMBOL;
|
|
else if (svp->type == T_POINTER)
|
|
svp->type = T_QUOTED_ARRAY;
|
|
return MY_TRUE;
|
|
}
|
|
else
|
|
return MY_FALSE;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
*svp = const0;
|
|
return MY_FALSE;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
#ifdef USE_RESTORED_OBJECTS
|
|
case '`': /* An object, restore it as a string */
|
|
{
|
|
char *source, *start, c;
|
|
string_t *str;
|
|
|
|
start = cp;
|
|
source = cp+1;
|
|
|
|
for(;;)
|
|
{
|
|
if ( !(c = *source++) )
|
|
{
|
|
*svp = const0;
|
|
return MY_FALSE;
|
|
}
|
|
|
|
if (c == '`') break;
|
|
*cp++ = c;
|
|
}
|
|
*cp = '\0';
|
|
*pt = source;
|
|
|
|
str = new_tabled(start);
|
|
#if 0
|
|
object_t *ob;
|
|
/*
|
|
using object restoration here crashes after a while...
|
|
|
|
Core was generated by `/ve/src/psyclpc --pidfile /ve/klotz.pid -d -d -d -d -d -d -d -DDEVELOPMENT -E33'.
|
|
Program terminated with signal 11, Segmentation fault.
|
|
#0 0x080cd3a9 in handle_newly_destructed_objects () at simulate.c:2936
|
|
2936 for (i = 0; i < ob->prog->num_variables; i++)
|
|
(gdb) bt
|
|
#0 0x080cd3a9 in handle_newly_destructed_objects () at simulate.c:2936
|
|
#1 0x08050f95 in backend () at backend.c:405
|
|
#2 0x080a3967 in main (argc=Cannot access memory at address 0x7
|
|
) at main.c:617
|
|
*/
|
|
ob = lookfor_object(str, MY_FALSE);
|
|
if (ob == NULL) {
|
|
/* errorf("restore failed: can't get object '%s'\n", start); */
|
|
#endif
|
|
#if 0
|
|
/* put a zero, just like in the old days */
|
|
put_number(svp, 0);
|
|
#else
|
|
/* put the name of the object as a string */
|
|
put_string(svp, str);
|
|
if (!svp->u.str) {
|
|
*svp = const0;
|
|
errorf("(restore) Out of memory (%lu bytes) for string.\n"
|
|
, (unsigned long) strlen(start));
|
|
}
|
|
#endif
|
|
#if 0
|
|
}
|
|
else put_object(svp, ob);
|
|
#endif
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
case '\"': /* A string */
|
|
{
|
|
char *source, *start, c;
|
|
|
|
start = cp;
|
|
source = cp+1;
|
|
|
|
for(;;)
|
|
{
|
|
if ( !(c = *source++) )
|
|
{
|
|
*svp = const0;
|
|
return MY_FALSE;
|
|
}
|
|
|
|
#ifndef MSDOS_FS
|
|
if (c == '\r')
|
|
c = '\n';
|
|
#endif
|
|
|
|
if (c == '\\')
|
|
{
|
|
if ( !(c = *source++) )
|
|
{
|
|
*svp = const0;
|
|
return MY_FALSE; /* String ends with a \\ buggy probably */
|
|
}
|
|
switch(c)
|
|
{
|
|
case '0': c = '\0'; break;
|
|
case 'a': c = '\007'; break;
|
|
case 'b': c = '\b' ; break;
|
|
case 't': c = '\t' ; break;
|
|
case 'n': c = '\n' ; break;
|
|
case 'v': c = '\013'; break;
|
|
case 'f': c = '\014'; break;
|
|
case 'r': c = '\r' ; break;
|
|
}
|
|
} else if (c == '\"') break;
|
|
*cp++ = c;
|
|
}
|
|
*cp = '\0';
|
|
*pt = source;
|
|
put_string(svp, new_tabled(start));
|
|
if (!svp->u.str)
|
|
{
|
|
*svp = const0;
|
|
errorf("(restore) Out of memory (%zu bytes) for string.\n"
|
|
, strlen(start));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case '(': /* Unshared mapping, struct or array */
|
|
*pt = cp+2;
|
|
switch ( cp[1] )
|
|
{
|
|
case '[':
|
|
{
|
|
if ( !restore_mapping(svp, pt) )
|
|
{
|
|
return MY_FALSE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case '{':
|
|
{
|
|
if ( !restore_array(svp, pt) )
|
|
{
|
|
return MY_FALSE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
#ifdef USE_STRUCTS
|
|
case '<':
|
|
{
|
|
if ( !restore_struct(svp, pt) )
|
|
{
|
|
return MY_FALSE;
|
|
}
|
|
break;
|
|
}
|
|
#endif /* USE_STRUCTS */
|
|
|
|
default:
|
|
*svp = const0;
|
|
return MY_FALSE;
|
|
}
|
|
break;
|
|
|
|
case '-': /* A number */
|
|
case '0': case '1': case '2': case '3': case '4':
|
|
case '5': case '6': case '7': case '8': case '9':
|
|
{
|
|
char c, *numstart = cp;
|
|
int nega = 0;
|
|
long l = 0;
|
|
|
|
if (*cp == '-')
|
|
{
|
|
nega = 1;
|
|
cp++;
|
|
}
|
|
|
|
while(lexdigit(c = *cp++)) l = (((l << 2) + l) << 1) + (c - '0');
|
|
if (c != '.')
|
|
{
|
|
put_number(svp, nega ? -l : l);
|
|
*pt = cp;
|
|
return c == delimiter;
|
|
}
|
|
|
|
/* If a float was written by the same host type as we are using,
|
|
* restore the internal representation.
|
|
* Otherwise, parse the float normally.
|
|
*/
|
|
svp->type = T_FLOAT;
|
|
if ( NULL != (cp = strchr(cp, '=')) && restored_host == CURRENT_HOST)
|
|
{
|
|
cp++;
|
|
if (sscanf(cp, "%"SCNxPHINT":%"SCNxPINT, &svp->x.exponent, &svp->u.mantissa) != 2)
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
STORE_DOUBLE_USED
|
|
double d;
|
|
|
|
d = atof(cp = numstart);
|
|
STORE_DOUBLE(svp, d);
|
|
}
|
|
cp = strchr(cp, delimiter);
|
|
*pt = cp+1;
|
|
return cp != NULL;
|
|
}
|
|
|
|
case '<': /* A shared value */
|
|
{
|
|
int id;
|
|
|
|
id = atoi(cp+1);
|
|
cp = strchr(cp, '>');
|
|
if (!cp)
|
|
{
|
|
*svp = const0;
|
|
return MY_FALSE;
|
|
}
|
|
|
|
/* If a '=' follows, this is the first occurance of this
|
|
* shared value, therefore restore it normally.
|
|
*/
|
|
if (cp[1] == '=')
|
|
{
|
|
int res;
|
|
|
|
*pt = cp+2;
|
|
|
|
/* Shared values can be used even before they have been read in
|
|
* completely.
|
|
*/
|
|
if (id != ++current_shared_restored)
|
|
{
|
|
current_shared_restored--;
|
|
*svp = const0;
|
|
return MY_FALSE;
|
|
}
|
|
|
|
/* Increase shared_restored_values[] if necessary */
|
|
|
|
if (id > max_shared_restored)
|
|
{
|
|
svalue_t *new;
|
|
|
|
max_shared_restored *= 2;
|
|
new = rexalloc(shared_restored_values
|
|
, sizeof(svalue_t)*max_shared_restored
|
|
);
|
|
if (!new)
|
|
{
|
|
current_shared_restored--;
|
|
*svp = const0;
|
|
errorf("(restore) Out of memory (%lu bytes) for "
|
|
"%ld shared values.\n"
|
|
, (unsigned long)max_shared_restored * sizeof(svalue_t)
|
|
, max_shared_restored);
|
|
return MY_FALSE;
|
|
}
|
|
shared_restored_values = new;
|
|
}
|
|
|
|
/* in case of an error... */
|
|
*svp = const0;
|
|
shared_restored_values[id-1] = const0;
|
|
|
|
/* Restore the value */
|
|
res = restore_svalue(&shared_restored_values[id-1], pt, delimiter);
|
|
assign_svalue_no_free(svp, &shared_restored_values[id-1]);
|
|
return res;
|
|
}
|
|
|
|
if (id <= 0 || id > current_shared_restored)
|
|
{
|
|
*svp = const0;
|
|
return MY_FALSE;
|
|
}
|
|
|
|
/* We know this value already: simply assign it */
|
|
|
|
assign_svalue_no_free(svp, &shared_restored_values[id-1]);
|
|
|
|
cp = strchr(cp, delimiter);
|
|
*pt = cp+1;
|
|
return cp != NULL;
|
|
}
|
|
|
|
default:
|
|
*svp = const0;
|
|
return MY_FALSE;
|
|
|
|
} /* switch()*/
|
|
|
|
cp = *pt;
|
|
if (delimiter == '\n' && *cp == '\r')
|
|
cp++;
|
|
if (*cp++ != delimiter)
|
|
return MY_FALSE;
|
|
*pt = cp;
|
|
|
|
return MY_TRUE;
|
|
} /* restore_svalue() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static Bool
|
|
old_restore_string (svalue_t *v, char *str)
|
|
|
|
/* Called to restore the string starting at <str> into the *<v>
|
|
* from old-format savefiles
|
|
* Return TRUE on success, FALSE else.
|
|
*
|
|
* In this format, no escaped characters exist.
|
|
*/
|
|
|
|
{
|
|
char *cp, c;
|
|
|
|
cp = ++str;
|
|
if ( '\0' != (c = *cp++) )
|
|
{
|
|
do {
|
|
#ifndef MSDOS_FS
|
|
if (c == '\r')
|
|
cp[-1] = '\n';
|
|
#else
|
|
if (c == CTRLZ)
|
|
cp[-1] = '\n';
|
|
#endif
|
|
} while ( '\0' != (c = *cp++) );
|
|
|
|
if (cp[-2] == '\n' && cp[-3] == '\"')
|
|
{
|
|
cp[-3] = '\0';
|
|
put_string(v, new_tabled(str));
|
|
if (!v->u.str)
|
|
{
|
|
*v = const0;
|
|
errorf("(restore) Out of memory (%zu bytes) for string\n"
|
|
, strlen(str));
|
|
}
|
|
return MY_TRUE;
|
|
}
|
|
}
|
|
*v = const0;
|
|
return MY_FALSE;
|
|
} /* old_restore_string() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
/* Cleanup structure for restore_object().
|
|
*/
|
|
|
|
struct discarded {
|
|
svalue_t v;
|
|
struct discarded *next;
|
|
};
|
|
|
|
typedef struct restore_cleanup_s {
|
|
svalue_t head; /* The T_ERROR_HANDLER structure */
|
|
int * pNesting; /* The nesting counter */
|
|
char * buff; /* The optional allocated line buffer. */
|
|
FILE * f; /* The optional input file */
|
|
struct discarded * dp;
|
|
char * filename; /* optional buffer for the filename */
|
|
/* List of values for which the variables no longer exist. */
|
|
} restore_cleanup_t;
|
|
|
|
|
|
static void
|
|
restore_object_cleanup ( svalue_t * arg)
|
|
|
|
/* The error handler during restore_object cleanup: free all resources
|
|
* and update the nesting.
|
|
*/
|
|
|
|
{
|
|
restore_cleanup_t * data = (restore_cleanup_t *)arg;
|
|
|
|
while (data->dp)
|
|
{
|
|
struct discarded * next = data->dp->next;
|
|
free_svalue(&data->dp->v);
|
|
xfree(data->dp);
|
|
data->dp = next;
|
|
}
|
|
|
|
if (*data->pNesting > 1)
|
|
xfree(data->buff);
|
|
else
|
|
mb_free(mbFile);
|
|
|
|
if (data->f)
|
|
fclose(data->f);
|
|
|
|
(*data->pNesting)--;
|
|
|
|
free_shared_restored_values();
|
|
|
|
if (data->filename)
|
|
xfree(data->filename);
|
|
|
|
xfree(arg);
|
|
} /* restore_object_cleanup() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
f_restore_object (svalue_t *sp)
|
|
|
|
/* EFUN restore_object()
|
|
*
|
|
* int restore_object (string name)
|
|
* int restore_object (string str)
|
|
*
|
|
* Restore values of variables for current object from the file <name>,
|
|
* or directly from the string <str>.
|
|
*
|
|
* To restore directly from a string <str>, the string must begin
|
|
* with the typical line "#x:y" as it is created by the save_object()
|
|
* efun.
|
|
*
|
|
* When restoring from a file, the name may end in ".c" which is stripped
|
|
* off by the parser. The master object will probably append a .o to the
|
|
* <name>. The validity of the filename is checked with a call to
|
|
* check_valid_path().
|
|
*
|
|
* Return 1 on success, 0 if there was nothing to restore.
|
|
* TODO: This double-mode is rather ugly, maybe call the restoring
|
|
* TODO:: from string-mode "restore_variables()"?
|
|
*/
|
|
|
|
{
|
|
static int nesting = 0; /* Used to detect recursive calls */
|
|
int restored_version; /* Formatversion of the saved data */
|
|
char *name; /* Full name of the file to read */
|
|
char *file; /* Filename passed, NULL if restoring from a string */
|
|
int lineno; /* Line number in file, for error messages */
|
|
string_t *var;
|
|
char *buff; /* Current line read from the savefile
|
|
* resp. a copy of the string passed.
|
|
*/
|
|
char *cur; /* Current position in the string passed */
|
|
char *space;
|
|
object_t *ob; /* Local copy of current_object */
|
|
size_t len;
|
|
FILE *f;
|
|
struct stat st; /* stat() info of the savefile */
|
|
svalue_t *arg; /* pointer to the argument on the stack - for convenience */
|
|
|
|
int var_rest; /* Number of variables left after rover */
|
|
int num_var; /* Number of variables in the object */
|
|
variable_t *rover = NULL;
|
|
/* Roving pointer through the variable block. The next variable
|
|
* to restore is searched from here, taking advantage of
|
|
* the locality of save_object().
|
|
*/
|
|
restore_cleanup_t * rcp;
|
|
/* Cleanup structure */
|
|
|
|
arg = sp;
|
|
|
|
/* Allocate memory for the error cleanup structure first. We need it
|
|
* anway and if we can't allocate it, then this is anyway a lost cause. */
|
|
rcp = xalloc(sizeof(*rcp));
|
|
if (!rcp)
|
|
{
|
|
errorf("(restore) Out of memory: (%zu bytes) for cleanup structure\n"
|
|
, sizeof(*rcp));
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
rcp->pNesting = &nesting;
|
|
rcp->buff = NULL;
|
|
rcp->f = NULL;
|
|
rcp->dp = NULL;
|
|
rcp->filename = NULL;
|
|
/* Push it on top of the argument on the stack. */
|
|
sp = push_error_handler(restore_object_cleanup, &(rcp->head));
|
|
|
|
/* Keep track of recursive calls */
|
|
nesting++;
|
|
|
|
/* No use in restoring a destructed object, or an object
|
|
* with no variables. Do this check now before we allocate
|
|
* any memory.
|
|
*/
|
|
ob = current_object;
|
|
if (ob->flags & O_DESTRUCTED)
|
|
{
|
|
sp = pop_n_elems(2, sp); /* pop and free error handler + argument */
|
|
sp++;
|
|
put_number(sp, 0);
|
|
return sp;
|
|
}
|
|
|
|
/* no need to restore objects without variables */
|
|
if (ob->prog->num_variables == 0)
|
|
{
|
|
sp = pop_n_elems(2, sp); /* pop and free error handler + argument */
|
|
sp++;
|
|
put_number(sp, 1);
|
|
return sp;
|
|
}
|
|
|
|
/* Check if got a filename or the value string itself */
|
|
buff = NULL;
|
|
name = NULL;
|
|
file = NULL;
|
|
f = NULL;
|
|
lineno = 0;
|
|
if (get_txt(arg->u.str)[0] == '#')
|
|
{
|
|
/* We need a copy of the value string because we're
|
|
* going to modify it a bit.
|
|
*/
|
|
len = mstrsize(arg->u.str);
|
|
buff = (nesting > 1) ? xalloc(len+1) : mb_alloc(mbFile, len+1);
|
|
if (buff == NULL)
|
|
{
|
|
outofmem(len+1, "copy of value string");
|
|
}
|
|
/* keep track of buff in the cleanup structure. */
|
|
rcp->buff = buff;
|
|
memcpy(buff, get_txt(arg->u.str), len);
|
|
buff[len] = '\0';
|
|
}
|
|
else
|
|
{
|
|
file = get_txt(arg->u.str);
|
|
}
|
|
|
|
/* If restoring from a file, set it up */
|
|
|
|
if (file)
|
|
{
|
|
string_t *sfile;
|
|
|
|
/* Get a valid filename */
|
|
|
|
sfile = check_valid_path(arg->u.str, ob, STR_RESTORE_OBJECT, MY_FALSE);
|
|
if (sfile == NULL)
|
|
{
|
|
errorf("Illegal use of restore_object('%s')\n", get_txt(arg->u.str));
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
|
|
/* Create the full filename */
|
|
len = mstrsize(sfile);
|
|
name = xalloc(len + (sizeof save_file_suffix));
|
|
if (!name)
|
|
{
|
|
free_mstring(sfile);
|
|
errorf("Out of memory (%zu bytes) for filename buffer in "
|
|
"restore_object('%s')\n", len, get_txt(arg->u.str));
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
rcp->filename = name; /* in case of errrors -> cleanup structure */
|
|
|
|
strcpy(name, get_txt(sfile));
|
|
if (name[len-2] == '.' && name[len-1] == 'c')
|
|
len -= 2;
|
|
strcpy(name+len, save_file_suffix);
|
|
|
|
free_mstring(sfile);
|
|
|
|
/* Open the file and gets its length */
|
|
f = fopen(name, "r");
|
|
if (!f || fstat(fileno(f), &st) == -1) {
|
|
if (f)
|
|
fclose(f);
|
|
sp = pop_n_elems(2, sp); /* pop and free error handler + argument */
|
|
sp++;
|
|
put_number(sp, 0);
|
|
return sp;
|
|
}
|
|
rcp->f = f; /* keep track of f in case of errors */
|
|
if (st.st_size == 0)
|
|
{
|
|
sp = pop_n_elems(2, sp); /* pop and free error handler + argument */
|
|
sp++;
|
|
put_number(sp, 0);
|
|
return sp;
|
|
}
|
|
FCOUNT_REST(name);
|
|
|
|
/* Allocate the linebuffer. Unfortunately, the whole file
|
|
* can be one single line.
|
|
*/
|
|
buff = (nesting > 1) ? xalloc((size_t)(st.st_size + 1))
|
|
: mb_alloc(mbFile, (size_t)(st.st_size+1));
|
|
if (!buff)
|
|
{
|
|
/* TODO: st_size is off_t which is most often int64_t or int32_t.
|
|
* TODO:: PRIdMAX or PRId64 should be used, I think. */
|
|
errorf("(restore) Out of memory (%ld bytes) for linebuffer.\n"
|
|
, (long) st.st_size+1);
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
rcp->buff = buff;
|
|
} /* if (file) */
|
|
|
|
/* Initialise the variables */
|
|
|
|
max_shared_restored = 64;
|
|
current_shared_restored = 0;
|
|
|
|
if (shared_restored_values)
|
|
{
|
|
debug_message("(restore) Freeing lost shared_restored_values.\n");
|
|
free_shared_restored_values();
|
|
}
|
|
|
|
shared_restored_values = xalloc(sizeof(svalue_t)*max_shared_restored);
|
|
|
|
if (!shared_restored_values)
|
|
{
|
|
errorf("(restore) Out of memory (%lu bytes) for shared values.\n"
|
|
, sizeof(svalue_t)*(unsigned long)max_shared_restored);
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
|
|
num_var = ob->prog->num_variables;
|
|
var_rest = 0;
|
|
restored_version = -1;
|
|
restored_host = -1;
|
|
|
|
/* Loop until we run out of text to parse */
|
|
|
|
cur = buff;
|
|
while(1)
|
|
{
|
|
svalue_t *v;
|
|
char *pt;
|
|
|
|
if (file)
|
|
{
|
|
/* Get the next line from the text */
|
|
lineno++;
|
|
if (fgets(buff, (int)st.st_size + 1, f) == NULL)
|
|
break;
|
|
cur = buff;
|
|
}
|
|
else if (cur[0] == '\0')
|
|
break;
|
|
|
|
/* Remember that we have a newline, and maybe even a CRLF at end of
|
|
* buff!
|
|
*/
|
|
pt = strchr(cur, '\r');
|
|
if (pt && pt[1] == '\n') /* Convert a CRLF into a LF */
|
|
*pt = '\n';
|
|
pt = NULL;
|
|
|
|
|
|
space = strchr(cur, ' ');
|
|
if (!file)
|
|
pt = strchr(cur, '\n');
|
|
else
|
|
pt = NULL;
|
|
|
|
if (space == NULL || (!file && pt && pt < space))
|
|
{
|
|
/* No space? It must be the version line! */
|
|
|
|
if (cur[0] == '#')
|
|
{
|
|
int i;
|
|
|
|
i = sscanf(cur+1, "%d:%d", &restored_version, &restored_host);
|
|
if (i > 0 && (i == 2 || restored_version >= CURRENT_VERSION) )
|
|
{
|
|
if (pt)
|
|
cur = pt+1;
|
|
else if (!file)
|
|
break;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* No version line: illegal format.
|
|
* Most of the cleanup will be done by the error handler during
|
|
* stack unwinding.
|
|
*/
|
|
if (file)
|
|
errorf("Illegal format (version line) when restoring %s "
|
|
"from %s line %d.\n"
|
|
, get_txt(current_object->name), name, lineno);
|
|
else
|
|
errorf("Illegal format (version line) when restoring %s.\n"
|
|
, get_txt(current_object->name));
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
|
|
/* Split the line at the position of the space.
|
|
* Left of it is the variable name, to the right is the value.
|
|
*/
|
|
*space = '\0';
|
|
|
|
/* Set 'v' to the variable to restore */
|
|
|
|
v = NULL;
|
|
|
|
do { /* A simple try.. environment */
|
|
|
|
if ( NULL != (var = find_tabled_str(cur)) )
|
|
{
|
|
/* The name exists in an object somewhere, now check if it
|
|
* is one of our variables
|
|
*/
|
|
|
|
do
|
|
rover++;
|
|
while ( --var_rest > 0
|
|
&& (rover->name != var
|
|
|| rover->type.typeflags & TYPE_MOD_STATIC)
|
|
);
|
|
|
|
if (var_rest > 0)
|
|
{
|
|
v = &ob->variables[num_var-var_rest];
|
|
break;
|
|
}
|
|
|
|
/* Wrap around and search again */
|
|
|
|
rover = ob->prog->variables-1;
|
|
var_rest = num_var + 1;
|
|
do
|
|
rover++;
|
|
while (--var_rest > 0
|
|
&& (rover->name != var
|
|
|| rover->type.typeflags & TYPE_MOD_STATIC)
|
|
);
|
|
if (var_rest > 0)
|
|
{
|
|
v = &ob->variables[num_var-var_rest];
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* No 'else', but if we come here, the variable name was
|
|
* not found in the shared string table or in the object.
|
|
* That means we can eventually discard this line, but first
|
|
* we have to parse it in case it contains the definition
|
|
* of a shared array some other variable might use.
|
|
*
|
|
* Therefore we create a dummy variable and initialize
|
|
* it to svalue-int, so that it can be freed without remorse.
|
|
*/
|
|
|
|
{
|
|
struct discarded *tmp;
|
|
|
|
tmp = (struct discarded *)xalloc(sizeof(struct discarded));
|
|
if (!tmp)
|
|
{
|
|
if (file)
|
|
errorf("Out of memory when restoring %s "
|
|
"from %s line %d.\n"
|
|
, get_txt(current_object->name), name, lineno);
|
|
else
|
|
errorf("Out of memory when restoring %s.\n"
|
|
, get_txt(current_object->name));
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
|
|
tmp->next = rcp->dp;
|
|
rcp->dp = tmp;
|
|
v = &tmp->v;
|
|
v->type = T_NUMBER;
|
|
break;
|
|
}
|
|
|
|
} while (MY_FALSE);
|
|
|
|
/* Get rid of the old value in v */
|
|
|
|
free_svalue(v);
|
|
*v = const0;
|
|
|
|
/* ...and set it to the new one */
|
|
|
|
pt = space+1;
|
|
if ( (restored_version < 0 && pt[0] == '\"')
|
|
? !old_restore_string(v, pt)
|
|
: !restore_svalue(v, &pt, '\n')
|
|
)
|
|
{
|
|
|
|
/* Whoops, illegal format */
|
|
|
|
if (file)
|
|
errorf("Illegal format (value string) when restoring %s "
|
|
"from %s line %d.\n"
|
|
, get_txt(current_object->name), name, lineno);
|
|
else
|
|
errorf("Illegal format (value string) when restoring %s.\n"
|
|
, get_txt(current_object->name));
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
|
|
cur = pt;
|
|
} /* while(1) */
|
|
|
|
/* Restore complete - now clean up */
|
|
|
|
if (d_flag > 1)
|
|
debug_message("%s Object %s restored from %s.\n"
|
|
, time_stamp(), get_txt(ob->name)
|
|
, file ? name : "passed value");
|
|
|
|
free_svalue(sp--); /* calls the cleanup handler */
|
|
free_svalue(sp); /* frees the argument */
|
|
put_number(sp, 1);
|
|
return sp;
|
|
|
|
} /* f_restore_object() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
restore_value_cleanup ( svalue_t * arg )
|
|
|
|
/* The error handler during restore value cleanup: free all resources.
|
|
*/
|
|
|
|
{
|
|
restore_cleanup_t * data = (restore_cleanup_t *) arg;
|
|
|
|
if (data->buff)
|
|
xfree(data->buff);
|
|
|
|
free_shared_restored_values();
|
|
|
|
xfree(arg);
|
|
} /* restore_value_cleanup() */
|
|
|
|
svalue_t *
|
|
f_restore_value (svalue_t *sp)
|
|
|
|
/* EFUN restore_value()
|
|
*
|
|
* mixed restore_value (string str)
|
|
*
|
|
* Decode the string representation <str> of a value back into the value
|
|
* itself and return it. <str> is a string as generated by save_value(),
|
|
* the '#x:y' specification of the saveformat however is optional.
|
|
*/
|
|
|
|
{
|
|
int restored_version; /* Formatversion of the saved data */
|
|
char *buff; /* The string to parse */
|
|
char *p;
|
|
restore_cleanup_t *rcp; /* Cleanup structure */
|
|
|
|
/* The restore routines will put \0s into the string, so we
|
|
* need to make a copy of all but malloced strings.
|
|
*/
|
|
{
|
|
size_t len;
|
|
|
|
len = mstrsize(sp->u.str);
|
|
buff = xalloc(len+1);
|
|
if (!buff)
|
|
{
|
|
errorf("(restore) Out of memory (%zu bytes).\n"
|
|
, len+1);
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
memcpy(buff, get_txt(sp->u.str), len);
|
|
buff[len] = '\0';
|
|
}
|
|
|
|
restored_version = -1;
|
|
restored_host = -1;
|
|
|
|
/* Initialise the shared value table */
|
|
|
|
max_shared_restored = 64;
|
|
|
|
if (shared_restored_values)
|
|
{
|
|
debug_message("(restore) Freeing lost shared_restored_values.\n");
|
|
free_shared_restored_values();
|
|
}
|
|
|
|
shared_restored_values = xalloc(sizeof(svalue_t)*max_shared_restored);
|
|
if (!shared_restored_values)
|
|
{
|
|
xfree(buff);
|
|
errorf("(restore) Out of memory (%lu bytes) for shared values.\n"
|
|
, (unsigned long)max_shared_restored * sizeof(svalue_t));
|
|
return sp; /* flow control hint */
|
|
}
|
|
|
|
current_shared_restored = 0;
|
|
|
|
/* Place the result variable onto the stack */
|
|
inter_sp = ++sp;
|
|
*sp = const0;
|
|
|
|
/* Setup the error cleanup */
|
|
rcp = xalloc(sizeof(*rcp));
|
|
if (!rcp)
|
|
{
|
|
xfree(buff);
|
|
errorf("(restore) Out of memory (%zu bytes).\n"
|
|
, sizeof(*rcp));
|
|
/* NOTREACHED */
|
|
return sp;
|
|
}
|
|
rcp->buff = buff;
|
|
|
|
push_error_handler(restore_value_cleanup, &(rcp->head));
|
|
|
|
/* Check if there is a version line */
|
|
if (buff[0] == '#')
|
|
{
|
|
int i;
|
|
|
|
i = sscanf(buff+1, "%d:%d", &restored_version, &restored_host);
|
|
|
|
/* Advance to the next line */
|
|
p = strchr(buff, '\n');
|
|
if (!p)
|
|
{
|
|
errorf("No data given.\n");
|
|
return sp-1;
|
|
}
|
|
p++;
|
|
}
|
|
else
|
|
p = buff; /* parse from beginning of buffer */
|
|
|
|
|
|
/* Now parse the value in buff[] */
|
|
|
|
if ( (restored_version < 0 && p[0] == '\"')
|
|
? !old_restore_string(sp, p)
|
|
: !restore_svalue(sp, &p, '\n')
|
|
)
|
|
{
|
|
/* Whoops, illegal format */
|
|
|
|
errorf("Illegal format when restoring a value.\n");
|
|
/* NOTREACHED */
|
|
return sp; /* flow control hint */
|
|
}
|
|
|
|
if (*p != '\0')
|
|
{
|
|
errorf("Illegal format when restoring a value: extraneous characters "
|
|
"at the end.\n");
|
|
/* NOTREACHED */
|
|
return sp; /* flow control hint */
|
|
}
|
|
|
|
/* Restore complete - now clean up and return the result */
|
|
|
|
free_svalue(inter_sp--);
|
|
sp = --inter_sp;
|
|
free_string_svalue(sp);
|
|
*sp = sp[1];
|
|
|
|
return sp;
|
|
} /* f_restore_value() */
|
|
|
|
/***************************************************************************/
|
|
|