/*---------------------------------------------------------------------------
* Gamedriver: Bytecode Interpreter
*
*---------------------------------------------------------------------------
* This module implements the bytecode interpreter for the compiled LPC
* programs. The machine is implemented as a stackmachine with separate
* stacks for values and control.
*
* See also 'exec.h' for the details of program storage, and 'svalue.h'
* for the details of value storage.
*
* --- Evaluator Stack ---
*
* The evaluation stack is an array of 'svalue_t's (see datatypes.h
* for information about this type) with EVALUATOR_SIZE<<1 elements.
* <inter_sp> resp. <sp> points to the last (that is topmost) valid
* entry in the stack, the framepointer <inter_fp> resp. <fp> points
* to the bottom of the frame of one function. Single values in the
* frame are then accessed by indexing the frame pointer.
* A typical stack layout looks like this:
*
* ^
* (inter_)sp -> | Top stack value
* | ...
* | Temporary stack values
* | Break addresses for switch instructions
* break_sp -> | (forming a sub-stack growing _down_).
* | ...
* | Local variable number 1
* | Local variable number 0
* | ...
* | Argument number 1
* (inter_)fp -> | Argument number 0
* |
* |
* VALUE_STACK -----
*
* The interpreter assumes that there are no destructed objects
* on the stack - to aid in this, the functions remove_object_from_stack()
* and (in array.c) check_for_destr() replace destructed objects by
* value 0.
*
*
* --- Control Stack ---
*
* During nested function calls, the return information to the higher
* functions are stored on the control stack.
*
* One particularity about the current implementation is that every
* inter-object call (ie. every 'extern_call') and every catch()
* constitutes in a recursive call to eval_instruction().
*
*
* --- Error Recovery Stack --- (implemented in backend.c)
*
* Error recovery in general is implemented using setjmp()/longjmp().
* The error recovery stack holds the (possibly nested) longjmp() contexts
* together with an indication where the jump will lead. Currently these
* context types are defined:
* ERROR_RECOVERY_NONE: No error recovery available
* (used by the top entry in the stack)
* ERROR_RECOVERY_BACKEND: Errors fall back to the backend,
* e.g. process_objects(), call_heart_beat()
* and others.
* ERROR_RECOVERY_APPLY: Errors fall back into the secure_apply()
* function used for sensitive applies.
* ERROR_RECOVERY_CATCH: Errors are caught by the catch() construct.
* ERROR_RECOVERY_CATCH_NOLOG: Errors are caught by the catch_nolog()
* construct.
*
* The _CATCH contexts differs from the others in that it allows the
* continuing execution after the error. In order to achieve this, the
* stack entry holds the necessary additional information to re-init
* the interpreter.
* TODO: Elaborate on the details.
*
*
* --- Bytecode ---
*
* The machine instructions are stored as unsigned characters and read
* sequentially. A single machine instruction consists of one or two
* bytes defining the instruction, optionally followed by more bytes
* with parameters (e.g. number of arguments on the stack, branch offsets,
* etc).
*
* Apart from the usual machine instructions (branches, stack
* manipulation, summarily called 'codes'), the machine implements every
* efun by its own instruction code. Since this leads to more than
* 256 instructions, the less often used instructions are grouped into
* 'xefuns'+'xcodes', 'tefuns'+'tcodes' and 'vefuns'+'tcodes', and the
* byte defining for the instruction within its group is prefixed by
* a byte defining the group: F_ESCAPE for xefuns, F_TEFUN for tefuns,
* and F_VEFUN for vefuns.
*
* Like normal machine instructions and efuns, xefuns are implemented as
* cases in a giant switch() instruction, while the implementations of
* tefuns/vefuns are called via function tables (tefuns take a fixed
* number of arguments, vefuns a variable number).
*
* Implementationwise, every machine instruction, efun or else, is assigned
* a unique number and a preprocessor symbol F_<name>. The relation between
* the assigned number and the bytecode is linear: normal instructions and
* efuns occupy the number range <offset>..<offset>+255, xefuns the range
* <offset>+256..<offset>+383, tefuns the range <offset>+384..<offset>+511,
* and vefuns the range from <offset>+512 . <offset> is currently 256, this
* allows the lexer to use the machine instruction numbers as an extension
* of the charset to encode references to efuns directly.
*
* All existing machine instructions are defined in the file func_spec,
* which during the compilation of the driver is evaluated by make_func.y
* to create the LPC compiler lang.y from prolang.y, the symbolic
* instruction names and numbers in instrs.h, and the definition of the
* tables efuns in efun_defs.c .
*
*
* --- Calling Convention ---
*
* All arguments for a function are evaluated and pushed to the value
* stack. The last argument is the last pushed. It is important that
* the called function gets exactly as many arguments as it wants; for
* LPC functions ('lfuns') this means that the actual function call will
* remove excessive arguments or push '0's for missing arguments. The
* number of arguments will be stored in the control stack, so that
* the return instruction not needs to know it explicitely.
*
* If the function called is an lfun (inherited or not), the number
* of arguments passed to the call is encoded in the bytecode stream,
* and the number of arguments expected can be determined from the
* functions 'struct function' entry.
*
* Efuns, operators and internal bytecode usually operate on a fixed
* number of arguments and the compiler makes sure that the right
* number is given. If an efun takes a variable number of arguments,
* the actual number is stored in the byte following the efun's opcode.
*
* The called function must ensure that exactly one value remains on the
* stack when returning. The caller is responsible of deallocating the
* returned value. This includes 'void' lfuns, which just push the
* value 0 as return value.
*
* When a LPC function returns, it will use the instruction F_RETURN, which
* will deallocate all arguments and local variables, and only let the
* top of stack entry remain. The number of arguments and local variables
* are stored in the control stack, so that the evaluator knows how much
* to deallocate.
*
* If flag 'extern_call' is set, then the evaluator should return from
* eval_instruction(). Otherwise, the evaluator will continue to execute
* the instruction at the returned address. In the current implementation,
* every inter-object call (call_other) receives its own (recursive)
* call to eval_instruction().
*
*---------------------------------------------------------------------------
* TODO: The virtual machine should be reconsidered, using the DGD and MudOS
* TODO:: machines for inspiration. This applies to implementation as well
* TODO:: as to the instruction set.
* TODO: Let all assign_ and transfer_ functions check for destruct objects.
* TODO:: The speed difference to assign_checked_ and transfer_checked_ is
* TODO:: not big enough to justify the extra set of functions.
*/
/*-------------------------------------------------------------------------*/
#include "driver.h"
#include "typedefs.h"
#include "my-alloca.h"
#include "my-rusage.h"
#include <fcntl.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <setjmp.h>
#include <ctype.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef MARK
#include <prof.h>
#endif
#ifdef HAVE_CRYPT_H
/* solaris defines crypt() here */
# include <crypt.h>
#endif
#if !defined(HAVE_CRYPT) && defined(HAVE__CRYPT)
# define crypt(pass, salt) _crypt(pass, salt)
#endif
#define USES_SVALUE_STRLEN
#include "interpret.h"
#include "actions.h"
#include "array.h"
#include "backend.h"
#include "call_out.h"
#include "closure.h"
#include "comm.h"
#include "ed.h"
#include "efuns.h"
#include "exec.h"
#include "filestat.h"
#include "gcollect.h"
#include "heartbeat.h"
#include "instrs.h"
#include "lex.h"
#include "main.h"
#include "mapping.h"
#include "object.h"
#include "otable.h"
#include "parse.h"
#include "prolang.h"
#include "ptrtable.h"
#include "random.h"
#include "sent.h"
#include "simulate.h"
#include "simul_efun.h"
#include "stdstrings.h"
#include "stralloc.h"
#include "smalloc.h" /* USES_SVALUE_STRLEN, malloc_increment_size() */
#include "sprintf.h"
#include "svalue.h"
#include "swap.h"
#include "switch.h"
#include "wiz_list.h"
#include "xalloc.h"
#include "../mudlib/sys/driver_hook.h"
#include "../mudlib/sys/debug_info.h"
#include "../mudlib/sys/trace.h"
/*-------------------------------------------------------------------------*/
/* Types */
/* --- struct catch_context: error_recovery subclass for catch() ---
*
* This extension of the struct error_recovery_info (see backend.h)
* stores the additional information needed to reinitialize the global
* variables when bailing out of a catch(). The type is always
* ERROR_RECOVERY_CATCH or ERROR_RECOVERY_CATCH_NOLOG.
*
* It is handled by the functions push_, pop_ and pull_error_context().
*/
struct catch_context
{
struct error_recovery_info recovery_info;
/* The subclassed recovery info.
*/
struct control_stack * save_csp;
object_t * save_command_giver;
svalue_t * save_sp;
/* The saved global values
*/
};
/* --- struct cache: one entry of the apply cache
*
* Every entry in the apply cache holds information about a function
* call, both for functions found and not found.
*/
struct cache
{
char *name;
/* The name of the cached function, shared for existing functions,
* allocated if the object does not have the function.
*/
program_t *progp;
/* The pointer to the program code of the function, or NULL if the
* object does not implement the function.
*/
int id;
/* The id_number of the program. */
funflag_t flags;
/* Copy of the _MOD_STATIC and _MOD_PROTECTED flags of the function.
*/
fun_hdr_p funstart;
/* Pointer to the function.
*/
int function_index_offset;
int variable_index_offset;
/* Function and variable index offset.
*/
};
/* --- struct mvf_info: structure used by m_values()/unmkmapping() ---
*
* This structure is passed by reference to the filter functions used
* by the two efuns and passes information from one filter call to the
* next.
*/
struct mvf_info
{
svalue_t * svp;
/* m_values_filter: Pointer to next result vector entry
* m_unmake_filter: Pointer to result array of svalues
*/
int num;
/* m_values_filter: Column to retrieve.
* m_unmake_filter: Next row to retrieve
*/
int width;
/* m_unmake_filter: width of the mapping
*/
};
/*-------------------------------------------------------------------------*/
/* Macros */
#define ERRORF(s) {inter_pc = pc; inter_sp = sp; error s ;}
#define ERROR(s) ERRORF((s))
/* ERRORF((...)) acts like error(...), except that first the local pc and sp
* are copied into the global variables.
* ERROR() is an easier to type form of ERRORF() when your error message
* is just one string. It will be redefined below for the tabled
* efuns.
*/
#define FATALF(s) {inter_pc = pc; inter_sp = sp; fatal s ;}
#define FATAL(s) FATALF((s))
/* Analogue.
*/
#if APPLY_CACHE_BITS < 1
# error APPLY_CACHE_BITS must be at least 1.
#else
# define CACHE_SIZE (1 << APPLY_CACHE_BITS)
#endif
/* Number of entries in the apply cache.
*/
/*-------------------------------------------------------------------------*/
/* Tracing */
int tracedepth;
/* Current depth of traced functions.
*/
int trace_level;
/* Current set of active trace options.
* This set can be different from interactive->trace_level if several
* nested trace() calls occur.
*/
static int traceing_recursion = -1;
/* Kind of mutex, used to turn off tracing while doing trace output.
* Necessary because output with add_message() might result result
* in further code to be executed.
*/
static Bool trace_exec_active = MY_FALSE;
/* TRUE whenever TRACE_EXEC is not just requested, but actually
* active. This distinction is necessary as tracing might be limited
* to one object only, and testing the object name for every instruction
* would be too expensive. Hence, the tracing condition is checked
* only on object changes and this variable is updated accordingly.
* See macros SET_TRACE_EXEC and TRACE_EXEC_P.
*/
#ifdef TRACE_CODE
/* The buffers for the traced code:
*/
static int previous_instruction[TOTAL_TRACE_LENGTH];
static ptrdiff_t stack_size[TOTAL_TRACE_LENGTH];
static ptrdiff_t abs_stack_size[TOTAL_TRACE_LENGTH];
static bytecode_p previous_pc[TOTAL_TRACE_LENGTH];
static program_t * previous_programs[TOTAL_TRACE_LENGTH];
static object_t * previous_objects[TOTAL_TRACE_LENGTH];
/* These arrays, organized as ring buffers, hold the vitals of the
* last TOTAL_TRACE_LENGTH instructions executed. Yet unused entries
* are 0 resp. NULL.
*/
static int last = TOTAL_TRACE_LENGTH - 1;
/* Index to the last used entry in the ringbuffers above.
*/
#endif
/* --- Macros --- */
#define TRACE_IS_INTERACTIVE() (command_giver && O_IS_INTERACTIVE(command_giver))
/* Return TRUE if the current command_giver is interactive.
* TODO: Instead of disabling all traceoutput whenever the command_giver
* TODO:: turns non-interactive, output should be redirected (with a
* TODO:: special mark) to the current_interactive.
*/
#define TRACETST(b) (TRACE_IS_INTERACTIVE() && (O_GET_INTERACTIVE(command_giver)->trace_level & (b)))
/* Return TRUE if the any of the tracing options <b> are requested
* by the interactive user.
*/
#define TRACEP(b) (trace_level & (b) && trace_test(b))
/* Return TRUE if tracing options <b> are both active in trace_level
* and requested by the interactive user.
*/
#define TRACEHB \
( current_heart_beat == NULL || TRACETST(TRACE_HEART_BEAT))
/* Return TRUE if either the current execution is not caused
* by a heart beat call, or if heartbeat tracing is allowed.
*/
#define SET_TRACE_EXEC MACRO( \
if (trace_level & TRACE_EXEC) \
trace_exec_active = MY_TRUE;\
)
/* If TRACE_EXEC is requested, (re)activate it.
* See trace_exec_active for the background.
*/
#define TRACE_EXEC_P ( TRACEP(TRACE_EXEC) \
|| (trace_exec_active = MY_FALSE, MY_FALSE))
/* If TRACE_EXEC is still requested, return TRUE, otherwise deactivate
* it and return FALSE.
* See trace_exec_active for the background.
*/
/*-------------------------------------------------------------------------*/
/* Variables */
/* The virtual machine's registers.
*
* While the interpreter is in eval_instruction(), some of the values are
* kept in local variables for greater speed, with the globals being updated
* only when necessary.
* The affected variables are: inter_pc, inter_sp, TODO: which else?
*/
svalue_t *inter_sp;
/* Points to last valid value on the value stack.
*/
#ifndef MALLOC_LPC_TRACE
static
#endif
bytecode_p inter_pc;
/* Next bytecode to interpret.
*/
static svalue_t *inter_fp;
/* Framepointer: pointer to first argument.
*/
static bytecode_p *break_sp;
/* Points to address to branch to at next F_BREAK from within a switch().
* This is actually a stack of addresses with break_sp pointing to the
* bottom with the most recent entry. This break stack is stored on
* the evaluator stack, one address per svalue_t (which incidentally
* stored in the u.string field), between the functions temporary values
* and its local variables.
* TODO: Since this stores an opcode* in a svalue, it should get its
* TODO:: own union type, and break_sp should be an svalue_t *.
*/
program_t *current_prog;
/* The current program. This is usually current_object->prog, but can
* differ when executing an inherited program.
*/
static svalue_t current_lambda;
/* If the VM is executing a lambda, this variable holds a counted
* reference to it to make sure that it isn't destructed while it is
* still executing.
*/
static char **current_strings;
/* Pointer to the string literal block of the current program for
* faster access.
*/
int function_index_offset;
/* Index of current program's function block within the functions of
* the current objects program (needed for inheritance).
*/
static int variable_index_offset;
/* Index of current program's variable block within the variables
* of the current object (needed for inheritance).
*/
svalue_t *current_variables;
/* Pointer to begin of the current variable block.
* This is current_object->variables + variable_index_offset for
* faster access.
*/
/* Other Variables */
int32 eval_cost;
/* The amount of eval cost used in the current execution thread.
*/
int32 assigned_eval_cost;
/* Auxiliary variable used to account eval costs to single objects and
* their user's wizlist entry.
* Whenver the execution thread enters a different object,
* assigned_eval_cost is set to the current value of eval_cost. When the
* thread leaves the object again, the difference between the actual
* eval_cost value and the older assigned_eval_cost is accounted to
* the current object.
* The implementation combines both actions in one function
* assign_eval_cost().
*/
svalue_t apply_return_value = { T_NUMBER };
/* This variable holds the result from a call to apply(), transferred
* properly from the interpreter stack where the called function
* left it.
* push_ and pop_apply_value() handle this particular transfer.
* Note: The process_string() helper function process_value() takes
* direct advantage of this variable.
*/
#define SIZEOF_STACK (EVALUATOR_STACK_SIZE<<1)
static svalue_t value_stack_array[SIZEOF_STACK+1];
#define VALUE_STACK (value_stack_array+1)
/* The evaluator stack, sized with (hopefully) enough fudge to handle
* function arguments and overflows.
* The stack grows upwards, and <inter_sp> points to last valid entry.
*
* The first entry of value_stack_array[] is not used and serves as
* dummy so that underflows can be detected in a portable way
* (Standard C disallows indexing before an array). Instead, VALUE_STACK
* is the real bottom of the stack.
*/
svalue_t catch_value = { T_INVALID } ;
/* Holds the value throw()n from within a catch() while the throw
* is executed.
*/
static struct control_stack control_stack_array[MAX_TRACE+2];
#define CONTROL_STACK (control_stack_array+2)
struct control_stack *csp;
/* The control stack holds copies of the machine registers for previous
* function call levels, with <csp> pointing to the last valid
* entry, describing the last context.
* This also means that CONTROL_STACK[0] (== control_stack_array[2]) will
* have almost no interesting values as it will terminate execution.
* Especially CONTROL_STACK[0].prog is NULL to mark the bottom.
*
* The first two entries of control_stack_array[] are not used and
* serve as dummies so that underflows can be detected in a portable
* way (Standard C disallows indexing before an array).
*/
#ifdef APPLY_CACHE_STAT
p_int apply_cache_hit = 0;
p_int apply_cache_miss = 0;
/* Number of hits and misses in the apply cache.
*/
#endif
static struct cache cache[CACHE_SIZE];
/* The apply cache.
*/
static struct
{
svalue_t v;
/* The target value:
* .v.type: T_CHAR_LVALUE
* .v.u.string: the char to modify
* or
* .v.type: T_{POINTER,STRING}_RANGE_LVALUE
* .v.u.{vec,string}: the target value holding the range
* .index1, .index2, .size: see below
*/
mp_int index1; /* First index of the range */
mp_int index2; /* Last index of the range plus 1 */
mp_int size; /* Current(?) size of the value */
}
special_lvalue;
/* When assigning to vector and string ranges or elements, the
* target information is stored in this structure.
* TODO: Having one global structure counts as 'ugly'.
* Used knowingly by: (r)index_lvalue(), transfer_pointer_range(),
* assign_string_range().
* Used unknowingly by: assign_svalue(), transfer_svalue(),
* add_number_to_lvalue(), F_VOID_ASSIGN.
*/
static svalue_t indexing_quickfix = { T_NUMBER };
/* When indexing arrays and mappings with just one ref, especially
* for the purpose of getting a lvalue, the indexed item is copied
* into this variable and indexed from here.
* Used by operators: push_(r)indexed_lvalue, push_indexed_value,
* push_indexed_map_lvalue.
* TODO: Rename this variable, or better: devise a nice solution.
* TODO:: Use the protected_lvalues instead?
* TODO:: To quote Marion:
* TODO:: marion says: but this is crude too
* TODO:: marion blushes.
* TODO: Is it made sure that this var will be vacated before the
* TODO:: next use? Otoh, if not it's no problem as the value is
* TODO:: by definition volatile.
*/
svalue_t last_indexing_protector = { T_NUMBER };
/* When indexing a protected non-string-lvalue, this variable receives
* the protecting svalue for the duration of the operation (actually
* until the next indexing operation (TODO: not nice)).
* This is necessary because the indexing operation necessarily destroys
* the protector structure, even though the protection is still needed.
* Used by: protected_index_lvalue().
*/
#ifdef OPCPROF
#define MAXOPC 0x280
/* Number of different instructions to trace.
*/
static int opcount[MAXOPC];
/* Counter array for instruction profiling: each entry counts the
* usages of one instruction. The full instruction code (not the
* opcode) is used as index.
*/
#endif
#ifdef DEBUG
static program_t *check_a_lot_ref_counts_search_prog;
/* Program you developer are especially interested in.
*/
static struct pointer_table *ptable;
/* check_a_lot_of_ref_counts: the table of structures already
* visited.
*/
#endif
/*-------------------------------------------------------------------------*/
/* Forward declarations */
static Bool apply_low(char *, object_t *, int, Bool);
static void call_simul_efun(int code, object_t *ob, int num_arg);
#ifdef DEBUG
static void check_extra_ref_in_vector(svalue_t *svp, size_t num);
#endif
/*-------------------------------------------------------------------------*/
/* Assign the evaluation cost elapsed since the last call to the
* current_object and it's user's wizlist entry. Then set assigned_eval_cost
* to the current eval_cost so that later calls can do the same.
*
* This function must be called at least whenever the execution leaves
* one object for another one.
*/
#define ASSIGN_EVAL_COST \
if (current_object->user)\
current_object->user->cost += eval_cost - assigned_eval_cost;\
current_object->ticks += eval_cost - assigned_eval_cost;\
{\
unsigned long carry = current_object->ticks / 1000000000;\
if (carry)\
{\
current_object->gigaticks += carry;\
current_object->ticks %= 1000000000;\
}\
}\
assigned_eval_cost = eval_cost;
void assign_eval_cost(void) { ASSIGN_EVAL_COST }
/*-------------------------------------------------------------------------*/
void
init_interpret (void)
/* Initialize the interpreter data structures, especially the apply cache.
*/
{
struct cache invalid_entry;
int i;
/* The cache is inited to hold entries for 'functions' in a non-existing
* program (id 0). The first real apply calls will thus see a (virtual)
* collision with 'older' cache entries.
*/
invalid_entry.id = 0;
invalid_entry.progp = (program_t *)1;
for (i = 0; i < CACHE_SIZE; i++)
cache[i] = invalid_entry;
}
/*=========================================================================*/
/* S V A L U E S */
/*-------------------------------------------------------------------------*/
/* The following functions handle svalues, ie. the data referenced
* by the svalue_ts. 'Freeing' in this context therefore never means
* a svalue_t, only the data referenced by it.
*
* destructed_object_ref(v): test if <v> references a destructed object.
* get_object_ref(v): return the object referenced by <v>, if any.
* free_string_svalue(v): free string svalue <v>.
* free_object_svalue(v): free object svalue <v>.
* zero_object_svalue(v): replace the object in svalue <v> by number 0.
* free_svalue(v): free the svalue <v>.
* assign_svalue_no_free(to,from): put a copy of <from> into <to>; <to>
* is considered empty.
* copy_svalue_no_free(to,from): put a shallow value copy of <from> into <to>;
* <to> is considered empty.
* assign_checked_svalue_no_free(to,from,sp,pc): put a copy of <from> into <to>;
* <to> is considered empty, <from> may be destructed
* object.
* assign_local_svalue_no_free(to,from,sp,pc): put a copy of local var <from>
* into <to>; <to> is considered empty, <from> may
* be destructed object.
* static assign_lrvalue_no_free(to,from): like assign_svalue_no_free(),
* but lvalues and strings are handled differently.
* assign_svalue(dest,v): assign <v> to <dest>, freeing <dest> first.
* Also handles assigns to lvalues.
* transfer_svalue_no_free(dest,v): move <v> into <dest>; <dest> is
* considered empty.
* transfer_svalue(dest,v): move <v> into <dest>; freeing <dest> first.
* Also handles transfers to lvalues.
* static add_number_to_lvalue(dest,i,pre,post): add <i> to lvalue <dest>.
*
* In addition there are some helper functions.
*
* TODO: All these functions and vars should go into a separate module.
*/
/*-------------------------------------------------------------------------*/
/* --- Protector structures ---
*
* Whenever an assignment is made to a single value, or to a range in
* a string, vector or mapping, the interpreter generates protector
* structures in place of the usual LVALUE-svalues, which hold:
* - a svalue structure referring to the svalue into which the assignment
* is done (this structure is always first so that the protector
* structures can be used instead of normal svalues),
* - the necessary information to store the assigned svalue into its
* place in the target holding the value,
* - a protective reference to the holding value.
*
* All this just to keep LPC statements like 'a = ({ 1 }); a[0] = (a = 0);'
* from crashing.
*
* TODO: A simpler way would be to compute the lhs of an assignment
* TODO:: after evaluating the rhs - not vice versa as it is now.
* TODO:: However, passing lvalues and ranges as ref-parameters to functions
* TODO:: would still be a potential problem.
*/
/* --- struct protected_lvalue: protect a single value
*/
struct protected_lvalue
{
svalue_t v;
/* .v.type: T_PROTECTED_LVALUE
* .v.u.lvalue: the protected value
*/
svalue_t protector; /* protects .v.u.lvalue (or its holder) */
};
/* --- struct protected_char_lvalue: protect a char in a string
*/
struct protected_char_lvalue
{
svalue_t v;
/* .v.type: T_PROTECTED_CHAR_LVALUE
* .v.u.string: points to the char to access
*/
svalue_t protector; /* protects .lvalue */
svalue_t *lvalue; /* the string containing the char */
char *start;
/* must be == lvalue->u.string, otherwise the string has been
* changed and this lvalue is invalid
*/
};
/* --- struct protected_range_lvalue: protect a range in a string or vector
*/
struct protected_range_lvalue {
svalue_t v;
/* .v.type: T_PROTECTED_{POINTER,STRING}_RANGE_LVALUE
* .v.u.{string,vec}: the target value holding the range
*/
svalue_t protector; /* protects .lvalue */
svalue_t *lvalue; /* the value holding the range */
int index1, index2; /* first and last index of the range */
int size; /* original size of .lvalue */
/* .v.u.{vec,string} must be == .lvalue->u.{vec,string}, otherwise
* the target has been changed and the range information (index, size)
* is no longer valid.
*/
};
/*-------------------------------------------------------------------------*/
/* Forward declarations */
static void transfer_pointer_range(svalue_t *source);
static void transfer_protected_pointer_range(
struct protected_range_lvalue *dest, svalue_t *source);
static void assign_string_range(svalue_t *source, Bool do_free);
static void assign_protected_string_range(
struct protected_range_lvalue *dest,svalue_t *source, Bool do_free);
/*-------------------------------------------------------------------------*/
static INLINE Bool
_destructed_object_ref (svalue_t *svp)
/* Return TRUE if the svalue in <svp> references a destructed object.
*/
{
lambda_t *l;
int type;
if (svp->type != T_OBJECT && svp->type != T_CLOSURE)
return MY_FALSE;
if (svp->type == T_OBJECT || !CLOSURE_MALLOCED(type = svp->x.closure_type))
return (svp->u.ob->flags & O_DESTRUCTED) ? MY_TRUE : MY_FALSE;
/* Lambda closure */
l = svp->u.lambda;
if (CLOSURE_HAS_CODE(type) && type == CLOSURE_UNBOUND_LAMBDA)
return MY_FALSE;
if (type == CLOSURE_ALIEN_LFUN
&& (l->function.alien.ob->flags & O_DESTRUCTED))
return MY_TRUE;
return (l->ob->flags & O_DESTRUCTED) ? MY_TRUE : MY_FALSE;
} /* _destructed_object_ref() */
Bool destructed_object_ref (svalue_t *v) { return _destructed_object_ref(v); }
#define destructed_object_ref(v) _destructed_object_ref(v)
/*-------------------------------------------------------------------------*/
static INLINE object_t *
_get_object_ref (svalue_t *svp)
/* If <svp> references an object (destructed or alive), return the object.
* Return NULL otherwise.
*/
{
lambda_t *l;
int type;
if (svp->type != T_OBJECT && svp->type != T_CLOSURE)
return NULL;
if (svp->type == T_OBJECT || !CLOSURE_MALLOCED(type = svp->x.closure_type))
return svp->u.ob;
/* Lambda closure */
l = svp->u.lambda;
if (CLOSURE_HAS_CODE(type) && type == CLOSURE_UNBOUND_LAMBDA)
return NULL;
if (type == CLOSURE_ALIEN_LFUN)
return l->function.alien.ob;
return l->ob;
} /* _get_object_ref() */
object_t * get_object_ref (svalue_t *v) { return _get_object_ref(v); }
#define get_object_ref(v) _get_object_ref(v)
/*-------------------------------------------------------------------------*/
static INLINE void
_free_string_svalue (svalue_t *v)
/* Free the string svalue <v>; <v> must be of type T_STRING.
*/
{
#ifdef DEBUG
if (v->type != T_STRING)
{
fatal("free_string_svalue(): Expected string, "
"received svalue type (%d:%hd)\n"
, v->type, v->x.generic);
/* NOTREACHED */
return;
}
#endif
switch(v->x.string_type)
{
case STRING_MALLOC:
xfree(v->u.string);
break;
case STRING_SHARED:
free_string(v->u.string);
break;
}
}
void free_string_svalue (svalue_t *v) { _free_string_svalue(v); }
#define free_string_svalue(v) _free_string_svalue(v)
/*-------------------------------------------------------------------------*/
void
free_object_svalue (svalue_t *v)
/* Free the object svalue <v>; <v> must be of type T_OBJECT.
*/
{
object_t *ob = v->u.ob;
#ifdef DEBUG
if (v->type != T_OBJECT)
{
fatal("free_object_svalue(): Expected object, "
"received svalue type (%d:%hd)\n"
, v->type, v->x.generic);
/* NOTREACHED */
return;
}
#endif
free_object(ob, "free_object_svalue");
}
/*-------------------------------------------------------------------------*/
void
zero_object_svalue (svalue_t *v)
/* Change <v> from an object svalue to the svalue-number 0.
*/
{
object_t *ob = v->u.ob;
free_object(ob, "zero_object_svalue");
put_number(v, 0);
}
/*-------------------------------------------------------------------------*/
static void
free_protector_svalue (svalue_t *v)
/* Free the svalue <v> which contains a protective reference to a vector
* or to a mapping.
*/
{
switch (v->type)
{
case T_POINTER:
free_array(v->u.vec);
break;
case T_MAPPING:
free_mapping(v->u.map);
break;
case T_PROTECTOR_MAPPING:
free_protector_mapping(v->u.map);
break;
}
}
/*-------------------------------------------------------------------------*/
void
free_svalue (svalue_t *v)
/* Free the svalue <v>, which may be of any type.
* Afterwards, the content of <v> is undefined.
*/
{
ph_int type = v->type;
v->type = T_INVALID;
/* If freeing the value throws an error, it is most likely that
* we run out of stack. To avoid the error handling running
* out of stack on the same value, we mask it before we free
* it - at the risk of leaking memory.
*/
assert_stack_gap();
switch (type)
{
case T_STRING:
switch(v->x.string_type)
{
case STRING_MALLOC:
xfree(v->u.string);
break;
case STRING_SHARED:
free_string(v->u.string);
break;
}
break;
case T_OBJECT:
{
object_t *ob = v->u.ob;
free_object(ob, "free_svalue");
break;
}
case T_QUOTED_ARRAY:
case T_POINTER:
free_array(v->u.vec);
break;
case T_MAPPING:
free_mapping(v->u.map);
break;
case T_SYMBOL:
free_string(v->u.string);
break;
case T_CLOSURE:
free_closure(v);
break;
case T_CALLBACK:
free_callback(v->u.cb);
break;
case T_LVALUE:
switch (v->u.lvalue->type)
{
case T_PROTECTED_LVALUE:
{
struct protected_lvalue *p;
p = v->u.protected_lvalue;
free_protector_svalue(&p->protector);
xfree(p);
break;
}
case T_PROTECTED_CHAR_LVALUE:
{
struct protected_char_lvalue *p;
p = v->u.protected_char_lvalue;
if (p->lvalue->type == T_STRING
&& p->lvalue->u.string == p->start)
{
p->lvalue->x.string_type = STRING_MALLOC;
}
else
{
xfree(p->start);
}
free_protector_svalue(&p->protector);
xfree(p);
break;
}
case T_PROTECTED_STRING_RANGE_LVALUE:
{
struct protected_range_lvalue *p;
p = v->u.protected_range_lvalue;
if (p->lvalue->type == T_STRING
&& p->lvalue->u.string == p->v.u.string)
{
p->lvalue->x.string_type = STRING_MALLOC;
}
else
{
xfree(p->v.u.string);
}
free_protector_svalue(&p->protector);
xfree(p);
break;
}
case T_PROTECTED_POINTER_RANGE_LVALUE:
{
struct protected_range_lvalue *p;
p = v->u.protected_range_lvalue;
free_array(p->v.u.vec);
free_protector_svalue(&p->protector);
xfree(p);
break;
}
case T_ERROR_HANDLER:
{
svalue_t *p;
p = v->u.lvalue;
(*p->u.error_handler)(p);
break;
}
} /* switch (v->u.lvalue->type) */
break; /* case T_LVALUE */
}
} /* free_svalue() */
/*-------------------------------------------------------------------------*/
static INLINE void
check_for_ref_loop (svalue_t * dest)
/* <dest> has just been assigned to - check if this created a reference loop.
* If yes, free <dest> and throw an error.
*/
{
if (dest->type == T_LVALUE || dest->type == T_PROTECTED_LVALUE)
{
/* rover1 will scan the lvalue chain in two-steps, rover2 will
* scan it step by step. If there is a loop, the two will eventually
* meet again.
*/
svalue_t * rover1, * rover2;
rover1 = rover2 = dest;
do {
if (rover1->type == T_LVALUE || rover1->type == T_PROTECTED_LVALUE)
rover1 = rover1->u.lvalue;
else
break;
if (rover1->type == T_LVALUE || rover1->type == T_PROTECTED_LVALUE)
rover1 = rover1->u.lvalue;
else
break;
if (rover2->type == T_LVALUE || rover2->type == T_PROTECTED_LVALUE)
rover2 = rover2->u.lvalue;
else
break;
if (rover1 == rover2)
{
free_svalue(dest);
error("Assignment would create reference loop.\n");
}
} while (rover1
&& (rover1->type == T_LVALUE || rover1->type == T_PROTECTED_LVALUE)
&& rover2
&& (rover2->type == T_LVALUE || rover2->type == T_PROTECTED_LVALUE)
);
}
} /* check_for_ref_loop() */
/*-------------------------------------------------------------------------*/
static INLINE void
_assign_svalue_no_free (svalue_t *to, svalue_t *from)
/* Put a duplicate of svalue <from> into svalue <to>, meaning that the original
* value is either copied when appropriate, or its refcount is increased.
* <to> is considered empty at the time of call.
*
* If <from> is a destructed object, <to> is set to the number 0 but
* <from> is left unchanged.
*/
{
#ifdef DEBUG
if (from == 0)
fatal("Null pointer to assign_svalue().\n");
#endif
/* Copy all the data */
*to = *from;
/* Now create duplicates resp. increment refcounts where necessary */
switch(from->type)
{
case T_STRING:
switch(from->x.string_type)
{
case STRING_MALLOC: /* No idea to make the string shared */
{
char *p, *str;
str = from->u.string;
p = xalloc(malloced_strlen(str));
if (!p)
{
put_number(to, 0);
error("Out of memory\n");
}
strcpy(p, str);
to->u.string = p;
break;
}
case STRING_VOLATILE: /* Good idea to make it shared */
put_string(to, make_shared_string(from->u.string));
if (!to->u.string)
{
put_number(to, 0);
error("Out of memory\n");
}
break;
case STRING_SHARED: /* It already is shared */
ref_string(from->u.string);
break;
#ifdef DEBUG
default:
fatal("assign_svalue_no_free(): bad string type %d\n", from->x.string_type);
#endif
}
break;
case T_OBJECT:
{
object_t *ob = to->u.ob;
if ( !(ob->flags & O_DESTRUCTED) )
(void)ref_object(ob, "ass to var");
else
put_number(to, 0);
break;
}
break;
case T_QUOTED_ARRAY:
case T_POINTER:
(void)ref_array(to->u.vec);
break;
case T_SYMBOL:
ref_string(to->u.string);
break;
case T_CLOSURE:
if (!destructed_object_ref(to))
addref_closure(to, "ass to var");
else
put_number(to, 0);
break;
case T_MAPPING:
(void)ref_mapping(to->u.map);
break;
}
/* Protection against endless reference loops */
if (to->type == T_LVALUE || to->type == T_PROTECTED_LVALUE)
{
check_for_ref_loop(to);
}
} /* _assign_svalue_no_free() */
void assign_svalue_no_free (svalue_t *to, svalue_t *from)
{ _assign_svalue_no_free(to,from); }
#define assign_svalue_no_free(to,from) _assign_svalue_no_free(to,from)
/*-------------------------------------------------------------------------*/
static INLINE void
copy_svalue_no_free (svalue_t *to, svalue_t *from)
/* Put a duplicate of svalue <from> into svalue <to>, meaning that the original
* value is either copied when appropriate, or its refcount is increased.
* In particular, if <from> is a mapping (which must not contain destructed
* objects!) or array, a shallow copy is created.
* <to> is considered empty at the time of call.
*
* If <from> is a destructed object, <to> is set to the number 0 but
* <from> is left unchanged.
*/
{
assign_svalue_no_free(to, from);
/* For arrays and mappings, create a shallow copy */
if (from->type == T_MAPPING)
{
mapping_t *old, *new;
old = to->u.map;
if (old->ref != 1)
{
DYN_MAPPING_COST(MAP_SIZE(old));
new = copy_mapping(old);
if (!new)
error("Out of memory: mapping[%lu] for copy.\n"
, MAP_SIZE(old));
free_mapping(old);
to->u.map = new;
}
}
else if (from->type == T_POINTER
|| from->type == T_QUOTED_ARRAY)
{
vector_t *old, *new;
size_t size, i;
old = to->u.vec;
size = VEC_SIZE(old);
if (old->ref != 1 && old != &null_vector)
{
DYN_ARRAY_COST(size);
new = allocate_uninit_array((int)size);
if (!new)
error("Out of memory: array[%lu] for copy.\n"
, (unsigned long) size);
for (i = 0; i < size; i++)
assign_svalue_no_free( &new->item[i]
, &old->item[i]);
free_array(old);
to->u.vec = new;
}
}
} /* copy_svalue_no_free() */
/*-------------------------------------------------------------------------*/
static INLINE void
assign_checked_svalue_no_free (svalue_t *to, svalue_t *from
, svalue_t *sp, bytecode_p pc
)
/* Put a duplicate of svalue <from> into svalue <to>, meaning that the original
* value is either copied when appropriate, or its refcount is increased.
* <to> is considered empty at the time of call.
* <from> may point to a variable or vector element, so it might contain
* a destructed object. In that case, <from> and <to> are set to
* svalue-number 0.
*
* <sp> and <pc> are the current stackpointer and program counter and are
* needed to update <inter_xx> in case of errors.
*/
{
switch (from->type)
{
case T_STRING:
switch(from->x.string_type)
{
case STRING_MALLOC: /* No idea to make the string shared */
case STRING_VOLATILE:
{
char *p;
char *str;
if (from->x.string_type == STRING_MALLOC)
p = xalloc(malloced_strlen(str = from->u.string));
else
p = xalloc(strlen(str = from->u.string)+1);
if (!p) {
put_number(to, 0);
inter_sp = sp;
inter_pc = pc;
error("Out of memory\n");
}
(void)strcpy(p, str);
put_malloced_string(to, p);
return;
}
case STRING_SHARED: /* It already is shared */
put_ref_string(to, from->u.string);
return;
}
case STRING_VOLATILE:
{
char *str;
str = make_shared_string(from->u.string);
if ( !str ) {
put_ref_string(to, STR_DEFAULT);
inter_sp = sp;
inter_pc = pc;
error("Out of memory\n");
}
put_string(to, str);
break;
}
#ifdef DEBUG
fatal("assign_checked_svalue_no_free(): bad string type %d\n", from->x.string_type);
#endif
break;
case T_OBJECT:
{
object_t *ob = from->u.ob;
if ( !(ob->flags & O_DESTRUCTED) ) {
ref_object(ob, "ass to var");
break;
}
zero_object_svalue(from);
break;
}
case T_QUOTED_ARRAY:
case T_POINTER:
(void)ref_array(from->u.vec);
break;
case T_SYMBOL:
ref_string(from->u.string);
break;
case T_CLOSURE:
if (!destructed_object_ref(from))
addref_closure(from, "ass to var");
else
assign_svalue(from, &const0);
break;
case T_MAPPING:
(void)ref_mapping(from->u.map);
break;
}
*to = *from;
/* Protection against endless reference loops */
if (to->type == T_LVALUE || to->type == T_PROTECTED_LVALUE)
{
check_for_ref_loop(to);
}
} /* assign_checked_svalue_no_free() */
/*-------------------------------------------------------------------------*/
static INLINE void
assign_local_svalue_no_free ( svalue_t *to, svalue_t *from
, svalue_t *sp, bytecode_p pc
)
/* Put a duplicate of svalue <from> into svalue <to>, meaning that the original
* value is either copied when appropriate, or its refcount is increased.
* <to> is considered empty at the time of call.
*
* <from> is meant to point to a local variable, which might be an arg
* to the current lfun and may thus contain a VOLATILE string. If that is
* the case, the string is made shared before assignment.
* If <from> is a lvalue, the chain is unraveled and the final non-lvalue
* is assigned. If that value is a destructed object, 0 is assigned.
*
* <sp> and <pc> are the current stackpointer and program counter and are
* needed to update <inter_xx> in case of errors.
*/
{
assign_from_lvalue:
switch (from->type) {
case T_STRING:
switch(from->x.string_type) {
case STRING_MALLOC: /* No idea to make the string shared */
{
char *p;
char *str;
p = xalloc(malloced_strlen(str = from->u.string));
if (!p) {
put_number(to, 0);
inter_sp = sp;
inter_pc = pc;
error("Out of memory\n");
}
(void)strcpy(p, str);
put_malloced_string(sp, p);
return;
}
case STRING_SHARED: /* It already is shared */
put_ref_string(to, from->u.string);
break;
case STRING_VOLATILE:
{
char *str;
str = make_shared_string(from->u.string);
if ( !str ) {
put_ref_string(to, STR_DEFAULT);
inter_sp = sp;
inter_pc = pc;
error("Out of memory\n");
}
put_string(to, str);
break;
}
#ifdef DEBUG
default:
fatal("assign_local_svalue_no_free(): bad string type %d\n", from->x.string_type);
#endif
}
return;
case T_OBJECT:
(void)ref_object(from->u.ob, "assign_local_lvalue_no_free");
break;
case T_QUOTED_ARRAY:
case T_POINTER:
(void)ref_array(from->u.vec);
break;
case T_SYMBOL:
ref_string(from->u.string);
break;
case T_CLOSURE:
addref_closure(from, "ass to var");
break;
case T_MAPPING:
(void)ref_mapping(from->u.map);
break;
case T_LVALUE:
case T_PROTECTED_LVALUE:
from = from->u.lvalue;
if (destructed_object_ref(from)) {
assign_svalue(from, &const0);
break;
}
goto assign_from_lvalue;
case T_PROTECTED_CHAR_LVALUE:
put_number(to, *from->u.string);
return;
}
*to = *from;
/* Protection against endless reference loops */
if (to->type == T_LVALUE || to->type == T_PROTECTED_LVALUE)
{
inter_sp = sp;
inter_pc = pc;
check_for_ref_loop(to);
}
} /* assign_local_svalue_no_free() */
/*-------------------------------------------------------------------------*/
static INLINE
void assign_lrvalue_no_free (svalue_t *to, svalue_t *from)
/* Put a duplicate of svalue <from> into svalue <to>, meaning that the original
* value is either copied when appropriate, or its refcount is increased.
* <to> is considered empty at the time of call.
*
* This function differs from assign_svalue_no_free() in the handling of
* two types:
* - if <from> is an unshared string, the string is made shared and
* both <to> and <from> are changed to use the shared string.
* - if <from> is a lvalue, <to>.u.lvalue is set to point to <from>.
* This is necessary when pushing references onto the stack - if
* assign_svalue_no_free() were used, the first free_svalue() would undo
* the whole lvalue indirection, even though there were still other lvalue
* entries in the stack for the same svalue.
* TODO: An alternative would be use a special struct lvalue {} with a
* refcount.
*/
{
#ifdef DEBUG
if (from == 0)
fatal("Null pointer to assign_lrvalue_no_free().\n");
#endif
/* Copy the data */
*to = *from;
/* Now adapt the refcounts or similar */
switch(from->type)
{
case T_STRING:
if (to->x.string_type != STRING_SHARED)
{
to->x.string_type = STRING_SHARED;
to->u.string = make_shared_string(from->u.string);
if (from->x.string_type == STRING_MALLOC)
{
xfree(from->u.string);
}
*from = *to;
}
ref_string(from->u.string);
break;
case T_OBJECT:
(void)ref_object(to->u.ob, "ass to var");
break;
case T_QUOTED_ARRAY:
case T_POINTER:
(void)ref_array(to->u.vec);
break;
case T_SYMBOL:
ref_string(to->u.string);
break;
case T_CLOSURE:
addref_closure(to, "ass to var");
break;
case T_MAPPING:
(void)ref_mapping(to->u.map);
break;
case T_LVALUE:
to->u.lvalue = from;
break;
}
/* Protection against endless reference loops */
if (to->type == T_LVALUE || to->type == T_PROTECTED_LVALUE)
{
check_for_ref_loop(to);
}
} /* assign_lrvalue_no_free() */
/*-------------------------------------------------------------------------*/
void
assign_svalue (svalue_t *dest, svalue_t *v)
/* Put a duplicate of svalue <v> into svalue <dest>, meaning that the
* original value is either copied when appropriate, or its refcount is
* increased.
*
* <dest> is considered a valid svalue and therefore freed before the
* assignment. Structured values will necessiate doing the assignment before
* the actual deallocation, otherwise recursive structures could cause crashs.
* One nasty example is
* a = ( ({((a=({0})),(a[0]=a)),(a=0)})[0] = query_verb() );
* which used to corrupt the shared string table, namely the entry for
* the verb in variable a if its length uses a memory block of
* the same length as an array of size 2.
*
* If <dest> is a lvalue, <v> will be assigned to the svalue referenced
* to by <dest>.
*/
{
/* Free the <dest> svalue.
* If <dest> is a lvalue, the loop will traverse the lvalue chain until
* the actual svalue is found.
* If a T_xxx_LVALUE is found, the assignment will be done here
* immediately.
*/
for (;;) {
switch(dest->type)
{
case T_LVALUE:
case T_PROTECTED_LVALUE:
dest = dest->u.lvalue;
continue;
case T_STRING:
switch(dest->x.string_type)
{
case STRING_MALLOC:
xfree(dest->u.string);
break;
case STRING_SHARED:
free_string(dest->u.string);
break;
}
break;
case T_OBJECT:
{
object_t *ob = dest->u.ob;
free_object(ob, "assign_svalue");
break;
}
case T_QUOTED_ARRAY:
case T_POINTER:
{
vector_t *vec = dest->u.vec;
assign_svalue_no_free(dest, v);
/* TODO: leaks vec if out of memory */
free_array(vec);
return;
}
case T_MAPPING:
{
mapping_t *map = dest->u.map;
assign_svalue_no_free(dest, v); /* leaks map if out of memory */
free_mapping(map);
return;
}
case T_SYMBOL:
free_string(dest->u.string);
break;
case T_CLOSURE:
free_closure(dest);
break;
/* If the final svalue in dest is one of these lvalues,
* the assignment is done right here and now.
* Note that 'dest' in some cases points to a protector structure.
*/
case T_CHAR_LVALUE:
if (v->type == T_NUMBER)
{
if (!v->u.number)
error("Can't assign 0 to a string character.\n");
else
*dest->u.string = (char)v->u.number;
}
return;
case T_PROTECTED_CHAR_LVALUE:
{
struct protected_char_lvalue *p;
p = (struct protected_char_lvalue *)dest;
if (p->lvalue->type == T_STRING
&& p->lvalue->u.string == p->start)
{
if (v->type == T_NUMBER)
{
if (!v->u.number)
error("Can't assign 0 to a string character.\n");
else
*p->v.u.string = (char)v->u.number;
}
}
return;
}
case T_POINTER_RANGE_LVALUE:
if (v->type == T_POINTER)
{
(void)ref_array(v->u.vec); /* transfer_...() will free it once */
transfer_pointer_range(v);
}
return;
case T_PROTECTED_POINTER_RANGE_LVALUE:
if (v->type == T_POINTER)
{
(void)ref_array(v->u.vec); /* transfer_...() will free it once */
transfer_protected_pointer_range(
(struct protected_range_lvalue *)dest, v
);
}
return;
case T_STRING_RANGE_LVALUE:
assign_string_range(v, MY_FALSE);
return;
case T_PROTECTED_STRING_RANGE_LVALUE:
assign_protected_string_range(
(struct protected_range_lvalue *)dest, v, MY_FALSE
);
return;
} /* switch() */
/* No more lvalues to follow, old value freed: do the assign next */
break;
} /* end for */
/* Now assign the value to the now-invalid <dest> */
assign_svalue_no_free(dest, v);
} /* assign_svalue() */
/*-------------------------------------------------------------------------*/
static INLINE void
_transfer_svalue_no_free (svalue_t *dest, svalue_t *v)
/* Move the value <v> into <dest>. If <v> is an unshared string, it
* is made shared.
*
* <dest> is assumed to be invalid before the call, <v> is invalid after.
*/
{
/* If <v> is a string, share it */
if (v->type == T_STRING && v->x.string_type == STRING_VOLATILE)
{
put_string(dest, make_shared_string(v->u.string));
if ( !dest->u.string )
{
put_number(dest, 0);
error("Out of memory\n");
}
}
else /* just copy the data */
{
*dest = *v;
}
/* Protection against endless reference loops */
if (dest->type == T_LVALUE || dest->type == T_PROTECTED_LVALUE)
{
v->type = T_INVALID;
check_for_ref_loop(dest);
}
} /* _transfer_svalue_no_free() */
void transfer_svalue_no_free (svalue_t *dest, svalue_t *v)
{ _transfer_svalue_no_free(dest,v); }
#define transfer_svalue_no_free(dest,v) _transfer_svalue_no_free(dest,v)
/*-------------------------------------------------------------------------*/
static INLINE void
transfer_svalue_no_free_spc ( svalue_t *dest, svalue_t *v
, svalue_t *sp, bytecode_p pc)
/* Move the value <v> into <dest>. If <v> is a volatile string, it
* is made shared.
*
* <dest> is assumed to be invalid before the call, <v> is invalid after.
*
* This function may be called from eval_instruction() and needs to receive
* the current stackpointer <sp> and the current programcounter <pc>.
*/
{
if (v->type == T_STRING && v->x.string_type == STRING_VOLATILE)
{
put_string(dest, make_shared_string(v->u.string));
if ( !dest->u.string )
{
put_number(dest, 0);
inter_sp = sp;
inter_pc = pc;
error("Out of memory\n");
}
}
else
{
*dest = *v;
}
/* Protection against endless reference loops */
if (dest->type == T_LVALUE || dest->type == T_PROTECTED_LVALUE)
{
v->type = T_INVALID;
inter_sp = sp;
inter_pc = pc;
check_for_ref_loop(dest);
}
} /* transfer_svalue_no_free_spc() */
/*-------------------------------------------------------------------------*/
void
transfer_svalue (svalue_t *dest, svalue_t *v)
/* Move svalue <v> into svalue <dest>.
*
* <dest> is considered a valid svalue and therefore freed before the
* assignment. <v> will be invalid after the call.
*
* If <dest> is a lvalue, <v> will be moved into the svalue referenced
* to by <dest>. If <v> is a volatile string, it is made shared.
*
* F_VOID_ASSIGN uses a modified copy of this code (for speed reasons).
*/
{
/* Unravel the T_LVALUE chain, if any. */
while (dest->type == T_LVALUE || dest->type == T_PROTECTED_LVALUE)
dest = dest->u.lvalue;
/* Free the <dest> svalue.
* If a T_xxx_LVALUE is found, the transfer will be done here
* immediately.
*/
for(;;)
{
switch (dest->type)
{
case T_STRING:
switch(dest->x.string_type)
{
case STRING_MALLOC:
xfree(dest->u.string);
break;
case STRING_SHARED:
free_string(dest->u.string);
break;
}
break;
case T_OBJECT:
{
object_t *ob = dest->u.ob;
free_object(ob, "transfer_svalue");
break;
}
case T_QUOTED_ARRAY:
case T_POINTER:
free_array(dest->u.vec);
break;
case T_SYMBOL:
free_string(dest->u.string);
break;
case T_CLOSURE:
free_closure(dest);
break;
case T_MAPPING:
free_mapping(dest->u.map);
break;
/* If the final svalue in dest is one of these lvalues,
* the assignment is done right here and now.
* Note that 'dest' in some cases points to a protector structure.
*/
case T_CHAR_LVALUE:
if (v->type == T_NUMBER)
{
if (!v->u.number)
error("Can't assign 0 to a string character.\n");
else
*dest->u.string = (char)v->u.number;
}
else
free_svalue(v);
return;
case T_PROTECTED_CHAR_LVALUE:
{
struct protected_char_lvalue *p;
p = (struct protected_char_lvalue *)dest;
if (p->lvalue->type == T_STRING
&& p->lvalue->u.string == p->start)
{
if (v->type == T_NUMBER)
{
if (!v->u.number)
error("Can't assign 0 to a string character.\n");
else
*p->v.u.string = (char)v->u.number;
return;
}
}
free_svalue(v);
return;
}
case T_POINTER_RANGE_LVALUE:
transfer_pointer_range(v);
return;
case T_PROTECTED_POINTER_RANGE_LVALUE:
transfer_protected_pointer_range(
(struct protected_range_lvalue *)dest, v
);
return;
case T_STRING_RANGE_LVALUE:
assign_string_range(v, MY_TRUE);
return;
case T_PROTECTED_STRING_RANGE_LVALUE:
assign_protected_string_range(
(struct protected_range_lvalue *)dest, v, MY_TRUE
);
return;
} /* end switch */
/* No more lvalues to follow, old value freed: do the assign next */
break;
} /* end for */
/* Transfer the value, making volatile strings shared */
if (v->type == T_STRING && v->x.string_type == STRING_VOLATILE)
{
put_string(dest, make_shared_string(v->u.string));
/* TODO: Nanue? No check for dest->u.string != NULL?
* TODO:: Like in transfer_svalue_no_free_spc()?
*/
}
else
{
*dest = *v;
}
/* Protection against endless reference loops */
if (dest->type == T_LVALUE || dest->type == T_PROTECTED_LVALUE)
{
v->type = T_INVALID;
check_for_ref_loop(dest);
}
} /* transfer_svalue() */
/*-------------------------------------------------------------------------*/
static void
transfer_pointer_range (svalue_t *source)
/* Transfer the vector <source> to the vector range defined by
* <special_lvalue>, modifying the target vector in special_lvalue
* accordingly. <source> is freed once in the call.
*
* If <source> is not a vector, it is just freed.
*/
{
if (source->type == T_POINTER)
{
vector_t *sv; /* Source vector (from source) */
vector_t *dv; /* Destination vector (from special_lvalue) */
vector_t *rv; /* Result vector */
mp_int dsize; /* Size of destination vector */
mp_int ssize; /* Size of source vector */
mp_int index1, index2; /* First and last index of destination range */
mp_int i;
/* Setup the variables */
dsize = special_lvalue.size;
index1 = special_lvalue.index1;
index2 = special_lvalue.index2;
dv = special_lvalue.v.u.lvalue->u.vec;
sv = source->u.vec;
ssize = (mp_int)VEC_SIZE(sv);
#ifdef NO_NEGATIVE_RANGES
if (index1 > index2)
error("Illegal range [%ld..%ld] for assignment.\n"
, index1, index2-1
);
#endif /* NO_NEGATIVE_RANGES */
if (ssize + index1 - index2 == 0)
{
/* <source> fits exactly into the target range */
svalue_t *s, *d; /* Copy source and destination */
s = sv->item;
d = dv->item + index1;
ref_array(dv); /* protect against recursive refs during the copy */
/* If there is just one ref to the source, use the faster
* transfer instead of the slow assign for the copy.
*/
if (sv->ref == 1)
{
for (i = ssize; --i >= 0; )
{
transfer_svalue(d++, s++);
}
free_empty_vector(sv);
}
else /* sv->ref > 1 */
{
for (i = ssize; --i >= 0; )
{
assign_svalue(d++, s++);
}
free_array(sv);
/* deref_array() is not enough, because in situations
* where one d == sv, eg
* arr = ({ ({ 1 }) });
* arr[0..0] = arr[0];
* sv would be left behind with 0 refs but unfreed.
*/
}
free_array(dv); /* Undo the ref_array() above */
}
else
{
/* Create a new vector */
svalue_t *s, *d; /* Copy source and destination */
rv = allocate_array(dsize + ssize + index1 - index2);
special_lvalue.v.u.lvalue->u.vec = rv;
s = dv->item;
d = rv->item;
for (i = index1; --i >= 0; )
{
assign_svalue_no_free(d++, s++);
}
s = sv->item;
for (i = ssize; --i >= 0; )
{
assign_svalue_no_free(d++, s++);
}
free_array(sv);
s = dv->item + index2;
for (i = dsize - index2; --i >= 0; )
{
assign_svalue_no_free(d++, s++);
}
free_array(dv); /* this can make the lvalue invalid to use */
}
}
else
/* Not a pointer: just free it */
free_svalue(source);
} /* transfer_pointer_range() */
/*-------------------------------------------------------------------------*/
static void
transfer_protected_pointer_range ( struct protected_range_lvalue *dest
, svalue_t *source)
/* Transfer the vector <source> to the vector range defined by
* <dest>, modifying the target vector in <dest>
* accordingly. <source> is freed once in the call.
*
* If <source> is not a vector, it is just freed.
*/
{
if (source->type == T_POINTER && dest->v.u.vec == dest->lvalue->u.vec)
{
vector_t *sv; /* Source vector (from source) */
vector_t *dv; /* Dest vector (from dest) */
vector_t *rv; /* Result vector */
mp_int dsize; /* Size of the dest vector */
mp_int ssize; /* Size of the source vector */
mp_int index1, index2; /* Target range indices */
mp_int i;
/* Setup the variables */
dsize = dest->size;
index1 = dest->index1;
index2 = dest->index2;
dv = dest->v.u.vec;
sv = source->u.vec;
ssize = (mp_int)VEC_SIZE(sv);
#ifdef NO_NEGATIVE_RANGES
if (index1 > index2)
error("Illegal range [%ld..%ld] for assignment.\n"
, index1, index2-1
);
#endif /* NO_NEGATIVE_RANGES */
if (ssize + index1 - index2 == 0)
{
/* <source> fits exactly into the target range */
svalue_t *s, *d; /* Copy source and destination */
s = sv->item;
d = dv->item + index1;
/* If there is just one ref to the source, use the faster
* transfer instead of the slow assign for the copy.
*/
if (sv->ref == 1)
{
for (i = ssize; --i >= 0; )
{
transfer_svalue(d++, s++);
}
free_empty_vector(sv);
}
else /* sv->ref > 1 */
{
for (i = ssize; --i >= 0; )
{
assign_svalue(d++, s++);
}
deref_array(sv);
/* The if() above effectively did the 'free_svalue(source)' */
}
}
else
{
/* Create a new vector */
svalue_t *s, *d; /* Copy source and destination */
rv = allocate_array(dsize + ssize + index1 - index2);
dest->lvalue->u.vec = rv;
s = dv->item;
d = rv->item;
for (i = index1; --i >= 0; )
{
assign_svalue_no_free(d++, s++);
}
s = sv->item;
for (i = ssize; --i >= 0; ) {
assign_svalue_no_free(d++, s++);
}
free_array(sv);
s = dv->item + index2;
for (i = dsize - index2; --i >= 0; )
{
assign_svalue_no_free(d++, s++);
}
free_array(dv); /* this can make the lvalue invalid to use */
}
}
else
/* Not a pointer: just free it */
free_svalue(source);
} /* transfer_protected_pointer_range() */
/*-------------------------------------------------------------------------*/
static void
assign_string_range (svalue_t *source, Bool do_free)
/* Transfer the string <source> to the string range defined by
* <special_lvalue>, modifying the target string in special_lvalue
* accordingly. If <do_free> is TRUE, <source> is freed once in the call.
*
* If <source> is not a string, it is just freed resp. ignored.
*/
{
if (source->type == T_STRING)
{
svalue_t *dsvp; /* destination svalue (from special_lvalue) */
char *ds; /* destination string (from dsvp) */
char *ss; /* source string (from source) */
char *rs; /* result string */
mp_int dsize; /* size of destination string */
mp_int ssize; /* size of source string */
mp_int index1, index2; /* range indices */
/* Set variables */
dsize = special_lvalue.size;
index1 = special_lvalue.index1;
index2 = special_lvalue.index2;
dsvp = special_lvalue.v.u.lvalue;
ds = dsvp->u.string;
ss = source->u.string;
ssize = (mp_int)svalue_strlen(source);
#ifdef NO_NEGATIVE_RANGES
if (index1 > index2)
error("Illegal range [%ld..%ld] for assignment.\n"
, index1, index2-1
);
#endif /* NO_NEGATIVE_RANGES */
/* Create the new string */
rs = xalloc((size_t)(dsize + ssize + index1 - index2 + 1));
if (!rs)
{
/* We don't pop the stack here --> don't free source */
error("Out of memory\n");
}
if (index1)
memcpy(rs, ds, (size_t)index1);
if (ssize)
memcpy(rs + index1, ss, (size_t)ssize);
strcpy(rs + index1 + ssize, ds + index2);
/* Assign the new string in place of the old */
free_string_svalue(dsvp);
dsvp->x.string_type = STRING_MALLOC;
dsvp->u.string = rs;
if (do_free)
free_string_svalue(source);
}
else
{
/* Not a string: just free it */
if (do_free)
free_svalue(source);
}
} /* assign_string_range() */
/*-------------------------------------------------------------------------*/
static void
assign_protected_string_range ( struct protected_range_lvalue *dest
, svalue_t *source
, Bool do_free
)
/* Transfer the string <source> to the string range defined by
* <dest>, modifying the target string in dest
* accordingly.
*
* If <do_free> is TRUE, <source> and the protector <dest> are freed once
* in the call.
*
* If <source> is not a string, it is just freed resp. ignored.
*/
{
if (source->type == T_STRING)
{
svalue_t *dsvp; /* destination value (from dest) */
char *ss; /* source string (from source) */
char *ds; /* destination string (from dsvp) */
char *rs; /* result string */
mp_int dsize; /* size of destination string */
mp_int ssize; /* size of source string */
mp_int index1, index2; /* range indices */
/* Set variables */
dsize = dest->size;
index1 = dest->index1;
index2 = dest->index2;
dsvp = dest->lvalue;
ds = dest->v.u.string;
#ifdef NO_NEGATIVE_RANGES
if (index1 > index2)
error("Illegal range [%ld..%ld] for assignment.\n"
, index1, index2-1
);
#endif /* NO_NEGATIVE_RANGES */
/* If the lvalue is no longer valid, free it */
if (dsvp->u.string != ds)
{
if (do_free)
{
free_svalue(source);
xfree(dest->v.u.string);
xfree(dest);
}
return;
}
/* Create a new string */
ss = source->u.string;
ssize = (mp_int)svalue_strlen(source);
rs = xalloc((size_t)(dsize + ssize + index1 - index2 + 1));
if (!rs)
{
error("Out of memory\n");
}
if (index1)
memcpy(rs, ds, (size_t)index1);
if (ssize)
memcpy(rs + index1, ss, (size_t)ssize);
strcpy(rs + (dest->index2 = (int)(index1 + ssize)), ds + index2);
xfree(ds);
dest->v.u.string = dsvp->u.string = rs;
if (do_free)
{
free_string_svalue(source);
dest->v.x.string_type = STRING_MALLOC;
free_protector_svalue(&dest->protector);
xfree(dest);
}
}
else
{
/* Not a string: just free it */
if (do_free)
{
free_svalue(source);
dest->v.x.string_type = STRING_MALLOC;
free_protector_svalue(&dest->protector);
xfree(dest);
}
}
} /* transfer_protected_string_range() */
/*-------------------------------------------------------------------------*/
static void
add_number_to_lvalue (svalue_t *dest, int i, svalue_t *pre, svalue_t *post)
/* Add the number <i> to the (PROTECTED_)LVALUE <dest>.
* If <pre> is not null, the <dest> value before the addition is copied
* into it.
* If <post> is not null, the <dest> value after the addition is copied
* into it.
* Both <pre> and <post> are supposed to be empty svalues when given.
*
* If <dest> is of the wrong type, an error is generated.
*/
{
/* Deref the T_(PROTECTED_)LVALUES */
do
dest = dest->u.lvalue;
while (dest->type == T_LVALUE || dest->type == T_PROTECTED_LVALUE);
/* Now increment the non-LVALUE */
switch (dest->type)
{
default:
error("Reference to bad type to ++/--\n");
break;
case T_NUMBER:
if (pre) put_number(pre, dest->u.number);
dest->u.number += i;
if (post) put_number(post, dest->u.number);
break;
case T_FLOAT:
{
STORE_DOUBLE_USED
double d;
d = READ_DOUBLE(dest);
if (pre)
{
pre->type = T_FLOAT;
STORE_DOUBLE(pre, d);
}
d += (double)i;
STORE_DOUBLE(dest, d);
if (post)
{
post->type = T_FLOAT;
STORE_DOUBLE(post, d);
}
break;
}
case T_PROTECTED_LVALUE:
add_number_to_lvalue(dest, i, pre, post);
break;
case T_CHAR_LVALUE:
if (((*dest->u.string + i) & 0xff) == 0)
error("Can't set string character to 0.\n");
if (pre) put_number(pre, (*dest->u.string));
(*dest->u.string) += i;
if (post) put_number(post, (*dest->u.string));
break;
case T_PROTECTED_CHAR_LVALUE:
{
struct protected_char_lvalue *p;
p = (struct protected_char_lvalue *)dest;
if (p->lvalue->type == T_STRING
&& p->lvalue->u.string == p->start)
{
if (((*p->v.u.string + i) & 0xff) == 0)
error("Can't set string character to 0.\n");
if (pre) put_number(pre, *p->v.u.string);
i = *p->v.u.string += i;
if (post) put_number(post, i);
}
break;
}
} /* switch() */
} /* add_number_to_lvalue() */
/*-------------------------------------------------------------------------*/
static vector_t *
inter_add_array (vector_t *q, vector_t **vpp)
/* Append array <q> to array *<vpp>. Both <q> and *<vpp> are freed,
* the result vector (just one ref) is assigned to *<vpp> and also returned.
*
* <inter_sp> is supposed to point at the two vectors and will be decremented
* by 2.
*/
{
vector_t *p; /* The second summand vector */
mp_int cnt;
vector_t *r; /* Result vector */
svalue_t *s, *d; /* Pointers for copying: src and dest */
size_t p_size, q_size; /* Sizes of p and q */
p = *vpp;
inter_sp -= 2;
/* Out of memory might result in some memory leaks. Better that freeing
* arrays with 0 ref count, or indigestion in garbage_collection() .
* It will simply give some more debugging output...
*/
/* *vpp could be in the summands, thus don't free p / q before
* assigning.
* On the other hand, with an uninitialized array, we musn't assign
* before the copying is done.
*/
p_size = VEC_SIZE(p);
q_size = VEC_SIZE(q);
s = p->item;
/* Allocate the result vector and copy p into it.
*/
if (!(p->ref-1))
{
/* p will be deallocated completely - try to optimize a bit */
#ifdef MALLOC_smalloc
/* We try to expand the existing memory for p (without moving)
* instead of allocating a completely new vector.
*/
d = malloc_increment_size(p, q_size * sizeof(svalue_t));
if ( NULL != d)
{
/* We got the additional memory */
r = p;
r->ref = 1;
r->size = p_size + q_size;
r->user->size_array -= p_size;
r->user = current_object->user;
r->user->size_array += p_size + q_size;
if (max_array_size && p_size + q_size > max_array_size)
{
/* Oops, overflow - invalidate everything */
*vpp = allocate_array(0);
d = r->item + p_size;
for (cnt = (mp_int)q_size; --cnt >=0; )
{
d[cnt].type = T_INVALID;
}
free_array(r);
free_array(q);
error("Illegal array size: %ld.\n", (long)(p_size + q_size));
}
} else
#endif
/* Just allocate a new vector and memcopy p into it. */
{
r = allocate_uninit_array((p_int)(p_size + q_size));
deref_array(p);
d = r->item;
for (cnt = (mp_int)p_size; --cnt >= 0; )
{
*d++ = *s++;
}
}
}
else
{
/* p must survive: allocate a new vector and assign the values
* from p.
*/
r = allocate_uninit_array((p_int)(p_size + q_size));
deref_array(p);
d = r->item;
for (cnt = (mp_int)p_size; --cnt >= 0; ) {
assign_checked_svalue_no_free (d++, s++, inter_sp, inter_pc);
}
}
/* Add the values from q. Again, try to optimize */
s = q->item;
if (q->ref == 1)
{
for (cnt = (mp_int)q_size; --cnt >= 0; )
{
if (destructed_object_ref(s))
assign_svalue(s, &const0);
*d++ = *s++;
}
*vpp = r;
free_empty_vector(q);
}
else /* q->ref > 1 */
{
for (cnt = (mp_int)q_size; --cnt >= 0; ) {
assign_checked_svalue_no_free (d++, s++, inter_sp, inter_pc);
}
*vpp = r;
deref_array(q);
}
if (!p->ref && p != q)
free_empty_vector(p);
return r;
} /* inter_add_array() */
/*=========================================================================*/
/* S T A C K */
/*-------------------------------------------------------------------------*/
/* The following functions handle the pushing and popping of the
* interpreter stack. Often functions appear in two versions: one version
* using the global variable <inter_sp>, the other version receiving and
* returning the old/new stack pointer as argument and result.
*
* Obviously, the former version can be easily called from outside the
* interpreter, while the latter allows better optimization.
*
* To make things even more complicated, some of the 'slower' functions
* are redefined with preprocessor macros to use the faster function - this
* is meant to make the code in this module faster, but relies on certain
* naming conventions (e.g. that 'sp' is always the local copy of the
* stack pointer).
*
* TODO: Streamline the functions, given them macros as fast alternative
* TODO:: publish them all in interpret.h and enforce their use.
*-------------------------------------------------------------------------
* The functions are:
*
* _push_object(ob, sp), push_object(ob):
* Push a non-destructed object onto the stack.
* _push_valid_object(ob, sp), push_valid_object(ob):
* Push an object onto the stack.
* _push_number(n, sp), push_number(n):
* Push a number onto the stack.
* _push_shared_string(p, sp), push_shared_string(p),
* push_referenced_shared_string(p):
* Push a shared string onto the stack.
* push_string_shared(p):
* Share a string and push it onto the stack.
* _push_string_malloced(p, sp), push_string_malloced(p):
* Malloc a copy of a string and push it onto the stack.
* _push_malloced_string(p, sp), push_malloced_string(p):
* Push a malloced string onto the stack.
* put_malloced_string(p, sp):
* Put a malloced string onto the stack.
* _push_volatile_string(p, sp), push_volatile_string(p):
* Push a volatile string onto the stack.
* push_svalue(v), push_svalue_block(num,v):
* Push one or more svalues onto the stack.
* pop_stack(), _drop_n_elems(n,sp):
* Pop (free) elements from the stack.
* stack_overflow(sp,fp,pc):
* Handle a stack overflow.
* push_vector(v), push_referenced_vector(v):
* Push a vector onto the stack.
* push_referenced_mapping(m):
* Push a mapping onto the stack.
*
* and as macros only:
*
* push_mapping(m,sp):
* Push a mapping onto the stack.
*/
/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
_push_object (object_t *ob, svalue_t *sp)
/* Push the object <ob> onto the stack, currently ending at <sp>, and
* return the new stackpointer.
* <ob> must not be destructed.
* The ref to <ob> is incremented.
*/
{
sp++;
put_ref_object(sp, ob, "push_object");
return sp;
}
/*-------------------------------------------------------------------------*/
void
push_object (object_t *ob)
/* Push the object <ob> onto the stack defined by <inter_sp>.
* <ob> must not be destructed.
* The ref to <ob> is incremented.
*/
{
inter_sp++;
put_ref_object(inter_sp, ob, "push_object");
}
/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
_push_valid_ob (object_t *ob, svalue_t *sp)
/* Push the object <ob> onto the stack, currently ending at <sp>, and
* return the new stackpointer.
* If <ob> is destructed, the number 0 is pushed.
* The ref to <ob> is incremented if necessary.
*/
{
sp++;
if (ob->flags & O_DESTRUCTED)
put_number(sp, 0);
else
put_ref_object(sp, ob, "push_valid_ob");
return sp;
}
/*-------------------------------------------------------------------------*/
void
push_valid_ob (object_t *ob)
/* Push the object <ob> onto the stack defined by <inter_sp>.
* If <ob> is destructed, the number 0 is pushed.
* The ref to <ob> is incremented if necessary.
*/
{
inter_sp++;
if (ob->flags & O_DESTRUCTED)
put_number(inter_sp, 0);
else
put_ref_object(inter_sp, ob, "push_valid_ob");
}
/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
_push_number (p_int n, svalue_t *sp)
/* Push the number <n> onto the stack, currently ending at <sp>, and return
* the new stackpointer.
*/
{
sp++;
put_number(sp, n);
return sp;
}
/*-------------------------------------------------------------------------*/
void
push_number (p_int n)
/* Push the number <n> onto the stack as defined by <inter_sp>.
*/
{
inter_sp++;
put_number(inter_sp, n);
}
/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
_push_shared_string (char *p, svalue_t *sp)
/* Push the shared string <p> onto the stack, currently ending at <sp>,
* and return the new stackpointer.
* The refs of <p> are incremented.
*/
{
sp++;
put_ref_string(sp, p);
return sp;
}
/*-------------------------------------------------------------------------*/
void
push_shared_string (char *p)
/* Push the shared string <p> onto the stack as defined by <inter_sp>.
* The refs of <p> are incremented.
*/
{
inter_sp = _push_shared_string(p, inter_sp);
}
/*-------------------------------------------------------------------------*/
void
push_referenced_shared_string (char *p)
/* Push the shared string <p> onto the stack as defined by <inter_sp>.
* The refs of <p> are _not_ incremented.
*/
{
svalue_t *sp = inter_sp;
sp++;
put_string(sp, p);
inter_sp = sp;
}
/*-------------------------------------------------------------------------*/
void
push_string_shared (char *p)
/* Make a shared string of <p> and push it onto the stack as defined
* by <inter_sp>.
*/
{
inter_sp++;
put_string(inter_sp, make_shared_string(p));
}
/*-------------------------------------------------------------------------*/
static svalue_t *
_push_string_malloced (char *p, svalue_t *sp)
/* Malloc a copy of string <p>, push it onto the stack currently ending
* at <sp>, and return the new stackpointer.
*/
{
char *s;
s = xalloc(strlen(p)+1);
strcpy(s, p);
sp++;
put_malloced_string(sp, s);
return sp;
}
/*-------------------------------------------------------------------------*/
void
push_string_malloced (char *p)
/* Malloc a copy of string <p> and push it onto the stack as defined
* by <inter_sp>.
*/
{
svalue_t *sp;
char *s;
s = xalloc(strlen(p)+1);
strcpy(s, p);
sp = ++inter_sp;
put_malloced_string(sp, s);
}
/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
_push_malloced_string (char *p, svalue_t *sp)
/* Push malloced string <p> onto the stack, currently ending at <sp>,
* and return the new stackpointer.
* <p> is not copied!
*/
{
sp++;
put_malloced_string(sp, p);
return sp;
}
/*-------------------------------------------------------------------------*/
void push_malloced_string (char *p)
/* Push malloced string <p> onto the stack as defined by <inter_sp>.
* <p> is not copied!
*/
{
inter_sp++;
put_malloced_string(inter_sp, p);
}
/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
_push_volatile_string (char *p, svalue_t *sp)
/* Push the volatile string <p> onto the stack, currently ending at <sp>,
* and return the new stackpointer.
*/
{
sp++;
put_volatile_string(sp, p);
return sp;
}
/*-------------------------------------------------------------------------*/
void
push_volatile_string (char *p)
/* Push the volatile string <p> onto the stack as defined by <inter_sp>.
*/
{
inter_sp++;
put_volatile_string(inter_sp, p);
}
/*-------------------------------------------------------------------------*/
void
push_svalue (svalue_t *v)
/* Push the svalue <v> onto the stack as defined by <inter_sp>.
* Same semantic as assign_svalue_no_free().
*/
{
assign_svalue_no_free(++inter_sp, v);
}
/*-------------------------------------------------------------------------*/
void
push_svalue_block (int num, svalue_t *v)
/* Push all <num> svalues starting at <v> onto the stack as defined by
* <inter_sp>. Same semantic as assign_svalue_no_free().
*/
{
svalue_t *w;
for (w = inter_sp; --num >= 0; v++)
{
w++;
assign_lrvalue_no_free(w, v);
}
inter_sp = w;
}
/*-------------------------------------------------------------------------*/
static INLINE void
_pop_stack (void)
/* Pop the topmost element from the stack as defined by <inter_sp>,
* using free_svalue().
*/
{
#ifdef DEBUG
if (inter_sp < VALUE_STACK)
fatal("VM Stack underflow: %ld too low.\n", (long)(VALUE_STACK - inter_sp));
#endif
free_svalue(inter_sp--);
}
void pop_stack (void) { _pop_stack(); }
/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
_pop_n_elems (int n, svalue_t *sp)
/* Pop the <n> topmost elements from the stack, currently ending at <sp>,
* and return the new stackpointer.
* The elements are freed using free_svalue().
*/
{
#ifdef DEBUG
if (n < 0)
fatal("pop_n_elems: %d elements.\n", n);
#endif
for (; --n >= 0; )
{
free_svalue(sp--);
}
return sp;
}
svalue_t * pop_n_elems (int n, svalue_t *sp)
{ return _pop_n_elems(n, sp); }
/*-------------------------------------------------------------------------*/
static void
stack_overflow (svalue_t *sp, svalue_t *fp, bytecode_p pc)
/* Recover from a stack overflow by popping all the elements between the
* current stack end <sp> and the begin of the frame <fp>.
* The function then assigns the new <sp> == <fp> and the <pc> to the
* corresponding inter_xx variables and generates an error.
*/
{
if (sp >= &VALUE_STACK[SIZEOF_STACK])
fatal("Fatal stack overflow: %ld too high.\n"
, (long)(sp - &VALUE_STACK[SIZEOF_STACK])
);
sp = _pop_n_elems(sp-fp, sp);
ERROR("stack overflow\n")
}
/*-------------------------------------------------------------------------*/
void
push_vector (vector_t *v)
/* Push vector <v> onto the stack as defined by <inter_sp>.
* The refs of <v> are incremented.
*/
{
inter_sp++;
put_ref_array(inter_sp, v);
}
/*-------------------------------------------------------------------------*/
void
push_referenced_vector (vector_t *v)
/* Push vector <v> onto the stack as defined by <inter_sp>.
* The refs of <v> are _not_ incremented.
*/
{
inter_sp++;
put_array(inter_sp, v);
}
/*-------------------------------------------------------------------------*/
void
push_referenced_mapping (mapping_t *m)
/* Push mapping <m> onto the stack as defined by <inter_sp>.
* The refs of <m> are _not_ incremented.
*/
{
inter_sp++;
put_mapping(inter_sp, m);
}
/*-------------------------------------------------------------------------*/
/* Macro-only functions:
*/
#define push_mapping(m) ( \
sp++,\
sp->type = T_MAPPING,\
sp->u.map = ref_mapping(m)\
)
/*-------------------------------------------------------------------------*/
/* Fast version of several functions, must come last so to not disturb
* the actual definitions:
*/
#define push_object(ob) (sp = _push_object((ob), sp))
#define push_valid_ob(ob) (sp = _push_valid_ob((ob), sp))
#define push_number(n) (sp = _push_number(n, sp))
#define push_shared_string(p) (sp = _push_shared_string((p), sp))
#define push_string_malloced(p) (sp = _push_string_malloced(p, sp))
#define push_malloced_string(s) (sp = _push_malloced_string((s), sp))
#define push_volatile_string(s) (sp = _push_volatile_string((s), sp))
#define pop_stack() free_svalue(sp--)
#define pop_n_elems(n) (sp = _pop_n_elems((n), sp))
#define push_vector(v) ( \
sp++,\
sp->type = T_POINTER,\
sp->u.vec = ref_array(v)\
)
#define push_referenced_vector(v) ( \
sp++,\
sp->type = T_POINTER,\
sp->u.vec = (v)\
)
/*=========================================================================*/
/* I N D E X I N G */
/*-------------------------------------------------------------------------*/
/* The following functions are concerned with the indexing of single
* elements and ranges of strings, vectors and mappings, both as rvalue
* and lvalue.
*
* Most of the functions are just the implementations of the corresponding
* machine operators and are called just from the interpreter switch().
* The actual arguments are pulled from the vm stack and the results pushed;
* the functions receive the current stackpointer and programcounter as
* function call parameters. The program counter is usally only used to
* update <inter_pc> in case of errors. Result of the call is the new
* stackpointer pointing to the result on the machine stack.
*
* TODO: A lot of the functions differ only in minute details - test how
* TODO:: much time merging the functions (and adding if()s for the
* TODO:: differences) really costs.
*-------------------------------------------------------------------------
* The functions (in a LPCish notation) are:
*
* push_indexed_lvalue(vector|mapping v, int|mixed i)
* Return &(v[i]), unprotected.
* push_rindexed_lvalue(vector v, int i)
* Return &(v[<i]), unprotected.
* push_protected_indexed_lvalue(vector|mapping v, int|mixed i)
* Return &(v[i]), protected.
* push_protected_rindexed_lvalue(vector v, int i)
* Return &(v[<i]), protected.
* push_protected_indexed_map_lvalue(mapping m, mixed i, int j)
* Return &(m[i:j]), protected.
* index_lvalue(vector|mapping|string & v, int|mixed i)
* Return &(*v[i]), unprotected, using special_lvalue.
* rindex_lvalue(vector|string & v, int i)
* Return &(*v[<i]), unprotected, using special_lvalue.
* protected_index_lvalue(vector|mapping|string & v, int|mixed i)
* Return &(*v[i]), protected.
* protected_rindex_lvalue(vector|string & v, int i)
* Return &(*v[<i]), protected.
* range_lvalue(vector|string & v, int i2, int i1)
* Return &(*v[i1..i2]), unprotected, using special_lvalue.
* protected_range_lvalue(vector|string & v, int i2, int i1)
* Return &(*v[i1..i2]), protected.
* push_indexed_value(string|vector|mapping v, int|mixed i)
* Return v[i].
* push_rindexed_value(string|vector v, int i)
* Return v[<i].
*/
/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
push_indexed_lvalue (svalue_t *sp, bytecode_p pc)
/* Operator F_PUSH_INDEXED_LVALUE(vector v=sp[-1], int i=sp[0])
* Operator F_PUSH_INDEXED_LVALUE(mapping v=sp[-1], mixed i=sp[0])
*
* Compute the lvalue &(v[i]) and push it into the stack. If v has just
* one ref left, the indexed item is stored in indexing_quickfix and the
* lvalue refers to that variable.
*/
{
svalue_t *i; /* the index value */
svalue_t *vec; /* the indexed vector or mapping */
svalue_t *item; /* the indexed element vec[i] */
int ind; /* numeric value of *i */
/* Get the arguments */
i = sp;
vec = sp - 1;
/* Index a vector.
*/
if (vec->type == T_POINTER)
{
if (i->type != T_NUMBER || (ind = i->u.number) < 0)
{
ERROR("Illegal index for []: not a (positive) number.\n")
/* TODO: Print type and value of i */
return NULL;
}
if ((size_t)ind >= VEC_SIZE(vec->u.vec))
{
ERRORF(("Index out of bounds for []: %ld, vector size: %lu.\n"
, (long)ind, VEC_SIZE(vec->u.vec)))
return NULL;
}
/* Compute the indexed element */
item = &vec->u.vec->item[ind];
if (vec->u.vec->ref == 1)
{
/* Rescue the indexed item as vec will go away */
assign_svalue (&indexing_quickfix, item);
item = &indexing_quickfix;
}
/* Remove the arguments from the stack */
sp = vec;
free_array(vec->u.vec);
/* Return the result */
vec->type = T_LVALUE;
vec->u.lvalue = item;
return sp;
}
/* Index a mapping
*/
if (vec->type == T_MAPPING)
{
mapping_t *m;
m = vec->u.map;
if (!m->num_values)
{
ERROR("Indexing a mapping of width 0.\n")
return NULL;
}
/* Compute the indexed element */
item = get_map_lvalue(m, i);
if (!item)
{
outofmemory("indexed lvalue");
/* NOTREACHED */
return NULL;
}
if (m->ref == 1)
{
/* Rescue the indexed item as vec will go away */
assign_svalue (&indexing_quickfix, item);
item = &indexing_quickfix;
}
/* Remove the arguments from the stack */
free_svalue(sp--);
free_mapping(m);
/* Return the result */
vec->type = T_LVALUE;
vec->u.lvalue = item;
return sp;
}
/* Illegal type to index */
inter_sp = sp;
inter_pc = pc;
error("(lvalue)Indexing on illegal type.\n");
/* TODO: Print the type */
return sp;
} /* push_indexed_lvalue() */
/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
push_rindexed_lvalue (svalue_t *sp, bytecode_p pc)
/* Operator F_PUSH_RINDEXED_LVALUE(vector v=sp[-1], int i=sp[0])
*
* Compute the lvalue &(v[<i]) and push it into the stack. If v has just
* one ref left, the indexed item is stored in indexing_quickfix and the
* lvalue refers to that variable.
*/
{
svalue_t *i; /* the index value */
svalue_t *vec; /* the vector */
svalue_t *item; /* the indexed item */
mp_int ind; /* the numeric value of *i */
/* Get the arguments */
i = sp;
vec = sp - 1;
/* Index a vector.
*/
if (vec->type == T_POINTER)
{
if (i->type != T_NUMBER || (ind = i->u.number) <= 0)
{
ERROR("Illegal index for [<]: not a (positive) number.\n")
/* TODO: Print type and value of i */
return NULL;
}
if ( (ind = (mp_int)VEC_SIZE(vec->u.vec) - ind) < 0) {
ERRORF(("Index out of bounds for [<]: %ld, vector size: %lu.\n"
, (long)(i->u.number), VEC_SIZE(vec->u.vec)))
return NULL;
}
/* Compute the indexed item */
item = &vec->u.vec->item[ind];
if (vec->u.vec->ref == 1)
{
/* Rescue the indexed item as vec will go away */
assign_svalue (&indexing_quickfix, item);
item = &indexing_quickfix;
}
/* Remove the arguments from the stack */
sp = vec;
free_array(vec->u.vec);
/* Return the result */
vec->type = T_LVALUE;
vec->u.lvalue = item;
return sp;
}
/* Indexing on illegal type */
inter_sp = sp;
inter_pc = pc;
error("(lvalue)Indexing on illegal type.\n");
/* TODO: Print type */
return NULL;
} /* push_rindexed_lvalue() */
/*-------------------------------------------------------------------------*/
/* void BUILD_MAP_PROTECTOR(svalue_t *dest, mapping_t *m)
*
* Init svalue <dest> to protectively hold mapping <m> in which one entry
* is about to be used as target for a lvalue.
*
* If mapping <m> is dirty, protect its hash_mapping part by incrementing
* its refcount (and if this is the first call, also initialize the .deleted
* entry), and by making the svalue a T_PROTECTOR_MAPPING.
*
* If <m> is not dirty, not protection is necessary.
*/
#define BUILD_MAP_PROTECTOR(dest, m) \
{ \
struct hash_mapping *hm; \
\
if ( NULL != (hm = (m)->hash) ) { \
if (!hm->ref++) \
hm->deleted = NULL; \
dest.type = T_PROTECTOR_MAPPING; \
} else { \
dest.type = T_MAPPING; \
} \
dest.u.map = m; \
}
/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
push_protected_indexed_lvalue (svalue_t *sp, bytecode_p pc)
/* Op. F_PUSH_PROTECTED_INDEXED_LVALUE(vector v=sp[-1], int i=sp[0])
* Op. F_PUSH_PROTECTED_INDEXED_LVALUE(mapping v=sp[-1], mixed i=sp[0])
*
* Compute the lvalue &(v[i]), store it in a struct protected_lvalue, and
* push the protector as PROTECTED_LVALUE into the stack.
*/
{
svalue_t * i; /* the index */
svalue_t * vec; /* the vector */
svalue_t * item; /* the indexed element */
struct protected_lvalue * lvalue; /* the protector */
int ind; /* numeric value of *i */
/* Get the arguments */
i = sp;
vec = sp - 1;
/* Index a vector.
*/
if (vec->type == T_POINTER)
{
if (i->type != T_NUMBER || (ind = i->u.number) < 0)
{
ERROR("Illegal index for []: not a (positive) number.\n")
/* TODO: Print type and value */
return NULL;
}
if ((size_t)ind >= VEC_SIZE(vec->u.vec))
{
ERRORF(("Index out of bounds for []: %ld, vector size: %lu.\n"
, (long)ind, VEC_SIZE(vec->u.vec)))
return NULL;
}
/* Compute the indexed item and set up the protector */
item = &vec->u.vec->item[ind];
lvalue = (struct protected_lvalue *)xalloc(sizeof *lvalue);
lvalue->v.type = T_PROTECTED_LVALUE;
lvalue->v.u.lvalue = item;
put_array(&(lvalue->protector), vec->u.vec);
/* The one ref to vec is transferred from *vec */
/* Remove the arguments and return the result */
sp = vec;
vec->type = T_LVALUE;
vec->u.lvalue = &lvalue->v;
return sp;
}
/* Index a mapping
*/
if (vec->type == T_MAPPING)
{
mapping_t *m;
m = vec->u.map;
if (!m->num_values)
{
ERROR("Indexing a mapping of width 0.\n");
return NULL;
}
/* Compute the indexed item and set up the protector */
item = get_map_lvalue(m, i);
if (!item)
{
outofmemory("indexed lvalue");
/* NOTREACHED */
return NULL;
}
lvalue = (struct protected_lvalue *)xalloc(sizeof *lvalue);
lvalue->v.type = T_PROTECTED_LVALUE;
lvalue->v.u.lvalue = item;
BUILD_MAP_PROTECTOR(lvalue->protector, m)
/* The one ref is transferred from the stack */
/* Remove the arguments and return the result */
pop_stack();
vec->type = T_LVALUE;
vec->u.lvalue = &lvalue->v;
return sp;
}
/* Indexing on illegal type. */
inter_sp = sp;
inter_pc = pc;
error("(lvalue)Indexing on illegal type.\n");
/* TODO: Print type */
return NULL;
} /* push_protected_indexed_lvalue() */
/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
push_protected_rindexed_lvalue (svalue_t *sp, bytecode_p pc)
/* Op. F_PUSH_PROTECTED_RINDEXED_LVALUE(vector v=sp[-1], int i=sp[0])
*
* Compute the lvalue &(v[<i]), store it in a struct protected_lvalue, and
* push the protector as PROTECTED_LVALUE into the stack.
*/
{
svalue_t * i; /* the index */
svalue_t * vec; /* the vector */
svalue_t * item; /* the indexed element */
struct protected_lvalue * lvalue; /* the protector */
mp_int ind; /* numeric value of *i */
/* Get the arguments */
i = sp;
vec = sp - 1;
/* Index a vector
*/
if (vec->type == T_POINTER)
{
if (i->type != T_NUMBER || (ind = i->u.number) <= 0)
{
ERROR("Illegal index for [<]: not a (positive) number.\n")
/* TODO: Print type and value of i */
return NULL;
}
if ( (ind = (mp_int)VEC_SIZE(vec->u.vec) - ind) < 0)
{
ERRORF(("Index out of bounds for [<]: %ld, vector size: %lu\n"
, (long) i->u.number, VEC_SIZE(vec->u.vec)))
return NULL;
}
/* Compute the indexed element and setup the protector */
item = &vec->u.vec->item[ind];
lvalue = (struct protected_lvalue *)xalloc(sizeof *lvalue);
lvalue->v.type = T_PROTECTED_LVALUE;
lvalue->v.u.lvalue = item;
put_array(&(lvalue->protector), vec->u.vec);
/* The one ref is transferred from the stack */
/* Remove arguments and return result */
sp = vec;
vec->type = T_LVALUE;
vec->u.lvalue = &lvalue->v;
return sp;
}
/* Indexing in illegal type */
inter_sp = sp;
inter_pc = pc;
error("(lvalue)Indexing on illegal type.\n");
/* TODO: Print type */
return NULL;
} /* push_protected_rindexed_lvalue() */
/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
push_protected_indexed_map_lvalue (svalue_t *sp, bytecode_p pc)
/* Op. F_PUSH_PROTECTED_INDEXED_MAP_LVALUE(mapping m=sp[-2], mixed i=sp[-1]
* , int j=sp[0])
*
* Compute the lvalue &(m[i:j]), store it in a struct protected_lvalue, and
* push the protector as PROTECTED_LVALUE into the stack.
*/
{
svalue_t * i; /* the index */
svalue_t * vec; /* the vector */
svalue_t * item; /* the indexed element */
struct protected_lvalue * lvalue; /* the protector */
/* Get the arguments */
i = sp - 1;
vec = sp - 2;
/* Index a mapping.
*/
if (vec->type == T_MAPPING)
{
mapping_t *m;
m = vec->u.map;
if (sp->u.number != T_NUMBER
|| (p_uint)sp->u.number >= (p_uint)m->num_values
/* using uints automagically checks for negative indices */
)
{
ERROR("Illegal subindex for []: not a number or out of bounds.\n")
/* TODO: This error message should be done nicer. */
return NULL;
}
/* Compute the indexed element and setup the protector */
item = get_map_lvalue(m, i);
if (!item)
{
outofmemory("indexed lvalue");
/* NOTREACHED */
return NULL;
}
item += sp->u.number;
lvalue = (struct protected_lvalue *)xalloc(sizeof *lvalue);
lvalue->v.type = T_PROTECTED_LVALUE;
lvalue->v.u.lvalue = item;
BUILD_MAP_PROTECTOR(lvalue->protector, m)
/* The one ref is transferred from the stack */
/* Remove the arguments and return the result */
sp--;
pop_stack();
vec->type = T_LVALUE;
vec->u.lvalue = &lvalue->v;
return sp;
}
/* Indexing on illegal type */
inter_sp = sp;
inter_pc = pc;
error("(lvalue)Indexing on illegal type.\n");
/* TODO: Print type */
return NULL;
} /* push_protected_indexed_map_lvalue() */
/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
index_lvalue (svalue_t *sp, bytecode_p pc)
/* Operator F_INDEX_LVALUE (string|vector &v=sp[0], int i=sp[-1])
* F_INDEX_LVALUE (mapping &v=sp[0], mixed i=sp[-1])
*
* Compute the index &(v[i]) of lvalue <v> and push it into the stack. The
* computed index is a lvalue itself.
* If <v> is a string-lvalue, it is made a malloced string if necessary,
* and the pushed result will be a lvalue pointing to a CHAR_LVALUE stored
* in <special_lvalue>.
*/
{
svalue_t *vec; /* the vector/mapping */
svalue_t *i; /* the index */
int ind; /* numeric value of <i> */
short type; /* type of <vec> */
/* get the arguments */
vec = sp;
i = sp -1;
/* Dereference the initial (and possibly more) lvalue-indirection
*/
do {
vec = vec->u.lvalue;
type = vec->type;
} while (type == T_LVALUE || type == T_PROTECTED_LVALUE);
/* Index a vector.
*/
if (type == T_POINTER)
{
vector_t *v = vec->u.vec;
if (i->type != T_NUMBER || (ind = i->u.number) < 0)
{
ERROR("Illegal index for []: not a (positive) number.\n")
/* TODO: Print type and value of i */
return NULL;
}
if ((size_t)ind >= VEC_SIZE(v))
{
ERRORF(("Index for [] out of bounds: %ld, vector size: %lu\n"
, (long)ind, VEC_SIZE(v)))
return NULL;
}
/* Remove the arguments and push the result */
sp = i;
sp->type = T_LVALUE;
sp->u.lvalue = &v->item[ind];
return sp;
}
/* Index a string.
*/
if (type == T_STRING)
{
if (i->type != T_NUMBER || (ind = i->u.number) < 0)
{
ERROR("Illegal index for []: not a (positive) number.\n")
/* TODO: Print type and value of i */
return NULL;
}
if (ind >= _svalue_strlen(vec) )
{
ERRORF(("Index out for [] of bounds: %ld, string length: %ld.\n"
, (long)ind, (long)_svalue_strlen(vec)))
return NULL;
}
/* If the string is not malloced, i.e. changeable, allocate
* a new copy which can be changed.
*/
if (vec->x.string_type != STRING_MALLOC)
{
char *p = string_copy(vec->u.string);
if (vec->x.string_type == STRING_SHARED)
free_string(vec->u.string);
vec->x.string_type = STRING_MALLOC;
vec->u.string = p;
}
/* Remove the arguments and create and push the result. */
sp = i;
sp->type = T_LVALUE;
sp->u.lvalue = &special_lvalue.v;
special_lvalue.v.type = T_CHAR_LVALUE;
special_lvalue.v.u.string = &vec->u.string[ind];
return sp;
}
/* Index a mapping.
*/
if (type == T_MAPPING)
{
svalue_t *item;
mapping_t *m;
m = vec->u.map;
if (!m->num_values)
{
ERROR("Indexing a mapping of width 0.\n");
return NULL;
}
/* Compute the element */
item = get_map_lvalue(m, i);
if (!item)
{
outofmemory("indexed lvalue");
/* NOTREACHED */
return NULL;
}
/* Remove the arguments and push the result. */
sp = i;
free_svalue(i);
sp->type = T_LVALUE;
sp->u.lvalue = item;
return sp;
}
/* Illegal type to index. */
inter_sp = sp;
inter_pc = pc;
error("(lvalue)Indexing on illegal type.\n");
/* TODO: Print type */
return NULL;
} /* index_lvalue() */
/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
rindex_lvalue (svalue_t *sp, bytecode_p pc)
/* Operator F_RINDEX_LVALUE (string|vector &v=sp[0], int i=sp[-1])
*
* Compute the index &(v[<i]) of lvalue <v> and push it into the stack. The
* computed index is a lvalue itself.
* If <v> is a string-lvalue, it is made a malloced string if necessary,
* and the pushed result will be a lvalue pointing to a CHAR_LVALUE stored
* in <special_lvalue>.
*/
{
svalue_t *vec; /* the vector/string */
svalue_t *i; /* the index */
mp_int ind; /* numeric value of <i> */
short type; /* type of <vec> */
/* get the arguments */
vec = sp;
i = sp -1;
if (i->type != T_NUMBER || (ind = i->u.number) <= 0)
{
ERROR("Illegal index for [<]: not a (positive) number.\n")
/* TODO: Print type and value of i */
return NULL;
}
/* Dereference the initial (and possibly more) lvalue-indirection
*/
do {
vec = vec->u.lvalue;
type = vec->type;
} while (type == T_LVALUE || type == T_PROTECTED_LVALUE);
/* Index a vector
*/
if (type == T_POINTER)
{
vector_t *v = vec->u.vec;
if ( (ind = (mp_int)VEC_SIZE(v) - ind) < 0)
{
ERRORF(("Index out of bounds for [<]: %ld, vector size: %lu\n"
, (long) i->u.number, VEC_SIZE(vec->u.vec)))
return NULL;
}
/* Remove the arguments and return the result */
sp = i;
sp->type = T_LVALUE;
sp->u.lvalue = &v->item[ind];
return sp;
}
/* Index a string
*/
if (type == T_STRING)
{
if ( (ind = (mp_int)_svalue_strlen(vec) - ind) < 0)
{
ERRORF(("Index out of bounds for [<]: %ld, string length: %ld\n"
, (long) i->u.number, (long)_svalue_strlen(vec)))
return NULL;
}
/* If the string is not malloced, i.e. changeable, allocate a
* copy in vec which can be changed.
*/
if (vec->x.string_type != STRING_MALLOC)
{
char *p = string_copy(vec->u.string);
if (vec->x.string_type == STRING_SHARED)
free_string(vec->u.string);
vec->x.string_type = STRING_MALLOC;
vec->u.string = p;
}
/* Remove the argument and return the result */
sp = i;
sp->type = T_LVALUE;
sp->u.lvalue = &special_lvalue.v;
special_lvalue.v.type = T_CHAR_LVALUE;
special_lvalue.v.u.string = &vec->u.string[ind];
return sp;
}
/* Indexing on illegal type */
inter_sp = sp;
inter_pc = pc;
error("(lvalue)Indexing on illegal type.\n");
/* TODO: Print the type */
return NULL;
} /* rindex_lvalue() */
/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
protected_index_lvalue (svalue_t *sp, bytecode_p pc)
/* Operator F_PROTECTED_INDEX_LVALUE (string|vector &v=sp[0], int i=sp[-1])
* F_PROTECTED_INDEX_LVALUE (mapping &v=sp[0], mixed i=sp[-1])
*
* Compute the index &(*v[i]) of lvalue <v>, wrap it into a protector, and push
* the reference to the protector as PROTECTED_LVALUE onto the stack.
*
* If <v> is a protected non-string-lvalue, the protected_lvalue referenced
* by <v>.u.lvalue will be deallocated, and the protector itself will be
* stored in <last_indexing_protector> for the time being.
*
* If <v> is a string-lvalue, it is made a malloced string if necessary.
*/
{
svalue_t *vec; /* the indexed value */
svalue_t *i; /* the index */
int ind; /* numeric value of <i> */
short type; /* type of <vec> */
/* Get arguments */
vec = sp->u.lvalue;
i = sp -1;
/* The loop unravels the (possible) lvalue chain starting at vec.
* When a non-lvalue is encountered, the indexing takes place
* the function returns.
*/
for (;;)
{
type = vec->type;
/* Index a vector.
*/
if (type == T_POINTER)
{
vector_t *v = vec->u.vec;
struct protected_lvalue *lvalue;
if (i->type != T_NUMBER || (ind = i->u.number) < 0)
{
ERROR("Illegal index for []: not a (positive) number.\n")
/* TODO: Print type and value of i */
return NULL;
}
if ((size_t)ind >= VEC_SIZE(v))
{
ERRORF(("Index for [] out of bounds: %ld, vector size: %lu.\n"
, (long)ind, VEC_SIZE(v)))
return NULL;
}
/* Drop the arguments */
sp = i;
/* Compute and return the result */
lvalue = (struct protected_lvalue *)xalloc(sizeof *lvalue);
lvalue->v.type = T_PROTECTED_LVALUE;
lvalue->v.u.lvalue = &v->item[ind];
put_ref_array(&(lvalue->protector), v);
sp->type = T_LVALUE;
sp->u.lvalue = &lvalue->v;
return sp;
}
/* Index a string.
*/
if (type == T_STRING)
{
struct protected_char_lvalue *val;
if (i->type != T_NUMBER || (ind = i->u.number) < 0)
{
ERROR("Illegal index for []: not a (positive) number.\n")
/* TODO: Print type and value of i */
return NULL;
}
if (ind >= svalue_strlen(vec) )
{
ERRORF(("Index for [] out of bounds: %ld, string length: %ld.\n"
, (long)ind, (long)_svalue_strlen(vec)))
return NULL;
}
/* If the string is not allocated, ie. changeable, allocate
* a new changeable copy.
*/
if (vec->x.string_type != STRING_MALLOC)
{
char *p = string_copy(vec->u.string);
if (vec->x.string_type == STRING_SHARED)
free_string(vec->u.string);
vec->u.string = p;
/* string_type is used by svalue_strlen */
vec->x.string_type = STRING_MALLOC;
}
/* Make the string 'VOLATILE' so that we have full control
* over its deallocation.
*/
vec->x.string_type = STRING_VOLATILE;
/* Drop the arguments */
sp = i;
/* Compute and return the result */
val = (struct protected_char_lvalue *)xalloc(sizeof *val);
val->v.type = T_PROTECTED_CHAR_LVALUE;
val->v.u.string = &vec->u.string[ind];
val->lvalue = vec;
val->start = vec->u.string;
val->protector.type = T_INVALID;
sp->type = T_LVALUE;
sp->u.protected_char_lvalue = val;
return sp;
}
/* Index a mapping.
*/
if (type == T_MAPPING)
{
svalue_t *item;
struct protected_lvalue *lvalue;
mapping_t *m;
m = vec->u.map;
if (!m->num_values)
{
ERROR("Indexing a mapping of width 0.\n");
return NULL;
}
/* Compute the indexed element */
item = get_map_lvalue(m, i);
if (!item)
{
outofmemory("indexed lvalue");
/* NOTREACHED */
return NULL;
}
/* Build the protector */
ref_mapping(m);
lvalue = (struct protected_lvalue *)xalloc(sizeof *lvalue);
lvalue->v.type = T_PROTECTED_LVALUE;
lvalue->v.u.lvalue = item;
BUILD_MAP_PROTECTOR(lvalue->protector, m)
/* Drop the arguments and return the result */
sp = i;
free_svalue(i);
sp->type = T_LVALUE;
sp->u.lvalue = &lvalue->v;
return sp;
}
/* lvalues are just dereferenced.
*/
if (type == T_LVALUE)
{
vec = vec->u.lvalue;
continue;
}
/* Non-string protected lvalues are dereferenced, a protected
* string lvalue is indexed immediately.
*/
if (type == T_PROTECTED_LVALUE)
{
struct protected_lvalue *lvalue;
struct protected_char_lvalue *val;
lvalue = (struct protected_lvalue *)vec;
if (lvalue->v.u.lvalue->type != T_STRING)
{
/* Deref a non-string protected lvalue.
* If this is the lvalue passed to the operator, also free
* the protector structure (since its stack space will be
* used for the result), but keep the protector itself
* in a global variable.
*/
if (vec == sp->u.lvalue)
{
free_protector_svalue(&last_indexing_protector);
last_indexing_protector = lvalue->protector;
vec = lvalue->v.u.lvalue;
xfree(lvalue);
continue;
}
vec = lvalue->v.u.lvalue;
continue;
}
vec = lvalue->v.u.lvalue; /* it's a string... */
if (i->type != T_NUMBER || (ind = i->u.number) < 0)
{
ERROR("Illegal index for []: not a (positive) number.\n")
/* TODO: Print type and value of i */
return NULL;
}
if (ind >= svalue_strlen(vec) )
{
ERRORF(("Index for [] out of bounds: %ld, string length: %ld\n"
, (long)ind, (long)_svalue_strlen(vec)))
return NULL;
}
/* If the string is not allocated, ie changeable, allocate
* a copy we can change.
*/
if (vec->x.string_type != STRING_MALLOC)
{
char *p = string_copy(vec->u.string);
if (vec->x.string_type == STRING_SHARED)
free_string(vec->u.string);
vec->u.string = p;
}
/* Make the string 'VOLATILE' so that we have full control
* over its deallocation.
*/
vec->x.string_type = STRING_VOLATILE;
/* Build the protector */
val = (struct protected_char_lvalue *)xalloc(sizeof *val);
val->v.type = T_PROTECTED_CHAR_LVALUE;
val->v.u.string = &vec->u.string[ind];
val->lvalue = vec;
val->start = vec->u.string;
/* Drop the arguments and return the result.
* If this was the lvalue passed to the operator in the
* first place, adopt the protecting value and free the old
* operator structure. If not, just don't assign a protecting
* value.
*/
if (lvalue == sp->u.protected_lvalue)
{
val->protector = lvalue->protector;
xfree(lvalue);
}
else
{
val->protector.type = T_INVALID;
}
sp = i;
sp->type = T_LVALUE;
sp->u.protected_char_lvalue = val;
return sp;
}
/* Indexing on illegal type */
inter_sp = sp;
inter_pc = pc;
error("(lvalue)Indexing on illegal type.\n");
/* TODO: Print type */
return NULL;
} /* for(ever) */
/* NOTREACHED */
return NULL;
} /* protected_index_lvalue() */
/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
protected_rindex_lvalue (svalue_t *sp, bytecode_p pc)
/* Operator F_PROTECTED_RINDEX_LVALUE (string|vector &v=sp[0], int i=sp[-1])
*
* Compute the index &(*v[<i]) of lvalue <v>, wrap it into a protector, and
* push the reference to the protector as PROTECTED_LVALUE onto the stack.
*
* If <v> is a protected non-string-lvalue, the protected_lvalue referenced
* by <v>.u.lvalue will be deallocated, and the protector itself will be
* stored in <last_indexing_protector> for the time being.
*
* If <v> is a string-lvalue, it is made a malloced string if necessary.
*/
{
svalue_t *vec; /* the indexed value */
svalue_t *i; /* the index */
mp_int ind; /* numeric value of <i> */
short type; /* type of <vec> */
/* Get arguments */
vec = sp->u.lvalue;
i = sp -1;
if (i->type != T_NUMBER || (ind = i->u.number) <= 0)
{
ERROR("Illegal indexi for [<]: not a (positive) number.\n")
/* TODO: Print type and value of i */
return NULL;
}
/* The loop unravels the (possible) lvalue chain starting at vec.
* When a non-lvalue is encountered, the indexing takes place
* the function returns.
*/
for (;;)
{
type = vec->type;
/* Index a vector.
*/
if (type == T_POINTER)
{
vector_t *v = vec->u.vec;
struct protected_lvalue *lvalue;
if ( (ind = (mp_int)VEC_SIZE(v) - ind) < 0)
{
ERRORF(("Index for [<] out of bounds: %ld, vector size: %lu\n"
, (long)i->u.number, VEC_SIZE(v)))
return NULL;
}
/* Create the protector for the result */
lvalue = (struct protected_lvalue *)xalloc(sizeof *lvalue);
lvalue->v.type = T_PROTECTED_LVALUE;
lvalue->v.u.lvalue = &v->item[ind];
put_ref_array(&(lvalue->protector), v);
/* Drop the arguments and return the result */
sp = i;
sp->type = T_LVALUE;
sp->u.lvalue = &lvalue->v;
return sp;
}
/* Index a string.
*/
if (type == T_STRING)
{
struct protected_char_lvalue *val;
if ( (ind = (mp_int)svalue_strlen(vec) - ind) < 0)
{
ERRORF(("Index for [<] out of bounds: %ld, string length: %ld.\n"
, (long)i->u.number, (long)svalue_strlen(vec)))
return NULL;
}
/* If the string is not allocated, ie. changeable, allocate
* a new changeable copy.
*/
if (vec->x.string_type != STRING_MALLOC)
{
char *p = string_copy(vec->u.string);
if (vec->x.string_type == STRING_SHARED)
free_string(vec->u.string);
vec->u.string = p;
}
/* Make the string 'VOLATILE' so that we have full control
* over its deallocation.
*/
vec->x.string_type = STRING_VOLATILE;
/* Build the protector */
val = (struct protected_char_lvalue *)xalloc(sizeof *val);
val->v.type = T_PROTECTED_CHAR_LVALUE;
val->v.u.string = &vec->u.string[ind];
val->lvalue = vec;
val->start = vec->u.string;
val->protector.type = T_INVALID;
/* Drop the arguments and return the result */
sp = i;
sp->type = T_LVALUE;
sp->u.protected_char_lvalue = val;
return sp;
}
/* lvalues are just dereferenced.
*/
if (type == T_LVALUE)
{
vec = vec->u.lvalue;
continue;
}
/* Non-string protected lvalues are dereferenced, a protected
* string lvalue is indexed immediately.
*/
if (type == T_PROTECTED_LVALUE)
{
struct protected_lvalue *lvalue;
struct protected_char_lvalue *val;
lvalue = (struct protected_lvalue *)vec;
if (lvalue->v.u.lvalue->type != T_STRING)
{
/* Deref a non-string protected lvalue.
* If this is the lvalue passed to the operator, also free
* the protector structure (since its stack space will be
* used for the result), but keep the protector itself
* in a global variable.
*/
if (vec == sp->u.lvalue)
{
free_protector_svalue(&last_indexing_protector);
last_indexing_protector = lvalue->protector;
vec = lvalue->v.u.lvalue;
xfree(lvalue);
continue;
}
vec = lvalue->v.u.lvalue;
continue;
}
vec = lvalue->v.u.lvalue; /* it's a string... */
if ( (ind = (mp_int)svalue_strlen(vec) - ind) < 0)
{
ERRORF(("Index for [<] out of bounds: %ld, string length: %ld.\n"
, (long)i->u.number, (long)svalue_strlen(vec)))
return NULL;
}
/* If the string is not allocated, ie changeable, allocate
* a copy we can change.
*/
if (vec->x.string_type != STRING_MALLOC)
{
char *p = string_copy(vec->u.string);
if (vec->x.string_type == STRING_SHARED)
free_string(vec->u.string);
vec->u.string = p;
}
/* Make the string 'VOLATILE' so that we have full control
* over its deallocation.
*/
vec->x.string_type = STRING_VOLATILE;
/* Build the protector */
val = (struct protected_char_lvalue *)xalloc(sizeof *val);
val->v.type = T_PROTECTED_CHAR_LVALUE;
val->v.u.string = &vec->u.string[ind];
val->lvalue = vec;
val->start = vec->u.string;
/* Drop the arguments and return the result.
* If this was the lvalue passed to the operator in the
* first place, adopt the protecting value and free the old
* operator structure. If not, just don't assign a protecting
* value.
*/
if (lvalue == sp->u.protected_lvalue)
{
val->protector = lvalue->protector;
xfree(lvalue);
}
else
{
val->protector.type = T_INVALID;
}
sp = i;
sp->type = T_LVALUE;
sp->u.protected_char_lvalue = val;
return sp;
}
/* Indexing on illegal type */
inter_sp = sp;
inter_pc = pc;
error("(lvalue)Indexing on illegal type.\n");
/* TODO: Print the type */
return NULL;
} /* for(ever) */
/* NOTREACHED */
return NULL;
} /* protected_rindex_lvalue() */
/*-------------------------------------------------------------------------*/
static svalue_t *
range_lvalue (int code, svalue_t *sp)
/* Operator F_RANGE_LVALUE (string|vector &v=sp[0], int i2=sp[-1], i1=sp[-2])
* and the operators F_{NR,RN,RR}_RANGE_LVALUE.
*
* Compute the range &(v[i1..i2]) of lvalue <v> and push it into the stack.
* The value pushed is a lvalue pointint to <special_lvalue>. <special_lvalue>
* then is the POINTER_RANGE_- resp. STRING_RANGE_LVALUE.
*
* <code> is a two-byte flag determining whether the indexes are counted
* from the beginning ('[i1..' and '..i2]') or the the end of the vector
* or string ('[<i1..' and '..<i2]'). If <code>&0xff00 is TRUE, the lower
* index is counted from the end, if <code>&0x00ff is TRUE, the upper
* index is counted from the end.
* TODO: This code thingy is not really nice.
*/
{
svalue_t *vec; /* the indexed vector or string */
svalue_t *i; /* the index */
int ind1, ind2; /* Lower and upper range index */
short type; /* type of <vec> */
mp_int size; /* size of <vec> in elements */
/* Get the arguments */
vec = sp;
i = sp-1;
#ifdef DEBUG
if (sp->type != T_LVALUE) {
inter_sp = sp;
error("wrong type to range_lvalue\n");
/* TODO: Print type */
return NULL;
}
#endif
/* Deref the initial, and possibly more, lvalues.
*/
do {
vec = vec->u.lvalue;
type = vec->type;
} while (type == T_LVALUE || type == T_PROTECTED_LVALUE);
/* Determine the type of the result, and the input's size.
*/
switch(type)
{
case T_POINTER:
special_lvalue.v.type = T_POINTER_RANGE_LVALUE;
size = (mp_int)VEC_SIZE(vec->u.vec);
break;
case T_STRING:
special_lvalue.v.type = T_STRING_RANGE_LVALUE;
size = (mp_int)svalue_strlen(vec);
break;
default:
inter_sp = sp;
error("(lvalue)Range index on illegal type.\n");
/* TODO: Print type */
return NULL;
}
/* Get and check the upper bound i2 */
if (i->type != T_NUMBER)
{
inter_sp = sp;
error("Illegal upper range index: not a number.\n");
/* TODO: Print type */
return NULL;
}
if (code & 0xff)
{
ind2 = size - i->u.number;
}
else
{
ind2 = i->u.number;
}
if (++ind2 < 0 || ind2 > size)
{
inter_sp = sp;
error("Upper range index out of bounds: %ld, size: %ld.\n"
, (long)i->u.number, (long)size);
return NULL;
}
/* Get and check the lower bound i1 */
if ((--i)->type != T_NUMBER)
{
inter_sp = sp;
error("Illegal lower range index: not a number.\n");
/* TODO: Print the type */
return NULL;
}
if (code & 0xff00)
{
ind1 = size - i->u.number;
}
else
{
ind1 = i->u.number;
}
if (ind1 < 0 || ind1 > size)
{ /* Appending (ind1 == size) is allowed */
inter_sp = sp;
error("Lower range index out of bounds: %ld, size: %ld.\n"
, (long)i->u.number, (long)size);
return NULL;
}
/* Finish the special_lvalue structure
*/
special_lvalue.v.u.lvalue = vec;
special_lvalue.size = size;
special_lvalue.index1 = ind1;
special_lvalue.index2 = ind2;
/* Drop the arguments and return the result. */
sp = i;
sp->type = T_LVALUE;
sp->u.lvalue = &special_lvalue.v;
return sp;
} /* range_lvalue() */
/*-------------------------------------------------------------------------*/
static svalue_t *
protected_range_lvalue (int code, svalue_t *sp)
/* X-Operator F_PROTECTED_RANGE_LVALUE
* (string|vector &v=sp[0], int i2=sp[-1], i1=sp[-2])
* and the x-operators F_PROTECTED_{NR,RN,RR}_RANGE_LVALUE and
* F_PROTECTED_LVALUE.
*
* Compute the range &(v[i1..i2]) of lvalue <v>, wrap it into a protector,
* and push the reference to the protector onto the stack.
*
* If <v> is a protected lvalue itself, its protecting svalue will be used
* in the result protector.
*
* If <v> is a string-lvalue, it is made a malloced string if necessary.
*
* <code> is a two-byte flag determining whether the indexes are counted
* from the beginning ('[i1..' and '..i2]') or the the end of the vector
* or string ('[<i1..' and '..<i2]'). If <code>&0xff00 is TRUE, the lower
* index is counted from the end, if <code>&0x00ff is TRUE, the upper
* index is counted from the end.
* TODO: This code thingy is not really nice.
*/
{
svalue_t *vec; /* the indexed vector or string */
svalue_t *i; /* the index */
int ind1, ind2; /* Lower and upper range index */
short type; /* type of <vec> */
mp_int size; /* size of <vec> in elements */
short lvalue_type; /* Result type */
svalue_t protector; /* Protecting svalue saved from v */
struct protected_range_lvalue *new_lvalue;
/* Result protector structure */
#ifdef DEBUG
if (sp->type != T_LVALUE)
{
inter_sp = sp;
error("wrong type to protected_range_lvalue\n");
/* TODO: Print type */
return NULL;
}
#endif
/* Get the arguments, and also remember the protector in case v
* is a protected lvalue.
*/
vec = sp->u.lvalue; /* deref initial lvalue */
i = sp - 1;
type = vec->type;
if (type != T_PROTECTED_LVALUE)
protector.type = T_INVALID;
else
protector = ((struct protected_lvalue*)vec)->protector;
/* Deref any possibly following lvalues
*/
while (type == T_LVALUE || type == T_PROTECTED_LVALUE)
{
vec = vec->u.lvalue;
type = vec->type;
}
/* Determine the type of the result, and the input's size.
* Also massage the input value a bit.
*/
switch(type)
{
case T_POINTER:
(void)ref_array(vec->u.vec); /* Count the coming protector */
lvalue_type = T_PROTECTED_POINTER_RANGE_LVALUE;
size = (mp_int)VEC_SIZE(vec->u.vec);
break;
case T_STRING:
/* If the string is not allocated, ie changeable, allocate
* a copy we can change.
*/
if (vec->x.string_type != STRING_MALLOC)
{
char *p = string_copy(vec->u.string);
if (vec->x.string_type == STRING_SHARED)
free_string(vec->u.string);
vec->u.string = p;
}
/* Make the string 'VOLATILE' so that we have full control
* over its deallocation.
*/
vec->x.string_type = STRING_VOLATILE;
lvalue_type = T_PROTECTED_STRING_RANGE_LVALUE;
size = (mp_int)svalue_strlen(vec);
break;
default:
inter_sp = sp;
error("(lvalue)Range index on illegal type.\n");
/* TODO: Print type */
return NULL;
}
/* Get and check the upper index i2 */
if (i->type != T_NUMBER)
{
inter_sp = sp;
error("Illegal upper range index: not a number.\n");
/* TODO: Print type. */
return NULL;
}
if (code & 0xff)
{
ind2 = size - i->u.number;
}
else
{
ind2 = i->u.number;
}
if (++ind2 < 0 || ind2 > size) {
inter_sp = sp;
error("Upper range index out of bounds: %ld, size: %ld.\n"
, (long)i->u.number, (long)size);
return NULL;
}
/* Get and check the lower index i1 */
if ((--i)->type != T_NUMBER)
{
inter_sp = sp;
error("Illegal lower range index: not a number.\n");
/* TODO: Print type. */
return NULL;
}
if (code & 0xff00)
{
ind1 = size - i->u.number;
}
else
{
ind1 = i->u.number;
}
if (ind1 < 0 || ind1 > size)
{
/* Appending (ind1 == size) is allowed */
inter_sp = sp;
error("Lower range index out of bounds: %ld, size: %ld.\n"
, (long)i->u.number, (long)size);
return NULL;
}
/* Build the protector */
new_lvalue = (struct protected_range_lvalue *)xalloc(sizeof *new_lvalue);
new_lvalue->v.type = lvalue_type;
new_lvalue->v.u = vec->u;
new_lvalue->protector = protector;
new_lvalue->lvalue = vec;
new_lvalue->index2 = ind2;
new_lvalue->index1 = ind1;
new_lvalue->size = size;
/* Drop the arguments and return the result */
sp = i;
sp->type = T_LVALUE;
sp->u.protected_range_lvalue = new_lvalue;
return sp;
} /* protected_range_lvalue() */
/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
push_indexed_value (svalue_t *sp, bytecode_p pc)
/* Operator F_INDEX (string|vector v=sp[0], int i=sp[-1])
* F_INDEX (mapping v=sp[0], mixed i=sp[-1])
*
* Compute the value (v[i]) and push it onto the stack.
* If the value would be a destructed object, 0 is pushed onto the stack
* and the ref to the object is removed from the vector/mapping.
*
* Mapping indices may use <indexing_quickfix> for temporary storage.
*/
{
svalue_t *vec; /* the indexed value */
svalue_t *i; /* the index */
int ind; /* numeric value of <i> */
/* Get arguments */
i = sp;
vec = sp - 1;
switch (vec->type)
{
case T_STRING:
{
if (i->type != T_NUMBER || (ind = i->u.number) < 0)
{
ERROR("Illegal index for []: not a (positive) number.\n")
/* TODO: Print type and value */
return NULL;
}
/* Index the string */
if (ind >= _svalue_strlen(vec))
ind = 0;
else
ind = vec->u.string[ind];
/* Drop the args and return the result */
free_string_svalue(vec);
sp = vec;
put_number(sp, ind);
return sp;
}
case T_POINTER:
if (i->type != T_NUMBER || (ind = i->u.number) < 0)
{
ERROR("Illegal index for []: not a (positive) number.\n")
/* TODO: Print type and value */
return NULL;
}
/* Drop the arguments */
sp = vec;
if (ind < 0 || (size_t)ind >= VEC_SIZE(vec->u.vec))
{
ERRORF(("Index for [] out of bounds: %ld, vector size: %lu\n"
, (long)ind, VEC_SIZE(vec->u.vec)))
return NULL;
}
/* Assign the indexed element to the sp entry holding vec.
* Decrement the vector ref manually to optimize the case that
* this is the last ref to the vector.
*/
if (vec->u.vec->ref == 1)
{
svalue_t *p, tmp;
/* Copy the indexed element into <tmp>
*/
#if !defined(NO_INLINES) && defined(__GNUC__)
tmp = const0;
/* gcc complains about tmp being clobbered */
#endif
p = &vec->u.vec->item[ind];
if (destructed_object_ref(p))
{
free_svalue(p);
put_number(&tmp, 0);
}
else
{
tmp = *p;
}
/* Invalidate the old space of the result value and free
* the vector.
*/
p->type = T_INVALID;
free_array(vec->u.vec);
/* Return the result */
*sp = tmp;
return sp;
}
deref_array(vec->u.vec);
/* The vector continues to live: keep the refcount as it is
* and just assign the indexed element for the result.
*/
assign_checked_svalue_no_free(sp, &vec->u.vec->item[ind], sp, pc);
return sp;
case T_MAPPING:
{
svalue_t *item;
mapping_t *m;
m = vec->u.map;
if (!m->num_values)
{
inter_sp = sp;
inter_pc = pc;
error("(value)Indexing a mapping of width 0.\n");
return NULL;
}
/* Get the item */
item = get_map_value(m, i);
/* Drop the arguments */
free_svalue(i); /* must come before the free(m) in case i and m are
* identical: if not done, the following test for
* m->ref == 1 would not be possible.
*/
if (m->ref == 1)
{
/* Only one ref left to the mapping: rescue the indexed
* item in indexing_quickfix before the free(m) will deallocate
* it.
*/
assign_svalue (&indexing_quickfix, item);
item = &indexing_quickfix;
}
free_mapping(m);
/* Return the result */
sp = vec;
assign_checked_svalue_no_free(sp, item, sp, pc);
return sp;
}
default:
inter_sp = sp;
inter_pc = pc;
error("(value)Indexing on illegal type.\n");
/* TODO: Print type */
return NULL;
}
/* NOTREACHED */
return NULL;
} /* push_indexed_value() */
/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
push_rindexed_value (svalue_t *sp, bytecode_p pc)
/* Operator F_RINDEX (string|vector v=sp[0], int i=sp[-1])
*
* Compute the value (v[<i]) and push it onto the stack.
* If the value would be a destructed object, 0 is pushed onto the stack
* and the ref to the object is removed from the vector/mapping.
*/
{
svalue_t *vec; /* the indexed value */
svalue_t *i; /* the index */
mp_int ind; /* numeric value of <i> */
/* Get arguments */
i = sp;
vec = sp - 1;
if (i->type != T_NUMBER || (ind = i->u.number) <= 0)
{
ERROR("Illegal index for [<]: not a (positive) number.\n")
/* TODO: Print type and value */
return NULL;
}
switch (vec->type)
{
case T_STRING:
{
/* Index the string */
if ( (ind = (mp_int)_svalue_strlen(vec) - ind) < 0 )
ind = 0;
else
ind = vec->u.string[ind];
/* Drop the args and return the result */
free_string_svalue(vec);
sp = vec;
put_number(sp, ind);
return sp;
}
case T_POINTER:
/* Drop the arguments */
sp = vec;
if ( (ind = (mp_int)VEC_SIZE(vec->u.vec) - ind) < 0) {
ERRORF(("Index for [<] out of bounds: %ld, vector size: %lu\n"
, (long)i->u.number, VEC_SIZE(vec->u.vec)))
return NULL;
}
/* Assign the indexed element to the sp entry holding vec.
* Decrement the vector ref manually to optimize the case that
* this is the last ref to the vector.
*/
if (vec->u.vec->ref == 1)
{
svalue_t *p, tmp;
/* Copy the indexed element into <tmp>
*/
#if !defined(NO_INLINES) && defined(__GNUC__)
tmp = const0;
/* gcc complains about tmp being clobbered */
#endif
p = &vec->u.vec->item[ind];
if (destructed_object_ref(p))
{
free_svalue(p);
put_number(&tmp, 0);
}
else
{
tmp = *p;
}
/* Invalidate the old space of the result value and free
* the vector.
*/
p->type = T_INVALID;
free_array(vec->u.vec);
/* Return the result */
*sp = tmp;
return sp;
}
deref_array(vec->u.vec);
/* The vector continues to live: keep the refcount as it is
* and just assign the indexed element for the result.
*/
assign_checked_svalue_no_free(sp, &vec->u.vec->item[ind], sp, pc);
return sp;
default:
inter_sp = sp;
inter_pc = pc;
error("(value)Indexing on illegal type.\n");
/* TODO: Print type */
return NULL;
}
/* NOTREACHED */
return NULL;
} /* push_rindexed_value() */
/*=========================================================================*/
/*-------------------------------------------------------------------------*/
void m_indices_filter ( svalue_t *key
, svalue_t *data UNUSED
, void *extra /* is a svalue_t ** */ )
/* Filter function used by mapping:m_indices() to implement the
* m_indices() efun. It is here take advantage of the inline expansion
* of assign_svalue_no_free().
*
* <key> points to a key in a mapping, <extra> points to a svalue_t*
* pointing to a storage place. *key is assigned to **extra, *extra is
* incremented afterwards.
*/
{
#ifdef __MWERKS__
# pragma unused(data)
#endif
svalue_t **svpp = (svalue_t **)extra;
assign_svalue_no_free( (*svpp)++, key );
}
/*-------------------------------------------------------------------------*/
static void m_values_filter ( svalue_t *key UNUSED
, svalue_t *data
, void *extra /* is a struct mvf_info * */ )
/* Filter function used by efun m_values().
*
* <data> points to a data entry in a mapping, <extra> points to
* a struct mvf_info describing the amount of data to copy, and the
* target place. The <data> is copied to where <extra> points and <*extra>
* is updated.
*/
{
#ifdef __MWERKS__
# pragma unused(key)
#endif
struct mvf_info * vip = (struct mvf_info *)extra;
assign_svalue_no_free( vip->svp++, data + vip->num);
}
/*-------------------------------------------------------------------------*/
static void m_unmake_filter ( svalue_t *key
, svalue_t *data
, void *extra)
/* Filter function used by efun unmkmapping().
*
* <key>/<data> point to key and data entry in a mapping, <extra> points to
* a struct mvf_info describing the amount of data to copy, and the
* target place. The <keu> and <data> is copied to where <extra> points
* and <*extra> is updated.
*/
{
struct mvf_info * vip = (struct mvf_info *)extra;
int i;
assign_svalue_no_free(vip->svp->u.vec->item + vip->num, key);
for (i = 0; i < vip->width; i++)
assign_svalue_no_free(vip->svp[i+1].u.vec->item + vip->num, data+i);
vip->num++;
}
/*-------------------------------------------------------------------------*/
static program_t *
search_inherited (char *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, str, prg->name);
debug_message("%s num_inherited=%d\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, prg->inherit[i].prog->name);
}
#endif
/* Duplicate virtual inherits don't count */
if ( prg->inherit[i].inherit_type & INHERIT_TYPE_DUPLICATE )
continue;
if ( strcmp(str, prg->inherit[i].prog->name ) == 0 )
{
#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() */
/*-------------------------------------------------------------------------*/
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;
}
/*-------------------------------------------------------------------------*/
#ifdef DEBUG
static INLINE svalue_t *
find_value (int num)
/* Return the address of object-global variable number <num> in the
* current variable block.
*
* <num> is the index of the variable in the current object's variable
* array.
*/
{
if (num >= current_object->prog->num_variables) {
fatal("Illegal variable access %d(%d).\n",
num, current_object->prog->num_variables);
}
return ¤t_variables[num];
}
#else
#define find_value(num) (¤t_variables[(num)])
#endif
/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
find_virtual_value (int num)
/* For the virtually inherited variable <num> (given as index within
* the current object's variable block) return the address of the actual
* variable.
*
* If the program for this variable was inherited more than one time,
* this function returns the address of the corresponding variable svalue
* of the very first inheritance. If the program was inherited just once,
* this function is identical to find_value().
*
* TODO: It would be nicer if the driver would 'know' here which inherit
* TODO:: to use, either by giving the inherit index in the code, or
* TODO:: by putting a reference to the base instance in the struct
* TODO:: inherit.
*/
{
inherit_t *inheritp;
program_t *progp;
char *progpp; /* actually a program_t **, but some compilers... */
/* Set inheritp to the inherited program which defines variable <num>
*/
inheritp = current_prog->inherit;
while
( inheritp->variable_index_offset + inheritp->prog->num_variables <= num
|| inheritp->variable_index_offset > num)
{
inheritp++;
}
/* Get the index of the variable within the inherited program.
*/
num -= inheritp->variable_index_offset;
/* Set inheritp to the first instance of this inherited program.
* A cleaner, but slighly slower way to write the following segment
* is: for (inheritp = current_object->prog_inherit
* ; inheritp->prog != progp
* ; inheritp++) NOOP;
*/
progp = inheritp->prog;
progpp = (char *)¤t_object->prog->inherit->prog;
while (*(program_t **)progpp != progp)
progpp += sizeof(inherit_t);
inheritp = (inherit_t *)
(((PTRTYPE)(progpp)) - offsetof(inherit_t, prog));
/* Compute the actual variable address */
num += inheritp->variable_index_offset;
#ifdef DEBUG
if (!current_object->variables)
{
if (num)
fatal("%s Fatal: find_virtual_value() on object %p '%s' "
"w/o variables, num %d\n"
, time_stamp(), current_object, current_object->name, num);
else
error("%s Error: find_virtual_value() on object %p '%s' "
"w/o variables, num %d\n"
, time_stamp(), current_object, current_object->name, num);
}
#endif
return ¤t_object->variables[num];
/* TODO: Why not '¤t_variables[num]'? */
} /* find_virtual_value() */
/*-------------------------------------------------------------------------*/
char *
add_slash (char *str)
/* Prepend a slash ('/') in front of string <str> and return an allocated
* copy of the result.
*/
{
char *tmp;
tmp = xalloc(strlen(str)+2);
if (tmp) {
*tmp = '/';
strcpy(tmp+1,str);
}
return tmp;
} /* add_slash() */
/*-------------------------------------------------------------------------*/
static void
bad_arg_pc (int arg, int instr, svalue_t *sp, bytecode_p pc)
NORETURN;
static void
bad_arg_pc (int arg, int instr, svalue_t *sp, bytecode_p pc)
/* Argument number <arg> to instruction <instr> was "bad".
* Set inter_sp/pc to <sp>/<pc> and raise a runtime error with
* a descriptive message.
*
* TODO: It would be nice if the function would say why the argument was bad.
* TODO:: In general, the should be a generic error function which is used
* TODO:: even within The Large Switch - not this 'goto bad_arg_1;' crap.
*/
{
ERRORF(("Bad argument %d to %s()\n", arg, get_f_name(instr)))
/* NOTREACHED */
}
/*-------------------------------------------------------------------------*/
void
bad_efun_arg (int arg, int instr, svalue_t *sp)
/* Argument number <arg> to instruction <instr> was "bad".
*
* inter_sp is updated to <sp>, inter_pc is supposed to be correct (and
* pointing just after the opcode).
*
* If <instr> is negative, it is used as an offset to inter_pc to find
* the actual instruction code at <inter_pc>+<instr>.
*
* TODO: It would be nice if the function would say why the argument was bad.
* TODO: This function depends on the order and values of F_ESCAPE,
* TODO:: F_TEFUN and F_VEFUN.
*/
{
inter_sp = sp;
if (instr < 0)
{
/* Find and decode the actual instruction at the given offset */
bytecode_p pc = inter_pc + instr;
instr = *pc;
if (instr <= F_VEFUN)
instr = pc[1] | (instr << F_ESCAPE_BITS);
}
error("Bad argument %d to %s()\n", arg, get_f_name(instr));
}
/*-------------------------------------------------------------------------*/
void
bad_xefun_arg (int arg, svalue_t *sp)
/* Argument number <arg> to a xefun (fixed args) or tefun was "bad".
*
* inter_sp is updated to <sp>, inter_pc is supposed to be correct and
* pointing just after the opcode.
*
* This function is also defined to bad_efun_vararg().
*/
{
bad_efun_arg(arg, -2, sp);
}
/*-------------------------------------------------------------------------*/
void
bad_xefun_vararg (int arg, svalue_t *sp)
/* Argument number <arg> to a xefun (var. args) or vefun was "bad".
*
* inter_sp is updated to <sp>, inter_pc is supposed to be correct and
* pointing just after the opcode.
*/
{
bad_efun_arg(arg, -3, sp);
}
/*-------------------------------------------------------------------------*/
Bool
_privilege_violation (char *what, svalue_t *where, svalue_t *sp)
/* Call the mudlib to check for a privilege violation:
*
* master->privilege_violation(what, current_object, where)
*
* where <what> describes the type of the violation, and <where> is the
* data used in the violation. <sp> is the current stack setting.
*
* If the apply returns a positive number, the privilege is granted and
* the function returns TRUE.
* If the apply returns 0, the privilege is gently denied and the function
* returns FALSE.
* If the apply returns something else, or if the lfun doesn't exist,
* an error is raised.
*
* If the current_object is the master or simul_efun object, this function
* immediately returns TRUE.
*
* <inter_sp> is updated to <sp>, <inter_pc> is assumed to be correct.
*/
{
svalue_t *svp;
/* Trusted objects */
if (current_object == master_ob) return MY_TRUE;
if (current_object == simul_efun_object) return MY_TRUE;
/* Setup and call the lfun */
push_volatile_string(what);
push_valid_ob(current_object);
sp++;
assign_svalue_no_free(sp, where);
inter_sp = sp;
svp = apply_master(STR_PRIVILEGE, 3);
/* Is there a lfun to call? */
if (!svp || svp->type != T_NUMBER || svp->u.number < 0)
{
inter_sp = sp-3;
error("privilege violation: %s\n", what);
/* TODO: Print full args and types */
}
/* Return the result */
return svp->u.number > 0;
} /* _privilege_violation() */
/*-------------------------------------------------------------------------*/
Bool
privilege_violation4 ( char *what, object_t *whom
, char *how_str, int how_num
, svalue_t *sp)
/* Call the mudlib to check for a privilege violation:
*
* !whom:
* master->privilege_violation(what, current_object, how_str, how_num)
* whom && how_str:
* master->privilege_violation(what, current_object, whom, how_str)
* whom && !how_str:
* master->privilege_violation(what, current_object, whom, how_num)
*
* where <what> describes the type of the violation, and <whom>/<how_str>/
* <how_num> are data used in the violation. <sp> is the current stack setting.
*
* If the apply returns a positive number, the privilege is granted and
* the function returns TRUE.
* If the apply returns 0, the privilege is gently denied and the function
* returns FALSE.
* If the apply returns something else, or if the lfun doesn't exist,
* an error is raised.
*
* If the current_object is the master or simul_efun object, this function
* immediately returns TRUE.
*
* If the lfun doesn't exist, or returns anything else but a positive
* number, an error is raised.
*
* <inter_sp> is updated to <sp>, <inter_pc> is assumed to be correct.
*/
{
svalue_t *svp;
/* Trust these objects */
if (current_object == master_ob) return MY_TRUE;
if (current_object == simul_efun_object) return MY_TRUE;
/* Set up the lfun call */
push_volatile_string(what);
push_valid_ob(current_object);
if (!whom)
{
if (how_str)
push_volatile_string(how_str);
else
push_number(0);
push_number(how_num);
}
else
{
push_object(whom);
if (how_str)
push_volatile_string(how_str);
else
push_number(how_num);
}
inter_sp = sp;
svp = apply_master(STR_PRIVILEGE, 4);
/* Was it the proper lfun to call? */
if (!svp || svp->type != T_NUMBER || svp->u.number < 0)
{
inter_sp = sp-4;
error("privilege violation : %s\n", what);
/* TODO: Print full args and types */
}
/* Return the result */
return svp->u.number > 0;
}
/*-------------------------------------------------------------------------*/
#define privilege_violation(what, where) (\
inter_pc = pc,\
_privilege_violation(what, where, sp)\
)
/* Just as _privilege_violation(), just that it automatically uses
* local <sp> and <pc> and updates the inter_ variables from them.
*/
/*-------------------------------------------------------------------------*/
static Bool
strpref (const char *p, const char *s)
/* Return TRUE if string <s> begins with string <p>, FALSE if not.
* Used by the function trace_test().
*/
{
while (*p)
if (*p++ != *s++)
return MY_FALSE;
return MY_TRUE;
}
/*-------------------------------------------------------------------------*/
static Bool
trace_test (int b)
/* Test if tracing of the given option(s) <b> is allowed right now.
* The function tests the options <b> against what the current interactive
* requested, and if a trace_prefix is given, if the prefix matches the
* name of the current object.
*/
{
interactive_t *ip;
return current_interactive
&& O_SET_INTERACTIVE(ip, current_interactive)
&& (ip->trace_level & b)
&& (ip->trace_prefix == NULL
|| (current_object
&& strpref(ip->trace_prefix, current_object->name)))
;
} /* trace_test() */
/*-------------------------------------------------------------------------*/
static void
do_trace (char *msg, char *fname, char *post)
/* If not in a heartbeat, or if heartbeat tracing is allowed, generate
* a tracemessage of the form '<tracedepth> <msg> <objname> <fname> <post>'
* and print it to the player using add_message().
*
* Don't do anything if the current command_giver is not interactive.
*
* <obj_name> is filled in only if TRACE_OBJNAME is requested, else
* the empty string is used.
*/
{
char buf[10000];
char *objname;
if (!TRACEHB)
return;
objname = TRACETST(TRACE_OBJNAME)
? (current_object && current_object->name ? current_object->name
: "?")
: "";
sprintf(buf, "*** %d %*s %s %s %s%s", tracedepth, tracedepth, ""
, msg, objname, fname, post);
add_message(buf);
#ifdef DEBUG
add_message(message_flush);
#endif
} /* do_trace() */
/*-------------------------------------------------------------------------*/
static void
do_trace_call (fun_hdr_p funstart, Bool is_lambda)
/* Trace a call to the function starting at <funstart>.
*/
{
if (!++traceing_recursion || !TRACE_IS_INTERACTIVE()) /* Do not recurse! */
{
int save_var_ix_offset = variable_index_offset;
/* TODO: Might be clobbered, but where? */
/* Trace the function itself */
if (is_lambda)
do_trace("Call direct ", "lambda-closure", " ");
else
{
char *name;
memcpy(&name, FUNCTION_NAMEP(funstart), sizeof name);
do_trace("Call direct ", name, " ");
}
/* If requested, also trace the arguments */
if (TRACEHB)
{
if (TRACETST(TRACE_ARGS))
{
int i;
svalue_t *svp;
add_message(" with %d arguments: "
, FUNCTION_NUM_ARGS(funstart) & 0x7f);
svp = inter_fp;
for (i = (FUNCTION_NUM_ARGS(funstart) & 0x7f); --i >= 0; )
{
print_svalue(svp++);
add_message(" ");
}
}
add_message("\n");
}
variable_index_offset = save_var_ix_offset;
}
traceing_recursion--;
} /* do_trace_call() */
/*-------------------------------------------------------------------------*/
static void
do_trace_return (svalue_t *sp)
/* Trace the return from a function call; <sp> is the current stack pointer,
* pointing to the result.
*/
{
tracedepth--; /* We leave this level */
if (!++traceing_recursion || !TRACE_IS_INTERACTIVE())
{
if (trace_test(TRACE_RETURN))
{
inter_sp = sp;
do_trace("Return", "", "");
if (TRACEHB) {
if (TRACETST(TRACE_ARGS)) {
add_message(" with value: ");
print_svalue(sp);
}
add_message("\n");
}
}
}
traceing_recursion--;
/* If requested, (re)activate TRACE_EXEC */
SET_TRACE_EXEC;
}
/*-------------------------------------------------------------------------*/
struct longjump_s *
push_error_context (svalue_t *sp, bytecode_t catch_inst)
/* Create a catch recovery context, using <sp> as the stackpointer to save,
* link it into the recovery stack and return the longjmp context struct.
* The actual type of the catch context is chosen on the actual
* <catch_inst>ruction (CATCH or CATCH_NO_LOG).
*/
{
struct catch_context *p;
p = xalloc (sizeof *p);
p->save_sp = sp;
p->save_csp = csp;
p->save_command_giver = command_giver;
p->recovery_info.rt.last = rt_context;
p->recovery_info.rt.type
= (catch_inst == F_CATCH) ? ERROR_RECOVERY_CATCH
: ERROR_RECOVERY_CATCH_NOLOG;
rt_context = (rt_context_t *)&p->recovery_info;
return &p->recovery_info.con;
} /* push_error_context() */
/*-------------------------------------------------------------------------*/
void
pop_error_context (void)
/* Pop and discard the top entry in the error recovery stack, assuming
* that it's a catch recovery entry.
*
* This function is called when the catch() completed normally.
*/
{
struct catch_context *p;
p = (struct catch_context *)rt_context;
#ifdef DEBUG
if (!ERROR_RECOVERY_CAUGHT(p->recovery_info.rt.type))
fatal("Catch: runtime stack underflow");
if (csp != p->save_csp-1)
fatal("Catch: Lost track of csp");
/* Note: the command_giver might have changed (with the exec() efun),
* so testing it is of no use.
*/
#endif
rt_context = p->recovery_info.rt.last;
xfree(p);
} /* pop_error_context() */
/*-------------------------------------------------------------------------*/
svalue_t *
pull_error_context (svalue_t *sp)
/* Restore the context saved by a catch() after a throw() or runtime error
* occured. <sp> is the current stackpointer and is used to pop the elements
* pushed since the catch().
*
* The function pops the topmost recovery entry, which must be the catch
* recovery entry, restores the important global variables and returns
* the saved stack pointer.
*/
{
struct catch_context *p;
struct control_stack *csp2;
p = (struct catch_context *)rt_context;
if (!ERROR_RECOVERY_CAUGHT(p->recovery_info.rt.type))
fatal("Catch: runtime stack underflow");
/* If there was a call_other() or similar, previous_ob and current_object
* must be restored. For this, find the control frame where the call
* occured and get the proper values from there.
*/
csp2 = p->save_csp;
while (++csp2 <= csp)
{
if (csp2->extern_call)
{
previous_ob = csp2->prev_ob;
current_object = csp2->ob;
break;
}
}
/* Restore the global variables and the evaluator stack */
csp = p->save_csp;
pop_n_elems(sp - p->save_sp);
command_giver = p->save_command_giver;
/* Remove the context from the context stack */
rt_context = p->recovery_info.rt.last;
xfree(p);
return sp;
} /* pull_error_context() */
/*-------------------------------------------------------------------------*/
void
push_control_stack ( svalue_t *sp
, bytecode_p pc
, svalue_t *fp
)
/* Push the current execution context onto the control stack.
* On stack overflow, raise a 'too deep recursion' error.
*/
{
/* Check for overflow */
if (csp >= &CONTROL_STACK[MAX_USER_TRACE-1])
{
if (!num_error || csp == &CONTROL_STACK[MAX_TRACE-1])
{
ERROR("Too deep recursion.\n")
}
}
/* Move csp to the next entry and fill it with the current context
*/
csp++;
/* csp->funstart has to be set later, it is used only for tracebacks. */
csp->fp = fp;
csp->prog = current_prog;
csp->lambda = current_lambda; put_number(¤t_lambda, 0);
/* csp->extern_call = MY_FALSE; It is set by eval_instruction() */
csp->catch_call = MY_FALSE;
csp->pc = pc;
csp->function_index_offset = function_index_offset;
csp->current_variables = current_variables;
csp->break_sp = break_sp;
} /* push_control_stack() */
/*-------------------------------------------------------------------------*/
void
pop_control_stack (void)
/* Pop the last entry from the control stack and restore the execution
* context from it - except for extern_call of which the old value will
* be used immediately after the pop.
*/
{
#ifdef DEBUG
if (csp < CONTROL_STACK)
fatal("Popped out of the control stack");
#endif
if ( NULL != (current_prog = csp->prog) ) /* is 0 when we reach the bottom */
current_strings = current_prog->strings;
if (current_lambda.type == T_CLOSURE)
free_closure(¤t_lambda);
current_lambda = csp->lambda;
inter_pc = csp->pc;
inter_fp = csp->fp;
function_index_offset = csp->function_index_offset;
current_variables = csp->current_variables;
break_sp = csp->break_sp;
csp--;
} /* pop_control_stack() */
/*-------------------------------------------------------------------------*/
static INLINE funflag_t
setup_new_frame1 (int fx, int fun_ix_offs, int var_ix_offs)
/* Setup current_prog, function_ and variable_index_offset for a call
* to function index <fx> in the current program.
*
* <fun_ix_offs> and <var_ix_offs> are offsets to be added to the
* functions given offsets - this is necessary when <fx> is given relative
* to some inherited program and needs to be adjusted for the topmost
* program.
*
* Return the 'flags' for the function.
*/
{
program_t *progp;
funflag_t flags;
progp = current_prog;
flags = progp->functions[fx];
/* Handle a cross-define.
* This is a rather rare occasion and usually happens only with functions
* like heart_beat() which are called by function index and not by name.
* This index, determined at compile time, might point to the
* cross-defined function entry.
*/
if (flags & NAME_CROSS_DEFINED)
{
fx += CROSSDEF_NAME_OFFSET(flags);
flags = progp->functions[fx];
}
/* If the function is inherited, find the real function definition
* and adjust the offsets to point to its code and variables.
* This is an iteration walking along the inherit chain.
*/
fun_ix_offs += fx;
while(flags & NAME_INHERITED)
{
inherit_t *inheritp;
inheritp = &progp->inherit[flags & INHERIT_MASK];
progp = inheritp->prog;
fx -= inheritp->function_index_offset;
var_ix_offs += inheritp->variable_index_offset;
/* Remember here that function offset is relative to current_prog,
* but variable_offset is relative to current_object.
*/
flags = progp->functions[fx];
}
/* fx is now the 'pure' function index without any offsets */
/* Setup the variables and return */
current_prog = progp;
function_index_offset = fun_ix_offs - fx;
variable_index_offset = var_ix_offs;
return flags;
} /* setup_new_frame1() */
/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
setup_new_frame2 (fun_hdr_p funstart, svalue_t *sp
, Bool allowRefs, Bool is_lambda)
/* Before calling the function at <funstart>, massage the data on the
* stack ending at <sp> to match the formal argumentlist of the function
* (excessive args are removed, missing args are provided as 0),
* and allocate the local variables on the stack.
*
* <is_lambda> has to be TRUE if the function is a lambda closure.
* This information is needed for proper tracing.
*
* If <allowRefs> is TRUE, references may be passed as extended varargs
* ('(varargs mixed *)'). Currently this is used only for simul efuns.
* TODO: Investigate if holding references in arrays is really such a
* TODO:: a bad thing. Maybe it's just an implementation issue.
*
* csp->num_local_variables is supposed to hold the number of actual
* arguments on the stack.
*
* Result is the new stackpointer, the framepointer <inter_fp>,
* csp->num_local_variables and <break_sp> are set up.
*/
{
int i; /* Difference between number of formal and actual args;
* Number of (uninitialized) local variables
*/
int num_arg; /* Number of formal args */
/* Setup the frame pointer */
inter_fp = sp - csp->num_local_variables + 1;
/* (Re)move excessive arguments.
* TODO: This code uses that bit7 makes num_arg negative.
*/
num_arg = FUNCTION_NUM_ARGS(funstart);
if ((i = csp->num_local_variables - num_arg) > 0)
{
/* More actual than formal args, or the function has
* a 'varargs' argument.
*/
if (num_arg < 0)
{
/* Function has a 'varargs' argument */
num_arg &= 0x7f;
if ((i = csp->num_local_variables - num_arg + 1) < 0)
{
/* More formal than actual parameters. */
csp->num_local_variables = num_arg;
/* First, fill in zero for the rest... */
do {
*++sp = const0;
} while (++i);
/* ...and an empty array for the varargs portion */
++sp;
put_array(sp, allocate_uninit_array(0));
}
else
{
/* More actual than formal parameters */
vector_t *v;
csp->num_local_variables = num_arg;
/* Move the extra args into an array and put that
* onto the stack
*/
v = allocate_uninit_array(i);
while (--i >= 0)
{
if (!allowRefs && sp->type == T_LVALUE)
num_arg = -1; /* mark error condition */
v->item[i] = *sp--;
}
++sp;
put_array(sp, v);
if (num_arg < 0)
{
bytecode_p pc = funstart; /* for the ERROR() macro */
ERROR("Varargs argument passed by reference.\n");
}
}
}
else
{
/* Function takes a fixed number of arguments */
/* Pop the extraneous args */
do {
free_svalue(sp--);
csp->num_local_variables--;
} while(--i);
} /* if(varargs or fixedargs) */
/* Clear the local variables */
if ( 0 != (i = FUNCTION_NUM_VARS(funstart)) )
{
csp->num_local_variables += i;
do {
*++sp = const0;
} while (--i);
}
}
else
{
/* Enough or too little arguments supplied to a fixed-args
* function: initialize the missing args and the locals
* in one swoop.
*/
if ( 0 != (i = FUNCTION_NUM_VARS(funstart) - i) )
{
csp->num_local_variables += i;
do {
*++sp = const0;
} while (--i);
}
}
/* Check for stack overflow. Since the actual stack size is
* larger than EVALUATOR_STACK_SIZE, this check at the
* end should be sufficient. If not, stack_overflow() will
* generate a fatal error and we have to resize.
*/
if ( sp >= &VALUE_STACK[EVALUATOR_STACK_SIZE] )
stack_overflow(sp, csp->fp, funstart);
/* Count the call depth for traces and handle tracing */
tracedepth++;
if (TRACEP(TRACE_CALL) && TRACE_IS_INTERACTIVE())
{
inter_sp = sp;
do_trace_call(funstart, is_lambda);
}
/* Initialize the break stack, pointing to the entry above
* the first available svalue.
*/
break_sp = (bytecode_p *)&sp[1].u.string;
return sp;
} /* setup_new_frame2() */
/*-------------------------------------------------------------------------*/
static funflag_t
setup_new_frame (int fx)
/* Setup a call for function <fx> in the current program.
* Result are the flags for the function.
*/
{
funflag_t flags;
flags = setup_new_frame1(fx, 0, 0);
inter_sp = setup_new_frame2(
current_prog->program + (flags & FUNSTART_MASK), inter_sp
, MY_FALSE, MY_FALSE
);
#ifdef DEBUG
if (!current_object->variables && variable_index_offset)
fatal("%s Fatal: new frame for object %p '%s' w/o variables, "
"but offset %d\n"
, time_stamp(), current_object, current_object->name
, variable_index_offset);
#endif
current_variables = current_object->variables;
if (current_variables)
current_variables += variable_index_offset;
current_strings = current_prog->strings;
return flags;
}
/*-------------------------------------------------------------------------*/
void
reset_machine (Bool first)
/* Reset the virtual machine. <first> is true on the very first call
* (the cold boot, so to speak). Subsequent calls pass <first> as false
* and this way make sure that all values currently on the stack
* are properly removed.
*/
{
traceing_recursion = -1;
if (first)
{
csp = CONTROL_STACK - 1;
inter_sp = VALUE_STACK - 1;
tracedepth = 0;
put_number(¤t_lambda, 0);
}
else
{
inter_sp = _pop_n_elems(inter_sp - VALUE_STACK + 1, inter_sp);
if (current_lambda.type == T_CLOSURE)
free_closure(¤t_lambda);
put_number(¤t_lambda, 0);
while (csp >= CONTROL_STACK)
{
if (csp->lambda.type == T_CLOSURE)
free_closure(&csp->lambda);
csp--;
}
}
} /* reset_machine() */
/*-------------------------------------------------------------------------*/
#ifdef DEBUG
int
check_state (void)
/* Check the virtual machine for consistency. Return 0 when it is, else
* print a debug message and return an error code.
*
* As this function can be costly, it is by default not called from
* the backend loop.
*/
{
int rc;
rc = 0;
if (rt_context->type != ERROR_RECOVERY_BACKEND) {
debug_message("%s rt_context stack inconsistent: type %d instead of %d\n"
, time_stamp(), rt_context->type, ERROR_RECOVERY_BACKEND);
printf("%s rt_context stack inconsistent: type %d instead of %d\n"
, time_stamp(), rt_context->type, ERROR_RECOVERY_BACKEND);
if (!rc) rc = 1;
}
if (csp != CONTROL_STACK - 1) {
debug_message("%s csp inconsistent: %p instead of %p\n"
, time_stamp(), csp, CONTROL_STACK-1);
printf("%s csp inconsistent: %p instead of %p\n"
, time_stamp(), csp, CONTROL_STACK-1);
if (!rc) rc = 2;
}
if (inter_sp != VALUE_STACK - 1) {
debug_message("%s sp inconsistent: %p instead of %p\n"
, time_stamp(), inter_sp, VALUE_STACK - 1);
printf("%s sp inconsistent: %p instead of %p\n"
, time_stamp(), inter_sp, VALUE_STACK - 1);
if (!rc) rc = 3;
}
return rc;
}
#endif
/*-------------------------------------------------------------------------*/
void
free_interpreter_temporaries (void)
/* Free all svalue the interpreter holds in global variables.
* Usually the values are freed whenever a new value is stored, but
* this function allows e.g. the garbage collector to free them all
* at once.
#ifdef TRACE_CODE
* The function also cleans out all destructed objects from the
* instruction trace.
#endif
*/
{
free_protector_svalue(&last_indexing_protector);
last_indexing_protector.type = T_NUMBER;
free_svalue(&indexing_quickfix);
indexing_quickfix.type = T_NUMBER;
free_svalue(&apply_return_value);
apply_return_value.type = T_NUMBER;
#ifdef TRACE_CODE
{
int i;
for (i = TOTAL_TRACE_LENGTH; --i >= 0; )
{
object_t *ob;
if (NULL != (ob = previous_objects[i])
&& ob->flags & O_DESTRUCTED
)
{
free_object(ob, "free_interpreter_temporaries");
previous_objects[i] = NULL;
previous_instruction[i] = 0;
}
}
}
#endif
} /* free_interpreter_temporaries() */
/*-------------------------------------------------------------------------*/
void
remove_object_from_stack (object_t *ob)
/* Object <ob> was/will be destructed, so remove all references from
* to it from the stack, including references through closures.
*/
{
svalue_t *svp;
for (svp = VALUE_STACK; svp <= inter_sp; svp++)
{
if (get_object_ref(svp) == ob)
{
free_svalue(svp);
put_number(svp, 0);
}
} /* foreach svp in stack */
} /* remove_object_from_stack() */
/*-------------------------------------------------------------------------*/
Bool
eval_instruction (bytecode_p first_instruction
, svalue_t *initial_sp)
/* Evaluate the code starting at <first_instruction>, using <inital_sp>
* as the stack pointer. All other variables like current_prog must be
* setup before the call. The function will return upon encountering
* a F_RETURN instruction for which .extern_call or .catch_call is true,
* or upon encountering a F_END_CATCH instruction.
*
* The result will state the reason for returning: FALSE for F_RETURN,
* and TRUE for F_END_CATCH.
*
* This also means that for every intra-object call eval_instruction()
* is called recursively.
*
* There must not be destructed objects on the stack. The destruct_object()
* function will automatically remove all occurences. The effect is that
* all called efuns know that they won't have destructed objects as
* arguments.
*
* All instructions/functions callable from LPC must return a value or be
* declared void. This does not apply to internal control codes like F_JUMP.
*/
{
register bytecode_p pc; /* Current program pointer */
register svalue_t *fp; /* Current frame pointer */
register svalue_t *sp; /* Current stack pointer */
/* For speed reasons, these variables shadow their global counterparts,
* allowing more optimisations.
* gcc feels better about setjmp() when variables are declared register.
* Still we might get 'variable foo might be clobbered' warnings, but
* declaring them as volatile would degrade optimization, so we don't.
*/
int num_arg; /* Number of arguments given to the current instr */
int instruction; /* The current instruction code */
#ifdef DEBUG
svalue_t *expected_stack; /* Expected stack at the instr end */
#endif
/* Handy macros. Some of these are redefined later for multi-
* byte instructions.
*/
# ifdef DEBUG
# define GET_NUM_ARG \
if (num_arg != GET_UINT8(pc-1)) {\
fatal("Argument count error for %s: %d vs. %d.\n", get_f_name(instruction), num_arg, GET_UINT8(pc-1));}
/* The macro catches two faults: getting num_arg for instructions
* which don't take arguments, and getting num_arg after incrementing
* the pc too far.
*/
# else /* DEBUG */
# define GET_NUM_ARG num_arg = GET_UINT8(pc); inter_pc = ++pc;
# endif /* DEBUG */
/* Get and/or test the number of arguments.
*/
# define TYPE_TEST1(arg, t) if ( (arg)->type != t ) goto bad_arg_1;
# define TYPE_TEST2(arg, t) if ( (arg)->type != t ) goto bad_arg_2;
# define TYPE_TEST3(arg, t) if ( (arg)->type != t ) goto bad_arg_3;
# define TYPE_TEST4(arg, t) if ( (arg)->type != t ) goto bad_arg_4;
/* Test the type of a certain argument.
*/
# ifdef MARK
# define CASE(x) case (x): MARK(x);
# else
# define CASE(x) case (x):
# endif
/* Macro to build the case: labels for the evaluator switch.
* 'MARK' adds profiling support.
*/
/* Setup the variables.
* The next F_RETURN at this level will return out of eval_instruction().
*/
if (!csp->catch_call)
csp->extern_call = MY_TRUE;
sp = initial_sp;
pc = first_instruction;
fp = inter_fp;
SET_TRACE_EXEC;
/* ------ The evaluation loop ------ */
again:
/* Get the next instruction and increment the pc */
instruction = LOAD_CODE(pc);
/* If this a xcode, the second byte will be added later */
#if 0
printf("DEBUG: %p (%p): %d %s\n", pc-1, sp, instruction, get_f_name(instruction));
#endif
# ifdef TRACE_CODE
/* Store some vitals in the trace buffer */
# if TOTAL_TRACE_LENGTH & TOTAL_TRACE_LENGTH-1
if (++last == TOTAL_TRACE_LENGTH)
last = 0;
# else
last = (last+1) & (TOTAL_TRACE_LENGTH-1);
# endif
previous_instruction[last] = instruction;
previous_pc[last] = pc-1;
stack_size[last] = sp - fp - csp->num_local_variables;
abs_stack_size[last] = sp - VALUE_STACK;
if (previous_objects[last])
{
/* Need to free the previously stored object */
free_object(previous_objects[last], "TRACE_CODE");
}
previous_objects[last] = ref_object(current_object, "TRACE_CODE");
previous_programs[last] = current_prog;
# endif /* ifdef TRACE_CODE */
# ifdef MALLOC_LPC_TRACE
inter_pc = pc;
# endif
# ifdef OPCPROF
opcount[instruction]++;
# endif
/* If requested, trace the instruction.
* Print the name of the instruction, but guard against recursions.
*/
if (trace_exec_active && TRACE_EXEC_P && TRACE_IS_INTERACTIVE())
{
if (!++traceing_recursion)
{
inter_sp = sp;
do_trace("Exec ", get_f_name(instruction), "\n");
instruction = EXTRACT_UCHAR(pc-1);
}
traceing_recursion--;
}
/* Test the evaluation cost.
* eval_cost < 0 signify a wrap-around - unlikely, but with these crazy
* wizards everything is possible.
*/
++eval_cost;
if (max_eval_cost && (eval_cost >= max_eval_cost || eval_cost < 0))
{
rt_context_t * context;
/* Evaluation too long. Restore some globals and throw
* an error.
*/
printf("%s eval_cost too big %ld\n", time_stamp(), (long)eval_cost);
assign_eval_cost();
/* If the error isn't caught, reset the eval costs */
for (context = rt_context
; !ERROR_RECOVERY_CONTEXT(context->type)
; context = context->last
) NOOP;
if (context->type <= ERROR_RECOVERY_BACKEND)
{
CLEAR_EVAL_COST;
RESET_LIMITS;
}
inter_pc = pc;
inter_fp = fp;
ERROR("Too long evaluation. Execution aborted.\n")
}
#if defined(DEBUG)
/* Get the expected number of arguments and determined the expected
* stack setting.
*/
if (instrs[instruction].min_arg != instrs[instruction].max_arg)
{
num_arg = GET_UINT8(pc);
pc++;
}
else
{
/* Safety measure. It is supposed that the evaluator knows
* the number of arguments.
*/
num_arg = -1;
}
if (num_arg != -1)
{
expected_stack = sp - num_arg +
( instrs[instruction].ret_type == TYPE_VOID ? 0 : 1 );
}
else
{
expected_stack = NULL;
}
#endif /* DEBUG */
/* The monster switch to execute the instruction.
* The order of the cases is held (mostly) in the order
* the instructions appear in func_spec.
*/
inter_sp = sp;
inter_pc = pc;
/* TODO: This continual update is crude, but circumvents a lot
* TODO:: of situations where an error is thrown but inter_sp
* TODO:: is invalid (heck, every assign_svalue() could cause that). In
* TODO:: the long run, we should do this only for efuns (which are by
* TODO:: then hopefully all tabled).
*/
switch(instruction)
{
default:
fatal("Undefined instruction %s (%d)\n", get_f_name(instruction),
instruction);
/* NOTREACHED */
bad_arg_1: bad_arg_pc(1, instruction, sp, pc);
bad_arg_2: bad_arg_pc(2, instruction, sp, pc);
bad_arg_3: bad_arg_pc(3, instruction, sp, pc);
bad_arg_4: bad_arg_pc(4, instruction, sp, pc);
bad_left: ERRORF(("Bad left type to %s.\n", get_f_name(instruction)))
bad_right: ERRORF(("Bad right type to %s.\n", get_f_name(instruction)))
/* NOTREACHED */
return MY_FALSE; /* hint for data flow analysis */
#ifdef F_ILLEGAL
case 255:
CASE(F_ILLEGAL); /* --- illegal --- */
inter_pc = pc;
fatal("Illegal instruction\n");
/* NOTREACHED */
#endif /* F_ILLEGAL */
CASE(F_TEFUN); /* --- tefun <code> --- */
{
/* Call the tabled efun 0x100 + <code>, where <code> is
* a uint8 in the range 0x80..0xff.
* The efun takes a fixed number of arguments which are
* on the stack.
*/
int code;
code = LOAD_UINT8(pc);
#ifdef TRACE_CODE
previous_instruction[last] = code + 0x100;
#endif
#ifdef OPCPROF
opcount[code+0x100]++;
#endif
inter_sp = sp;
inter_pc = pc;
ASSIGN_EVAL_COST
sp = (*efun_table[code-128])(sp);
break;
}
CASE(F_VEFUN); /* --- vefun <code> <nargs> --- */
{
/* Call the tabled efun 0x200 + <code>, where <code> is
* a uint8 in the range 0x00..0x7f, with uint8 <nargs>
* arguments on the stack.
*/
int code;
int numarg;
code = LOAD_UINT8(pc);
numarg = LOAD_UINT8(pc);
#ifdef TRACE_CODE
previous_instruction[last] = code + 0x200;
#endif
#ifdef OPCPROF
opcount[code+0x200]++;
#endif
inter_sp = sp;
inter_pc = pc;
ASSIGN_EVAL_COST
sp = (*vefun_table[code])(sp, numarg);
break;
}
/* F_ESCAPE contains a sub-switch() and is handled at the
* end.
*/
/* --- Predefined functions with counterparts in LPC --- */
CASE(F_IDENTIFIER); /* --- identifier <var_ix> --- */
{
/* Push value of object variable <var_ix>.
* It is possible that it is a variable that points to
* a destructed object. In that case, it has to be replaced by 0.
*
* <var_ix> is a uint8.
*/
svalue_t * val = find_value((int)(LOAD_UINT8(pc)));
sp++;
assign_checked_svalue_no_free(sp, val, sp, pc);
break;
}
CASE(F_STRING); /* --- string <ix> --- */
{
/* Push the string current_strings[<ix>] onto the stack,
* <ix> being a (16-Bit) ushort, stored low byte first.
* See also the F_CSTRINGx functions.
*/
unsigned short string_number;
LOAD_SHORT(string_number, pc);
push_shared_string(current_strings[string_number]);
break;
}
CASE(F_CSTRING3); /* --- cstring3 <ix> --- */
{
/* Push the string current_strings[0x3<ix>] onto the stack.
* <ix> is a 8-Bit uint.
*/
push_shared_string(current_strings[LOAD_UINT8(pc)+0x300]);
break;
}
CASE(F_CSTRING2); /* --- cstring2 <ix> --- */
{
/* Push the string current_strings[0x2<ix>] onto the stack.
* <ix> is a 8-Bit uint.
*/
push_shared_string(current_strings[LOAD_UINT8(pc)+0x200]);
break;
}
CASE(F_CSTRING1); /* --- cstring1 <ix> --- */
{
/* Push the string current_strings[0x1<ix>] onto the stack.
* <ix> is a 8-Bit uint.
*/
push_shared_string(current_strings[LOAD_UINT8(pc)+0x100]);
break;
}
CASE(F_CSTRING0); /* --- cstring0 <ix> --- */
{
/* Push the string current_strings[0x0<ix>] onto the stack.
* <ix> is a 8-Bit uint.
*/
push_shared_string(current_strings[LOAD_UINT8(pc)]);
break;
}
CASE(F_NUMBER); /* --- number <num> --- */
{
/* Push the number <num> onto the stack.
* <num> is a p_int stored in the host format.
* See also the F_CONSTx functions.
* TODO: It should be rewritten to use the LOAD_ macros (but
* TODO:: then the compiler needs to use them, too.
*/
sp++;
sp->type = T_NUMBER;
memcpy(&sp->u.number, pc, sizeof sp->u.number);
pc += sizeof sp->u.number;
break;
}
CASE(F_NNUMBER); /* --- nnumber <num> --- */
{
/* Push the number -<num> onto the stack.
* <num> is a p_int stored in the host format.
* See also the F_CONSTx functions.
* TODO: It should be rewritten to use the LOAD_ macros (but
* TODO:: then the compiler needs to use them, too.
*/
sp++;
sp->type = T_NUMBER;
memcpy(&sp->u.number, pc, sizeof sp->u.number);
pc += sizeof sp->u.number;
sp->u.number = - sp->u.number;
break;
}
CASE(F_CONST0); /* --- const0 --- */
/* Push the number 0 onto the stack.
*/
push_number(0);
break;
CASE(F_CONST1); /* --- const1 --- */
/* Push the number 1 onto the stack.
*/
push_number(1);
break;
CASE(F_NCONST1); /* --- nconst1 --- */
/* Push the number -1 onto the stack.
*/
push_number(-1);
break;
CASE(F_CLIT); /* --- clit <num> --- */
{
/* Push the number <num> onto the stack.
* <num> is a 8-Bit uint.
*/
push_number((p_int)LOAD_UINT8(pc));
break;
}
CASE(F_NCLIT); /* --- nclit <num> --- */
{
/* Push the number -<num> onto the stack.
* <num> is a 8-Bit uint.
*/
push_number(-(p_int)LOAD_UINT8(pc));
break;
}
CASE(F_FLOAT); /* --- float <mant> <exp> --- */
{
/* Push the float build from <mant> (4 bytes) and <exp> (2 bytes)
* onto the stack. The binary format is the one determined
* by STORE_DOUBLE in datatypes.h
* TODO: This code makes heavy assumptions about data sizes and
* TODO:: layout. E.g. there need not be a 16-Bit integral type
* TODO:: available.
* TODO: It should be rewritten to use the LOAD_ macros (but
* TODO:: then the compiler needs to use them, too.
*/
#if SIZEOF_CHAR_P == 4
sp++;
sp->type = T_FLOAT;
memcpy((char *)&sp->u.mantissa, pc, sizeof(sp->u.mantissa));
memcpy((char *)&sp->x.exponent, pc + sizeof(sp->u.mantissa), sizeof(sp->x.exponent));
pc += sizeof(sp->u.mantissa)+sizeof(sp->x.exponent);
#else
int32 mantissa;
/* TODO: int16 */ short exponent;
sp++;
sp->type = T_FLOAT;
memcpy((char *)&mantissa, pc, sizeof(mantissa));
sp->u.mantissa = mantissa;
memcpy((char *)&exponent, pc + sizeof(mantissa), sizeof(exponent));
sp->x.exponent = exponent;
pc += sizeof(mantissa)+sizeof(exponent);
#endif
break;
}
CASE(F_CLOSURE); /* --- closure <ix> --- */
{
/* Push the closure value <ix> onto the stack.
* <ix> is a uint16, stored low byte first.
* Values 0xf000..0xffff are efun and simul-efun symbols, the others
* are operators and literals.
* Simul-efun symbols (0xf800..0xffff) and true efun symbolx (0xf000..
* 0xf7ff for which instrs[].Default >= 0) are made signed and stored
* as they are.
* Operator symbols (0xf000..0xf7ff for which instrs[].Default == -1)
* are moved into their 0xe800..0xefff range, then made signed and
* stored.
*/
/* TODO: uint16 */ unsigned short tmp_ushort;
/* TODO: int32 */ int ix;
LOAD_SHORT(tmp_ushort, pc);
ix = tmp_ushort;
if (ix < 0xf000)
{
sp++;
inter_sp = sp;
inter_pc = pc;
closure_literal(sp, ix);
/* If out of memory, this will set sp to svalue-0 and
* throw an error.
*/
}
else
{
sp++;
sp->type = T_CLOSURE;
sp->u.ob = ref_object(current_object, "closure");
if (ix >= CLOSURE_SIMUL_EFUN_OFFS)
{
/* Sefun closure */
sp->x.closure_type = (short)ix;
}
else
{
/* Efun or operator closure */
if (pragma_warn_deprecated
&& instrs[ix - CLOSURE_EFUN_OFFS].deprecated != NULL)
warnf("Warning: %s() is deprecated: %s\n"
, instrs[ix - CLOSURE_EFUN_OFFS].name
, instrs[ix - CLOSURE_EFUN_OFFS].deprecated
);
sp->x.closure_type
= (short)( instrs[ix - CLOSURE_EFUN_OFFS].Default == -1
? ix + CLOSURE_OPERATOR-CLOSURE_EFUN
: ix);
}
}
break;
}
CASE(F_SYMBOL); /* --- symbol <ix> <num> --- */
{
/* Push a symbol of current_strings[<ix>] with <num> quotes
* onto the stack.
* <ix> is a uint16, stored low byte first. <num> is a uint8.
*/
char *str;
/* TODO: uint16 */ unsigned short string_number;
LOAD_SHORT(string_number, pc);
sp++;
sp->type = T_SYMBOL;
sp->x.quotes = LOAD_UINT8(pc);
sp->u.string = str = ref_string(current_strings[string_number]);
break;
}
CASE(F_RETURN0); /* --- return0 --- */
/* Return from the function with result value 0.
*/
push_number(0);
/* FALLTHROUGH */
CASE(F_RETURN); /* --- return --- */
{
/* Return from the function with the result topmost on the stack.
* If this is an .extern_call, eval_instruction()
* is left here.
*/
svalue_t *svp; /* Save of current sp */
svp = sp;
/* Remove any intermediate error contexts */
while (csp->catch_call)
{
pop_control_stack();
pop_error_context();
}
/* Deallocate frame, but not the result value.
*/
#ifdef DEBUG
if (fp + csp->num_local_variables > sp)
fatal("Bad stack at F_RETURN, %ld values too low\n"
, (long)((fp + csp->num_local_variables) - sp));
else if (fp + csp->num_local_variables < sp)
fatal("Bad stack at F_RETURN, %ld values too high\n"
, (long)(sp - (fp + csp->num_local_variables)));
#endif
while (sp != fp)
{
free_svalue(--sp);
}
*sp = *svp;
/* Restore the previous execution context */
if ( NULL != (current_prog = csp->prog) ) /* is 0 when we reach the bottom */
current_strings = current_prog->strings;
function_index_offset = csp->function_index_offset;
current_variables = csp->current_variables;
break_sp = csp->break_sp;
if (current_lambda.type == T_CLOSURE)
free_closure(¤t_lambda);
current_lambda = csp->lambda;
if (csp->extern_call)
{
/* eval_instruction() must be left - setup the globals */
ASSIGN_EVAL_COST
current_object = csp->ob;
previous_ob = csp->prev_ob;
inter_pc = csp->pc;
inter_fp = csp->fp;
if (trace_level)
{
do_trace_return(sp);
if (csp == CONTROL_STACK - 2)
/* TODO: This can't be legal according to ISO C */
traceing_recursion = -1;
}
csp--;
inter_sp = sp;
return MY_FALSE;
}
/* We stay in eval_instruction() */
if (trace_level)
do_trace_return(sp);
pc = csp->pc;
fp = csp->fp;
csp--;
break;
}
CASE(F_BREAK); /* --- break --- */
{
/* Break out of a switch() by pulling the continuation address
* from the break stack.
*/
pc = *break_sp;
break_sp += sizeof(svalue_t)/sizeof(*break_sp);
break;
}
CASE(F_SWITCH); /* --- switch <lots of data...> --- */
{
/* The switch()-Statement: pop the topmost value from the stack,
* search it in the given case values and set the pc to the
* associated code. Also push the address of the next instruction
* as break address onto the break stack.
*
* The compiler makes sure that there is always a 'default' case
* and that all execution paths eventually execute a F_BREAK.
*
* The layout created by the LPC compiler is this:
*
* switch b1 a2 b2 [b3 [b4] ]
* instructions (sans the first byte 'i0')...
* l[]
* [c0 [c1]]
* a0 a1 i0
* v*n
* o*n
* [d0]
*
* b1 & 0x03 is 0, marking this switch statement as unaligned.
* Since for an efficient search the tables v*n and o*n must be
* 4-Byte aligned (TODO: on some machines 8-Byte), the interpreter
* will on first execution of such a switch align it (using
* closure:align_switch()) by arranging the bytes a0..a2 around
* the tables. The aligned layout is this:
*
* switch b1 b2 [b3 [b4] ]
* instructions...
* l[]
* [c0 [c1]] <-- p0 = pc + offset
* a0..
* v[] <-- tabstart
* o[] <-- end_tab = pc + offset + tablen
* ..a2 <-- p1
* [d0]
*
* b1 (bits 1..0) = len: the length in bytes needed to store
* 'offset', 'tablen', 'default offset', 'o*n' and the
* length of lookup tables for table ranges.
* b1 (bits 7..2) = tablen lo
* c0 = tablen mid (optional)
* c1 = tablen hi (optional)
* b2 = offset lo
* b3 = offset med (optional)
* b4 = offset hi (optional)
* a0, a1 = default-case offset lo and med in host byte order
* d0 = default-case offset hi (optional)
* a2 'type' (bits 0..4): start position for search (used to index
* a table with the real offsets)
* (bit 5) : 0: numeric switch , 1: string switch
* (bits 6..7): in an unaligned switch, the true value
* of <len> (b1, bits 1..0).
* l[]: range lookup table: each <len> bytes, network byte order
* (numeric switch only)
* v[]: case values, char* or p_int, host byte order
* o[]: case offsets : each <len> bytes, network byte order
*
* The case value table v[] holds (sorted numerically) all values
* appearing in the case statements, both singular values and range
* bounds. Range bound values (which are inclusive) always appear
* next to each other.
*
* The offset table o[] holds the associated offset with
* this interpretation:
*
* singular cases: jump destination offsets relative to pc.
*
* range cases: the 'offset' for the lower bound is 1, the
* offset for the upper bound gives the jump
* destination relative to pc.
*
* lookup ranges: the 'offset' for the lower bound is 0, the
* offset for the upper bound is an offset
* pointing into the lookup table.
* The real jump offset is then
* l[o[i] + <value> - lower-bound].
*
* The lookup ranges are used for an efficient implementation of
* sparse ranges like 'case 0: case 2: case 5: ...'.
*
* TODO: This code still makes too many un-macro'ed mem accesses.
*/
Bool useDefault; /* TRUE: Immediately jump to the default case */
mp_int offset; /* Length of instruction and range-table area */
mp_int def_offs; /* Offset to code for the 'default' case */
int tablen; /* Number of single case entries, multiplied by 4 */
int len; /* Number of bytes per offset/length value (1..3) */
int type; /* Start position for search */
static int32 off_tab[] = {
0*sizeof(char*), 0x00001*sizeof(char*), 0x00003*sizeof(char*),
0x00007*sizeof(char*), 0x0000f*sizeof(char*), 0x0001f*sizeof(char*),
0x0003f*sizeof(char*), 0x0007f*sizeof(char*), 0x000ff*sizeof(char*),
0x001ff*sizeof(char*), 0x003ff*sizeof(char*), 0x007ff*sizeof(char*),
0x00fff*sizeof(char*), 0x01fff*sizeof(char*), 0x03fff*sizeof(char*),
0x07fff*sizeof(char*), 0x0ffff*sizeof(char*), 0x1ffff*sizeof(char*),
0x3ffff*sizeof(char*), 0x7ffff*sizeof(char*)
};
/* Start offsets for the binary search for different table sizes.
* This table is indexed by <type> & 0x1f, and the compiler choses
* the start position to be the first power of 2 which is at least
* half the table size. This way the search algorithm only needs
* to check for the upper table end.
* TODO: Is the choice really so?
*/
bytecode_p p0;
/* Points after the range lookup tables (initially). */
bytecode_p p1;
/* Points to the table of offsets. */
bytecode_p tabstart;
/* Points to the 'v*n' table of cases */
bytecode_p end_tab;
/* Points to the 'o*n' table of offsets for the cases */
bytecode_p break_addr;
/* Address of the first bytecode after the switch, will be pushed
* onto the break stack.
*/
mp_int s;
/* Search value for the lookup, derived from the stack value.
* It is either u.number or the numeric value of u.string.
*/
/* TODO: opcode? */ unsigned char *l;
/* Current search pointer into the value table v[] */
mp_int r;
/* Current value retrieved from *<l> */
mp_int d;
/* Half the distance between <l> and the current upper resp. lower
* bound of the search partition
*/
/* TODO: opcode? */ unsigned char *p2;
/* For a found case, the pointer into o[] */
mp_int o0, o1;
/* The offsets read from *(p2-1) and *p2, resp. *p2 and *(p2+1) */
int i; /* Temporary */
/* Extract the basic tablen and len */
tablen = EXTRACT_UCHAR(pc);
if ( !(len = tablen & SWITCH_VALUELEN) )
{
/* Oops, first lets align the switch */
align_switch(pc);
tablen = EXTRACT_UCHAR(pc);
len = tablen & SWITCH_VALUELEN;
}
tablen &= ~SWITCH_VALUELEN;
/* SWITCH_TABLEN_SHIFT is 2, so don't need to do
* tablen = (tablen >> SWITCH_TABLEN_SHIFT) * 4
*/
/* Get the offset, aka the length of instruction and range table
* part, and let p0 point after them.
*/
offset = EXTRACT_UCHAR(pc+1);
if (len > 1)
{
offset += EXTRACT_UCHAR(pc+2) << 8;
if (len > 2)
{
offset += EXTRACT_UCHAR(pc+3) << 16;
}
}
p0 = pc + offset;
/* Get the full tablen, aka the number of single case entries,
* and set p1 to point _after_ the offset table 'o*n'.
* The computed formula is
*
* p1 = p0 + tablen * sizeof(char*) + tablen * len * sizeof(char)
* (length of v*n) (length of o*n)
*
* with the code taking into account that the _variable_ tablen
* already comes as 'tablen * sizeof(char*)'.
*
* TODO: This code assumes sizeof(char*) == 4.
*/
if (len > 1)
{
tablen += *(unsigned char *)(p0++) << 8;
if (len > 2)
{
tablen += *(unsigned char *)(p0++) << 16;
#if SIZEOF_CHAR_P == 4
p1 = (unsigned char *)(p0 + (tablen << 1) - (tablen >> 2));
}
else
{
p1 = (unsigned char *)(p0 + tablen + (tablen >> 1));
#else
p1 = (unsigned char *)(p0 + tablen + tablen*3/sizeof(p_int) );
}
else
{
p1 = (unsigned char *)(p0 + tablen + tablen*2/sizeof(p_int) );
#endif
}
}
else
{
p1 = (unsigned char *)(p0 + tablen + tablen / sizeof(p_int) );
}
/* Gather the 'default offset' and the 'type' from the alignment
* bytes before v[] (pointer to by p0) and the bytes after
* o[] (pointed to by p1).
* Set 'tabstart' to the real start of 'v*n'.
* Set 'break_addr' to the first instruction after the switch.
*/
{
int a, b;
union { unsigned char b[sizeof(p_int)-1]; short s; } abuf;
/* TODO: Assumes sizeof(p_int)-1 >= sizeof(short) */
/* TODO: Assumes sizeof(p_int) == 4 */
/* TODO: Assumes sizeof(short) == 2 */
/* Gather the bytes a0..a2 into abuf.b[] */
b = (int)(((p_int)p0-1) & sizeof abuf.b);
/* The number of a-bytes after 'o*n' */
memcpy((char *)abuf.b, p0, sizeof abuf.b);
a = (int)(sizeof abuf.b - b);
/* The number of remaining bytes */
memcpy((char *)(abuf.b + a), (char *)(p1 + a), (size_t)b);
def_offs = abuf.s;
type = abuf.b[2];
if (len > 2)
{
def_offs += p1[3] << 16;
break_addr = p1 + sizeof(p_int);
}
else
{
break_addr = p1 + sizeof(p_int)-1;
}
tabstart = p0 + a;
}
/* Set 'end_tab' to point to the 'o*n' table,
* push the break address onto the break stack.
*/
end_tab = tabstart + tablen;
break_sp -= sizeof(svalue_t)/sizeof(*break_sp);
*break_sp = break_addr;
/* Get the search value from the argument passed on the
* stack. This also does the type checking.
*/
useDefault = MY_FALSE;
if (type & SWITCH_TYPE)
{
/* String switch */
if ( sp->type == T_NUMBER && !sp->u.number )
{
/* Special case: uninitialized string '0'.
* Use a magic value for this one.
*/
s = (mp_int)ZERO_AS_STR_CASE_LABEL;
}
else if ( sp->type == T_STRING )
{
/* The case strings in the program shared, so whatever
* string we get on the stack, it must at least have
* a shared twin to be sensible. Get the address of
* that twin.
*/
switch(sp->x.string_type)
{
case STRING_SHARED:
s = (mp_int)sp->u.string;
break;
default:
s = (mp_int)findstring(sp->u.string);
break;
}
}
else
{
/* Non-string value for string switch: use default */
useDefault = MY_TRUE;
s = 0;
}
}
else if (sp->type == T_NUMBER)
{
/* Numeric switch and number given */
s = sp->u.number;
}
else
{
/* Non-number value for numeric switch: use default */
useDefault = MY_TRUE;
s = 0;
}
pop_stack();
if (useDefault)
{
o1 = def_offs;
}
else
{
/* Setup the binary search:
* l points roughly into the middle of the table,
* d is 1/4 of the (assumed) total size of the table
*/
i = type & SWITCH_START;
l = tabstart + off_tab[i];
d = (mp_int)((off_tab[i]+sizeof(p_int)) >> 1 & ~(sizeof(p_int)-1));
/* '+sizeof()' to make the off_tab[] value even and non-0 */
/* Binary search for the value <s> in the table, starting at
* position <l> and first subdivision size <d>.
* The algorithm runs until <d> falls below the size of a case value
* (sizeof(p_int)).
*
* After the loop terminates, o1 will be the jump offset relative
* to the pc, which might be the 'default' offset if the value <s>
* was not found.
*/
for(;;)
{
r = *(p_int*)l; /* Get the case value */
if (s < r)
{
/* --- s < r --- */
if (d < (mp_int)sizeof(p_int))
{
if (!d)
{
/* End of search: s not found.
*
* Set p2 to the offset matching <l> and retrieve
* o0 and o1 from there.
*
* s might still be in a range, then <l>/<p2> point to
* the entries for the upper bound.
*/
p2 = tabstart + tablen
+ ((p_int*)l - (p_int*)tabstart)*len;
o0 = EXTRACT_UCHAR(p2-1);
o1 = EXTRACT_UCHAR(p2);
if (len > 1)
{
o0 += EXTRACT_UCHAR(p2-2) << 8;
o1 = EXTRACT_UCHAR(p2+1) + (o1 << 8);
if (len > 2)
{
o0 += EXTRACT_UCHAR(p2-3) << 16;
o1 = EXTRACT_UCHAR(p2+2) + (o1 << 8);
}
}
/* Because the pre-table alignment area is in the
* indexing underflow memory region, we can't make
* useful predictions on the peeked o0 value in case
* of underflow.
*/
/* Test for a range */
if (o0 <= 1 && l > tabstart)
{
/* No indexing underflow: test if s is in range */
r = ((p_int*)l)[-1]; /* the lower bound */
if (s >= r)
{
/* s is in the range */
if (!o0)
{
/* Look up the real jump offset */
l = pc + o1 + (s-r) * len;
o1 = 0;
i = len;
do {
o1 = (o1 << 8) + *l++;
} while (--i);
break;
}
/* o1 holds jump destination */
break;
}
/* s is not in the range */
}
/* <s> not found at all: use 'default' address */
o1 = def_offs;
/* o1 holds jump destination */
break;
} /* if (!d) */
/* Here is 0 < d < sizeof(p_int).
* Set d = 0 and finish the loop in the next
* iteration.
* TODO: Why the delay?
*/
d = 0;
}
else
{
/* Move <l> down and half the partition size <d>. */
l -= d;
d >>= 1;
}
}
else if (s > r)
{
/* --- s > r --- */
if (d < (mp_int)sizeof(p_int))
{
if (!d)
{
/* End of search: s not found.
*
* Set p2 to the offset matching <l> and retrieve
* o0 and o1 from there.
*
* s might still be in a range, then <l> points to
* the entry of the lower bound, and <p2> is set to
* the entry for the upper bound.
*/
p2 = tabstart + tablen
+ (((p_int*)l - (p_int*)tabstart) + 1)*len;
o0 = EXTRACT_UCHAR(p2-1);
o1 = EXTRACT_UCHAR(p2);
if (len > 1)
{
o0 += EXTRACT_UCHAR(p2-2) << 8;
o1 = EXTRACT_UCHAR(p2+1) + (o1 << 8);
if (len > 2)
{
o0 += EXTRACT_UCHAR(p2-3) << 16;
o1 = EXTRACT_UCHAR(p2+2) + (o1 << 8);
}
}
/* Test for a range */
if (o0 <= 1)
{
/* It is a range. */
if (s <= ((p_int*)l)[1])
{
/* s is in the range, and r is already correct
* (ie the upper bound)
*/
if (!o0)
{
/* Lookup the real jump offset */
l = pc + o1 + (s-r) * len;
o1 = 0;
i = len;
do {
o1 = (o1 << 8) + *l++;
} while (--i);
break;
}
/* o1 holds jump destination */
break;
}
/* s is not in the range */
}
/* <s> not found at all: use 'default' address */
o1 = def_offs;
/* o1 holds jump destination */
break;
} /* !d */
/* Here is 0 < d < sizeof(p_int).
* Set d = 0 and finish the loop in the next
* iteration.
* TODO: Why the delay?
*/
d = 0;
}
else
{
/* Move <l> up, and half the partition size <d>
* If this would push l beyond the table, repeat the
* steps 'move <l> down and half the partition size'
* until <l> is within the table again.
*/
l += d;
while (l >= end_tab)
{
d >>= 1;
if (d <= (mp_int)sizeof(p_int)/2)
{
/* We can't move l further - finish the loop */
l -= sizeof(p_int);
d = 0;
break;
}
l -= d;
}
d >>= 1;
}
}
else
{
/* --- s == r --- */
/* End of search: s found.
*
* Set p2 to the offset matching <l> and retrieve
* o0 and o1 from there.
*
* We don't distinguish between a singular case match
* and a match with an upper range bound, but we have
* to take extra steps in case <s> matched a lower range
* bound. In that light, o0 need not be an exact value.
*/
p2 = tabstart + tablen + ((p_int*)l - (p_int*)tabstart)*len;
o0 = EXTRACT_UCHAR(p2-1);
o1 = EXTRACT_UCHAR(p2);
if (len > 1)
{
o0 |= EXTRACT_UCHAR(p2-2);
o1 = EXTRACT_UCHAR(p2+1) + (o1 << 8);
if (len > 2)
{
o0 |= EXTRACT_UCHAR(p2-3);
o1 = EXTRACT_UCHAR(p2+2) + (o1 << 8);
}
}
/* Test if <s> matched the end of a range with a lookup table.
*/
/* TODO: Does this mean that the compiler never creates
* TODO:: an ordinary range at the beginning of v[]?
*/
if (!o0 && l > tabstart)
{
r = ((p_int*)l)[-1]; /* the lower bound */
l = pc + o1 + (s-r) * len;
o1 = 0;
i = len;
do
{
o1 = (o1 << 8) + *l++;
} while (--i);
/* o1 holds jump destination */
break;
}
/* Test if <s> matched the start of a range */
if (o1 <= 1)
{
/* Yup. Realign p2 and reget o1 */
p2 += len;
/* Set l to point to the jump offset */
if (o1)
{
/* start of ordinary range */
l = p2;
}
else
{
/* start of range with lookup table */
i = len;
do {
o1 = (o1 << 8) + *p2++;
} while (--i);
l = pc + o1;
}
/* Get the jump offset from where <l> points */
o1 = 0;
i = len;
do {
o1 = (o1 << 8) + *l++;
} while (--i);
/* o1 holds jump destination */
break;
}
/* At this point, s was a match with a singular case, and
* o1 already holds the jump destination.
*/
break;
}
} /* binary search */
} /* if (useDefault) */
/* o1 is now the offset to jump to. */
pc += o1;
break;
}
CASE(F_SSCANF); /* --- sscanf <numarg> --- */
{
/* EFUN sscanf()
*
* int sscanf(string str, string fmt, mixed var1, mixed var2, ...)
*
* Scanf <str> according to <fmt> and store the resultes in var1...
* The compiler knows that var1... have to be passed as lvalues.
*
* Result is the number of variables assigned.
*/
int i;
num_arg = LOAD_UINT8(pc);
/* GET_NUM_ARG doesn't work here. Trust me. */
inter_sp = sp;
inter_pc = pc;
i = e_sscanf(num_arg, sp);
pop_n_elems(num_arg-1);
free_svalue(sp);
put_number(sp, i);
break;
}
#ifdef F_PARSE_COMMAND
CASE(F_PARSE_COMMAND); /* --- parse_command <numargs> --- */
{
/* EFUN parse_command()
*
* int parse_command(string cmd, object|object* objs
* , string fmt, mixed var1, mixed var2...)
*
* Parse the command <cmd> against <objs> and the format <fmt>
* and assign the parsed values to variables var1....
* The compiler knows that var1... have to be passed as lvalues.
*
* Result is TRUE if the pattern matches, and FALSE if not.
*/
int i;
svalue_t *arg;
assign_eval_cost();
num_arg = LOAD_UINT8(pc);
/* GET_NUM_ARG doesn't work here either. */
arg = sp - num_arg + 1;
if (arg[0].type != T_STRING)
goto bad_arg_1;
if (arg[1].type != T_OBJECT && arg[1].type != T_POINTER)
goto bad_arg_2;
if (arg[2].type != T_STRING)
goto bad_arg_3;
if (arg[1].type == T_POINTER)
check_for_destr(arg[1].u.vec);
inter_sp = sp;
inter_pc = pc;
if (compat_mode)
i = e_old_parse_command(arg[0].u.string, &arg[1], arg[2].u.string
, &arg[3], num_arg-3);
else
i = e_parse_command(arg[0].u.string, &arg[1], arg[2].u.string
, &arg[3], num_arg-3);
pop_n_elems(num_arg); /* Get rid of all arguments */
push_number(i ? 1 : 0); /* Push the result value */
break;
}
#endif /* PARSE_COMMAND */
CASE(F_LOCAL); /* --- local <ix> --- */
{
/* Fetch the value of local variable <ix> and push it
* onto the stack.
*/
svalue_t * new_fp = fp + LOAD_UINT8(pc);
sp++;
assign_local_svalue_no_free(sp, new_fp, sp, pc);
break;
}
CASE(F_CATCH); /* --- catch <offset> <guarded code> --- */
CASE(F_CATCH_NO_LOG); /* --- catch_no_log <offset> <guarded code> --- */
{
/* catch(...instructions...)
* catch_nolog(...instructions...)
*
* Execute the instructions (max. uint8 <offset> bytes) following the
* catch statement. If an error occurs, or a throw() is executed,
* catch that exception, push the <catch_value> (a global var)
* onto the stack and continue execution at instruction
* <pc>+1+<offset>.
*
* The implementation is such that a control-stack entry is created
* as if the instructions following catch are called as a subroutine
* from <pc>+1+<offset>. Additionally an appropriate error context
* is pushed. This way the error handling will have the VM 'return'
* to the right place automatically.
*
* The last instruction of the guarded code is F_END_CATCH which
* will clean up the control and error stack.
*
* If the actual guarded code is longer than 256 Bytes, the compiler
* will generate appropriate branches:
*
* catch 2
* branch guarded_code
* branch continuation
* guarded_code: ...
*/
uint offset;
/* Get the offset to the next instruction after the CATCH statement.
*/
offset = LOAD_UINT8(pc);
/* Save the important variables in their global locations */
inter_pc = pc;
inter_sp = sp;
inter_fp = fp;
/* Perform the catch() */
if (!catch_instruction(instruction, offset
#ifdef __INTEL_COMPILER
, (svalue_t ** volatile) &inter_sp
#else
, (volatile svalue_t ** volatile) &inter_sp
#endif
, inter_pc, inter_fp))
{
return MY_FALSE; /* Guarded code terminated with 'return' itself */
}
/* Restore the important variables */
pc = inter_pc;
sp = inter_sp;
fp = inter_fp;
/* Not really necessary, but tells gcc to complain less */
instruction = F_CATCH;
num_arg = -1;
#ifdef DEBUG
expected_stack = NULL;
#endif
break;
}
CASE(F_INC); /* --- inc --- */
{
/* void inc (mixed & sp[0])
*
* Increment the (numeric) value designed by the lvalue on top
* of the stack, then remove the lvalue from the
* stack (not free()!, this lvalue is just a copy).
*/
svalue_t *svp;
/* Get the designated value */
#ifdef DEBUG
if (sp->type != T_LVALUE)
ERROR("Bad argument to ++: not a lvalue.\n")
/* TODO: Give value and type */
#endif
svp = sp->u.lvalue;
/* Now increment where we can */
if (svp->type == T_NUMBER)
{
if (svp->u.number == PINT_MAX)
{
ERRORF(("Numeric overflow: (%ld)++\n", (long)svp->u.number));
/* NOTREACHED */
break;
}
svp->u.number++;
sp--;
break;
}
else if (svp->type == T_FLOAT)
{
STORE_DOUBLE_USED
double d;
d = READ_DOUBLE(svp) + 1.0;
if (d < (-DBL_MAX) || d > DBL_MAX)
ERRORF(("Numeric overflow: (%g)++\n", READ_DOUBLE(svp)));
sp->type = T_FLOAT;
STORE_DOUBLE(svp, d);
sp--;
break;
}
else if (svp->type == T_CHAR_LVALUE)
{
if ((unsigned char)(*svp->u.string) == 0xff /* TODO: MAX_CHAR */ )
ERROR("Can't set string character to 0.\n")
else
(*svp->u.string)++;
sp--;
break;
}
else if (svp->type == T_LVALUE
|| svp->type == T_PROTECTED_LVALUE)
{
inter_sp = sp;
add_number_to_lvalue(svp, 1, NULL, NULL);
sp--;
break;
}
ERROR("++ of non-numeric argument\n")
/* TODO: Give type and value */
break;
}
CASE(F_DEC); /* --- dec --- */
{
/* void dec (mixed & sp[0])
*
* Decrement the (numeric) value designed by the lvalue on top
* of the stack, then remove the lvalue from the
* stack (not free()!, this lvalue is just a copy).
*/
svalue_t *svp;
/* Get the designated value */
#ifdef DEBUG
if (sp->type != T_LVALUE)
ERROR("Bad argument to --: not a lvalue.\n")
/* TODO: Give type and value */
#endif
svp = sp->u.lvalue;
/* Now decrement where we can */
if (svp->type == T_NUMBER)
{
if (svp->u.number == PINT_MIN)
{
ERRORF(("Numeric overflow: (%ld)--\n", (long)svp->u.number));
/* NOTREACHED */
break;
}
svp->u.number--;
sp--;
break;
}
else if (svp->type == T_FLOAT)
{
STORE_DOUBLE_USED
double d;
d = READ_DOUBLE(svp) - 1.0;
if (d < (-DBL_MAX) || d > DBL_MAX)
ERRORF(("Numeric overflow: (%g)--\n", READ_DOUBLE(svp)));
sp->type = T_FLOAT;
STORE_DOUBLE(svp, d);
sp--;
break;
}
else if (svp->type == T_CHAR_LVALUE)
{
if ((unsigned char)(*svp->u.string) == 0x01)
ERROR("Can't set string character to 0.\n")
else
(*svp->u.string)--;
sp--;
break;
}
else if (svp->type == T_LVALUE
|| svp->type == T_PROTECTED_LVALUE)
{
inter_sp = sp;
add_number_to_lvalue(svp, -1, NULL, NULL);
sp--;
break;
}
ERROR("-- of non-numeric argument\n")
/* TODO: Give type and value */
break;
}
CASE(F_POST_INC); /* --- post_inc --- */
{
/* mixed post_inc (mixed & sp[0])
*
* Increment the numeric value designated by the lvalue on top
* of the stack, and replace the stack entry with the value
* before the increment. The lvalue itself is simply removed, not
* free()d.
*/
svalue_t *svp;
/* Get the designated value */
#ifdef DEBUG
if (sp->type != T_LVALUE)
ERROR("Bad argument to ++: not a lvalue.\n")
/* TODO: Give the type and value */
#endif
svp = sp->u.lvalue;
/* Do the push and increment */
if (svp->type == T_NUMBER)
{
if (svp->u.number == PINT_MAX)
{
ERRORF(("Numeric overflow: (%ld)++\n", (long)svp->u.number));
/* NOTREACHED */
break;
}
put_number(sp, svp->u.number++ );
break;
}
else if (svp->type == T_FLOAT)
{
STORE_DOUBLE_USED
double d;
d = READ_DOUBLE(svp);
sp->type = T_FLOAT;
STORE_DOUBLE(sp, d);
d += 1.0;
if (d < (-DBL_MAX) || d > DBL_MAX)
ERRORF(("Numeric overflow: (%g)++\n", READ_DOUBLE(svp)));
STORE_DOUBLE(svp, d);
break;
}
else if (svp->type == T_CHAR_LVALUE)
{
if ((unsigned char)(*svp->u.string) == 0xff /* TODO: MAX_CHAR */ )
ERROR("Can't set string character to 0.\n")
else
put_number(sp, (*svp->u.string)++ );
break;
}
else if (svp->type == T_LVALUE
|| svp->type == T_PROTECTED_LVALUE)
{
inter_sp = sp;
add_number_to_lvalue(svp, 1, sp, NULL);
break;
}
ERROR("++ of non-numeric argument\n")
/* TODO: Give type and value */
break;
}
CASE(F_POST_DEC); /* --- post_dec --- */
{
/* mixed post_dec (mixed & sp[0])
*
* Decrement the numeric value designated by the lvalue on top
* of the stack, and replace the stack entry with the value
* before the decrement. The lvalue itself is simply removed, not
* free()d.
*/
svalue_t *svp;
/* Get the designated value */
#ifdef DEBUG
if (sp->type != T_LVALUE)
ERROR("Bad argument to --: not a lvalue.\n")
/* TODO: Give the type and value */
#endif
svp = sp->u.lvalue;
/* Do the push and decrement */
if (svp->type == T_NUMBER)
{
if (svp->u.number == PINT_MIN)
{
ERRORF(("Numeric overflow: (%ld)--\n", (long)svp->u.number));
/* NOTREACHED */
break;
}
put_number(sp, svp->u.number-- );
break;
}
else if (svp->type == T_FLOAT)
{
STORE_DOUBLE_USED
double d;
d = READ_DOUBLE(svp);
sp->type = T_FLOAT;
STORE_DOUBLE(sp, d);
d -= 1.0;
if (d < (-DBL_MAX) || d > DBL_MAX)
ERRORF(("Numeric overflow: (%g)--\n", READ_DOUBLE(svp)));
STORE_DOUBLE(svp, d);
break;
}
else if (svp->type == T_CHAR_LVALUE)
{
if ((unsigned char)(*svp->u.string) == 0x01)
ERROR("Can't set string character to 0.\n")
else
put_number(sp, (*svp->u.string)-- );
break;
}
else if (svp->type == T_LVALUE
|| svp->type == T_PROTECTED_LVALUE)
{
inter_sp = sp;
add_number_to_lvalue(svp, -1, sp, NULL);
break;
}
ERROR("-- of non-numeric argument\n")
/* TODO: Give the type and value */
break;
}
CASE(F_PRE_INC); /* --- pre_inc --- */
{
/* mixed pre_inc (mixed & sp[0])
*
* Increment the numeric value designated by the lvalue on top
* of the stack, and replace the stack entry with the incremented
* value. The lvalue itself is simply removed, not free()d.
*/
svalue_t *svp;
/* Get the designated value */
#ifdef DEBUG
if (sp->type != T_LVALUE)
ERROR("Bad argument to ++: not a lvalue\n")
/* TODO: Give type and value */
#endif
svp = sp->u.lvalue;
/* Do the increment and push */
if (svp->type == T_NUMBER)
{
if (svp->u.number == PINT_MAX)
{
ERRORF(("Numeric overflow: ++(%ld)\n", (long)svp->u.number));
/* NOTREACHED */
break;
}
put_number(sp, ++(svp->u.number) );
break;
}
else if (svp->type == T_FLOAT)
{
STORE_DOUBLE_USED
double d;
d = READ_DOUBLE(svp) + 1.0;
if (d < (-DBL_MAX) || d > DBL_MAX)
ERRORF(("Numeric overflow: ++(%g)\n", READ_DOUBLE(svp)));
sp->type = T_FLOAT;
STORE_DOUBLE(sp, d);
STORE_DOUBLE(svp, d);
break;
}
else if (svp->type == T_CHAR_LVALUE)
{
if ((unsigned char)(*svp->u.string) == 0xff /* TODO: MAX_CHAR */ )
ERROR("Can't set string character to 0.\n")
else
put_number(sp, ++(*svp->u.string) );
break;
}
else if (svp->type == T_LVALUE
|| svp->type == T_PROTECTED_LVALUE)
{
inter_sp = sp;
add_number_to_lvalue(svp, 1, NULL, sp);
break;
}
ERROR("++ of non-numeric argument\n")
/* TODO: Give type and value */
break;
}
CASE(F_PRE_DEC); /* --- pre_dec --- */
{
/* mixed pre_dec (mixed & sp[0])
*
* Decrement the numeric value designated by the lvalue on top
* of the stack, and replace the stack entry with the decremented
* value. The lvalue itself is simply removed, not free()d.
*/
svalue_t *svp;
/* Get the designated value */
#ifdef DEBUG
if (sp->type != T_LVALUE)
ERROR("Bad argument to --: not a lvalue\n")
/* TODO: Give the type and value */
#endif
svp = sp->u.lvalue;
/* Do the decrement and push */
if (svp->type == T_NUMBER)
{
if (svp->u.number == PINT_MIN)
{
ERRORF(("Numeric overflow: --(%ld)\n", (long)svp->u.number));
/* NOTREACHED */
break;
}
put_number(sp, --(svp->u.number) );
break;
}
else if (svp->type == T_FLOAT)
{
STORE_DOUBLE_USED
double d;
d = READ_DOUBLE(svp) - 1.0;
if (d < (-DBL_MAX) || d > DBL_MAX)
ERRORF(("Numeric overflow: --(%g)\n", READ_DOUBLE(svp)));
sp->type = T_FLOAT;
STORE_DOUBLE(sp, d);
STORE_DOUBLE(svp, d);
break;
}
else if (svp->type == T_CHAR_LVALUE)
{
if ((unsigned char)(*svp->u.string) == 0x01)
ERROR("Can't set string character to 0.\n")
else
put_number(sp, --(*svp->u.string) );
break;
}
else if (svp->type == T_LVALUE
|| svp->type == T_PROTECTED_LVALUE)
{
inter_sp = sp;
add_number_to_lvalue(svp, -1, NULL, sp);
break;
}
ERROR("-- of non-numeric argument\n")
/* TODO: Give the type and value */
break;
}
CASE(F_LAND); /* --- land <offset> --- */
{
/* If sp[0] is the number 0, leave it on the stack (as result)
* and branch by <offset>.
* Otherwise, pop the value and just continue.
*/
if (sp->type == T_NUMBER)
{
if (sp->u.number == 0)
{
uint offset = LOAD_UINT8(pc);
pc += offset;
break;
}
/* No need to explicitely free_svalue(), it's just a number */
}
else
{
free_svalue(sp);
}
sp--;
pc++;
break;
}
CASE(F_LOR); /* --- lor <offset> --- */
{
/* If sp[0] is not the number 0, leave it on the stack (as result)
* and branch by <offset>.
* Otherwise, pop the value and just continue.
*/
if (sp->type == T_NUMBER && sp->u.number == 0)
sp--; /* think 'free_svalue(sp--)' here... */
else
pc += GET_UINT8(pc);
pc++;
break;
}
CASE(F_ASSIGN); /* --- assign --- */
{
/* Assign the value sp[-1] to the value designated by lvalue sp[0].
* The assigned value sp[-1] remains on the stack as result
* (ie. the assign yields a rvalue).
*
* Make sure that complex destinations like arrays are not freed
* before the assignment is complete - see the comments to
* assign_svalue().
*/
svalue_t *dest;
/* Get the designated lvalue */
#ifdef DEBUG
if (sp->type != T_LVALUE)
fatal("Bad argument to F_ASSIGN: not a lvalue\n");
/* TODO: Give type and value */
#endif
dest = sp->u.lvalue;
assign_svalue(dest, sp-1);
sp--;
break;
}
CASE(F_VOID_ASSIGN); /* --- void_assign --- */
{
/* Assign the value sp[-1] to the value designated by lvalue sp[0],
* then remove both values from the stack.
*
* VOID_ASSIGNs occur pretty often, so the implementation uses an
* adopted copy of the transfer_svalue() code.
*
* Make sure that complex destinations like arrays are not freed
* before the assignment is complete - see the comments to
* assign_svalue().
*/
svalue_t *dest;
/* Get the designated value */
#ifdef DEBUG
if (sp->type != T_LVALUE)
fatal("Bad argument to F_VOID_ASSIGN: not a lvalue\n");
/* TODO: Give type and value */
#endif
dest = sp->u.lvalue;
/* Free the destination svalue so that transfer_svalue_no_free_spc()
* can be used. However, if the dest is a lvalue, a pointer or
* mapping, the assignment takes place right here and the next
* instruction will be executed by means of a 'goto again'.
*/
switch(dest->type)
{
case T_STRING:
switch(dest->x.string_type)
{
case STRING_MALLOC:
xfree(dest->u.string);
break;
case STRING_SHARED:
free_string(dest->u.string);
break;
}
break;
case T_OBJECT:
{
object_t *ob = dest->u.ob;
free_object(ob, "void_assign");
break;
}
case T_SYMBOL:
free_string(dest->u.string);
break;
case T_CLOSURE:
free_closure(dest);
break;
/* The cases below all assign right in the case block */
case T_QUOTED_ARRAY:
case T_POINTER:
{
vector_t *v = dest->u.vec;
transfer_svalue_no_free_spc(dest, sp-1, sp, pc);
sp -= 2;
free_array(v);
goto again;
}
case T_MAPPING:
{
mapping_t *m = dest->u.map;
transfer_svalue_no_free_spc(dest, sp-1, sp, pc);
sp -= 2;
free_mapping(m);
goto again;
}
case T_CHAR_LVALUE:
{
if (sp[-1].type == T_NUMBER)
{
if (sp[-1].u.number == 0)
ERROR("Can't assign 0 to string character.\n");
*dest->u.string = (char)sp[-1].u.number;
}
else
{
free_svalue(sp-1);
}
sp -= 2;
goto again;
}
/* the assignment class of operators always gets 'fresh' lvalues.
* Thus, if we encounter a protected lvalue of any flavour, this is
* due to a dereference of a reference stored in the original
* lvalue, and the protected lvalue must not be freed.
*/
case T_PROTECTED_CHAR_LVALUE:
{
struct protected_char_lvalue *p;
p = (struct protected_char_lvalue *)dest;
if (p->lvalue->type == T_STRING
&& p->lvalue->u.string == p->start)
{
if (sp[-1].type == T_NUMBER)
{
if (sp[-1].u.number == 0)
ERROR("Can't assign 0 to string character.\n");
*p->v.u.string = (char)sp[-1].u.number;
sp -= 2;
goto again;
}
}
sp--;
pop_stack();
goto again;
}
case T_POINTER_RANGE_LVALUE:
transfer_pointer_range(sp-1);
sp -= 2;
goto again;
case T_PROTECTED_POINTER_RANGE_LVALUE:
transfer_protected_pointer_range(
(struct protected_range_lvalue *)dest, sp-1
);
sp -= 2;
goto again;
case T_STRING_RANGE_LVALUE:
inter_sp = sp;
assign_string_range(sp-1, MY_TRUE);
sp -= 2;
goto again;
case T_PROTECTED_STRING_RANGE_LVALUE:
inter_sp = sp;
assign_protected_string_range(
(struct protected_range_lvalue *)dest, sp-1, MY_TRUE
);
sp -= 2;
goto again;
case T_LVALUE:
case T_PROTECTED_LVALUE:
{
/* This may be a chain of LVALUEs - we might as well
* call transfer_svalue() to deal with it
*/
transfer_svalue(dest->u.lvalue, sp-1);
sp -= 2;
goto again;
}
}
/* Nothing complicated: dest was just freed, now transfer
* the data.
*/
transfer_svalue_no_free_spc(dest, sp-1, sp, pc);
sp -= 2;
break;
}
CASE(F_ADD); /* --- add --- */
/* Add sp[0] to sp[-1] (the order is important), pop both
* summands from the stack and push the result.
*
* Possible type combinations:
* string + (string,int,float) -> string
* (int,float) + string -> string
* int + int -> int
* float + (int,float) -> float
* int + float -> float
* vector + vector -> vector
* mapping + mapping -> mapping
*/
switch ( sp[-1].type )
{
case T_STRING:
inter_pc = pc;
inter_sp = sp;
switch ( sp->type )
{
case T_STRING:
{
char *res;
size_t l = _svalue_strlen(sp-1);
size_t l2 = _svalue_strlen(sp);
DYN_STRING_COST(l+l2)
res = xalloc(l + l2 + 1);
if (!res)
ERROR("Out of memory\n")
strcpy(res, (sp-1)->u.string);
strcpy(res+l, sp->u.string);
free_string_svalue(sp);
sp--;
free_string_svalue(sp);
put_malloced_string(sp, res);
break;
}
case T_NUMBER:
{
char buff[80];
char *res;
size_t len1;
buff[sizeof(buff)-1] = '\0';
sprintf(buff, "%ld", sp->u.number);
if (buff[sizeof(buff)-1] != '\0')
FATAL("Buffer overflow in F_ADD: int number too big.\n")
res = xalloc((len1 = svalue_strlen(sp-1)) + strlen(buff) + 1);
if (!res)
ERROR("Out of memory\n")
strcpy(res, (sp-1)->u.string);
strcpy(res+len1, buff);
pop_n_elems(2);
push_malloced_string(res);
DYN_STRING_COST(len1)
break;
}
case T_FLOAT:
{
char buff[160];
char *res;
size_t len1;
buff[sizeof(buff)-1] = '\0';
sprintf(buff, "%g", READ_DOUBLE( sp ) );
if (buff[sizeof(buff)-1] != '\0')
FATAL("Buffer overflow in F_ADD: float number too big.\n")
res = xalloc((len1 = svalue_strlen(sp-1)) + strlen(buff) + 1);
if (!res)
ERROR("Out of memory\n")
strcpy(res, (sp-1)->u.string);
strcpy(res+len1, buff);
sp--;
free_string_svalue(sp);
put_malloced_string(sp, res);
DYN_STRING_COST(len1)
break;
}
default:
goto bad_add;
}
break;
/* End of case T_STRING */
case T_NUMBER:
switch ( sp->type )
{
case T_STRING:
{
char buff[80], *res;
size_t len1;
buff[sizeof(buff)-1] = '\0';
sprintf(buff, "%ld", (sp-1)->u.number);
if (buff[sizeof(buff)-1] != '\0')
FATAL("Buffer overflow in F_ADD: int number too big.\n")
inter_pc = pc;
inter_sp = sp;
res = xalloc(svalue_strlen(sp) + (len1 = strlen(buff)) + 1);
if (!res)
ERROR("Out of memory\n")
strcpy(res, buff);
strcpy(res+len1, sp->u.string);
free_string_svalue(sp);
sp--;
/* Overwrite the number at sp */
put_malloced_string(sp, res);
break;
}
case T_NUMBER:
{
p_int i;
p_int right = sp->u.number;
p_int left = (sp-1)->u.number;
if ((left >= 0 && right >= 0 && PINT_MAX - left < right)
|| (left < 0 && right < 0 && PINT_MIN - left > right)
)
{
ERRORF(("Numeric overflow: %ld + %ld\n"
, (long)left, (long)right));
/* NOTREACHED */
break;
}
i = left + right;
sp--;
sp->u.number = i;
break;
}
case T_FLOAT:
{
STORE_DOUBLE_USED
double sum;
sum = (double)((sp-1)->u.number) + READ_DOUBLE(sp);
if (sum < (-DBL_MAX) || sum > DBL_MAX)
ERRORF(("Numeric overflow: %ld + %g\n"
, (long)(sp-1)->u.number, READ_DOUBLE(sp)));
STORE_DOUBLE(sp-1, sum);
sp--;
sp->type = T_FLOAT;
break;
}
default:
goto bad_add;
}
break;
/* End of case T_NUMBER */
case T_FLOAT:
{
STORE_DOUBLE_USED
double sum;
if (sp->type == T_FLOAT)
{
sum = READ_DOUBLE(sp-1) + READ_DOUBLE(sp);
if (sum < (-DBL_MAX) || sum > DBL_MAX)
ERRORF(("Numeric overflow: %g + %g\n"
, READ_DOUBLE(sp-1), READ_DOUBLE(sp)));
STORE_DOUBLE(sp-1, sum);
sp--;
break;
}
if (sp->type == T_NUMBER)
{
sum = READ_DOUBLE(sp-1) + (double)(sp->u.number);
if (sum < (-DBL_MAX) || sum > DBL_MAX)
ERRORF(("Numeric overflow: %g + %ld\n"
, READ_DOUBLE(sp-1), (long)sp->u.number));
STORE_DOUBLE(sp-1, sum);
sp--;
break;
}
if (sp->type == T_STRING)
{
char buff[160];
char *res;
size_t len1;
buff[sizeof(buff)-1] = '\0';
sprintf(buff, "%g", READ_DOUBLE(sp-1) );
if (buff[sizeof(buff)-1] != '\0')
FATAL("Buffer overflow in F_ADD: float number too big.\n")
res = xalloc(svalue_strlen(sp) + (len1 = strlen(buff)) + 1);
if (!res)
error("Out of memory\n");
strcpy(res, buff);
strcpy(res+len1, (sp)->u.string);
free_string_svalue(sp);
sp--;
put_malloced_string(sp, res);
break;
}
goto bad_add;
}
/* End of case T_FLOAT */
case T_POINTER:
{
if (sp->type != T_POINTER) goto bad_add;
inter_sp = sp;
inter_pc = pc;
inter_add_array(sp->u.vec, &(sp-1)->u.vec);
sp--;
break;
}
case T_MAPPING:
{
mapping_t *m;
if (sp->type != T_MAPPING) goto bad_add;
check_map_for_destr((sp-1)->u.map);
check_map_for_destr(sp->u.map);
inter_pc = pc;
inter_sp = sp;
m = add_mapping((sp-1)->u.map,sp->u.map);
if (!m) {
ERROR("Out of memory.\n")
}
pop_n_elems(2);
push_mapping(m); /* This will make ref count == 2 */
deref_mapping(m);
if (max_mapping_size && MAP_SIZE(m) > max_mapping_size)
{
check_map_for_destr(m);
if (max_mapping_size && MAP_SIZE(m) > max_mapping_size)
ERRORF(("Illegal mapping size: %ld\n", MAP_SIZE(m)));
}
break;
}
default:
bad_add:
ERROR("Bad type of arg to '+'\n")
/* TODO: Give type, value and position. */
/* NOTREACHED */
}
break;
CASE(F_SUBTRACT); /* --- subtract --- */
{
/* Subtract sp[0] from sp[-1] (the order is important), pop both
* arguments from the stack and push the result.
*
* Possible type combinations:
* int - int -> int
* float - (int,float) -> float
* int - float -> float
* string - string -> string
* vector - vector -> vector
* mapping - mapping -> mapping
*/
p_int i;
if ((sp-1)->type == T_NUMBER)
{
if (sp->type == T_NUMBER)
{
p_int left = (sp-1)->u.number;
p_int right = sp->u.number;
if ((left >= 0 && right < 0 && PINT_MAX + right < left)
|| (left < 0 && right >= 0 && PINT_MIN + right > left)
)
{
ERRORF(("Numeric overflow: %ld - %ld\n"
, (long)left, (long)right));
/* NOTREACHED */
break;
}
i = left - right;
sp--;
sp->u.number = i;
break;
}
if (sp->type == T_FLOAT)
{
STORE_DOUBLE_USED
double diff;
diff = (double)((sp-1)->u.number) - READ_DOUBLE(sp);
if (diff < (-DBL_MAX) || diff > DBL_MAX)
ERRORF(("Numeric overflow: %ld - %g\n"
, (long)(sp-1)->u.number, READ_DOUBLE(sp)));
sp--;
STORE_DOUBLE(sp, diff);
sp->type = T_FLOAT;
break;
}
}
else if ((sp-1)->type == T_FLOAT)
{
STORE_DOUBLE_USED
double diff;
if (sp->type == T_FLOAT)
{
diff = READ_DOUBLE(sp-1) - READ_DOUBLE(sp);
if (diff < (-DBL_MAX) || diff > DBL_MAX)
ERRORF(("Numeric overflow: %g - %g\n"
, READ_DOUBLE(sp-1), READ_DOUBLE(sp)));
sp--;
STORE_DOUBLE(sp, diff);
break;
}
if (sp->type == T_NUMBER)
{
diff = READ_DOUBLE(sp-1) - (double)(sp->u.number);
if (diff < (-DBL_MAX) || diff > DBL_MAX)
ERRORF(("Numeric overflow: %g - %ld\n"
, READ_DOUBLE(sp-1), (long)sp->u.number));
sp--;
STORE_DOUBLE(sp, diff);
break;
}
}
else if ((sp-1)->type == T_POINTER && sp->type == T_POINTER)
{
vector_t *v;
v = sp->u.vec;
if (v->ref > 1)
{
deref_array(v);
v = slice_array(v, 0, (mp_int)VEC_SIZE(v) - 1 );
}
sp--;
/* subtract_array already takes care of destructed objects */
sp->u.vec = subtract_array(sp->u.vec, v);
break;
}
else if ((sp-1)->type == T_MAPPING && sp->type == T_MAPPING)
{
mapping_t *m;
m = subtract_mapping(sp[-1].u.map, sp->u.map);
free_mapping(sp->u.map);
sp--;
free_mapping(sp->u.map);
sp->u.map = m;
break;
}
else if ((sp-1)->type == T_STRING && sp->type == T_STRING)
{
char * result;
inter_sp = sp;
result = intersect_strings((sp-1)->u.string, sp->u.string, MY_TRUE);
free_string_svalue(sp);
sp--;
free_string_svalue(sp);
put_malloced_string(sp, result);
break;
}
else
goto bad_arg_1;
goto bad_arg_2;
}
CASE(F_MULTIPLY); /* --- multiply --- */
{
/* Multiply sp[-1] by sp[0] pop both arguments from the stack
* and push the result.
* TODO: Could be extended to cover mappings.
* TODO:: array/string multiplied by element === implode.
*
* Possible type combinations:
* int * int -> int
* float * (int,float) -> float
* int * float -> float
* string * int -> string
* int * string -> string
* array * int -> array
* int * array -> array
*/
p_int i;
switch ( sp[-1].type )
{
case T_NUMBER:
if (sp->type == T_NUMBER)
{
p_int left = (sp-1)->u.number;
p_int right = sp->u.number;
if (left > 0 && right > 0)
{
if ((left != 0 && PINT_MAX / left < right)
|| (right != 0 && PINT_MAX / right < left)
)
{
ERRORF(("Numeric overflow: %ld * %ld\n"
, (long)left, (long)right));
/* NOTREACHED */
break;
}
}
else if (left < 0 && right < 0)
{
if ((left != 0 && PINT_MAX / left > right)
|| (right != 0 && PINT_MAX / right > left)
)
{
ERRORF(("Numeric overflow: %ld * %ld\n"
, (long)left, (long)right));
/* NOTREACHED */
break;
}
}
else if (left != 0 && right != 0)
{
if ((left > 0 && PINT_MIN / left > right)
|| (right > 0 && PINT_MIN / right > left)
)
{
ERRORF(("Numeric overflow: %ld * %ld\n"
, (long)left, (long)right));
/* NOTREACHED */
break;
}
}
i = left * right;
sp--;
sp->u.number = i;
break;
}
if (sp->type == T_FLOAT)
{
STORE_DOUBLE_USED
double product;
product = (sp-1)->u.number * READ_DOUBLE(sp);
if (product < (-DBL_MAX) || product > DBL_MAX)
ERRORF(("Numeric overflow: %ld * %g\n"
, (long)(sp-1)->u.number, READ_DOUBLE(sp)));
sp--;
STORE_DOUBLE(sp, product);
sp->type = T_FLOAT;
break;
}
if (sp->type == T_STRING)
{
char * result;
size_t reslen;
size_t len;
if (sp[-1].u.number < 0)
goto bad_arg_1;
len = svalue_strlen(sp);
if (len > (size_t)PINT_MAX
|| (len != 0 && PINT_MAX / len < sp[-1].u.number)
|| (sp[-1].u.number != 0 && PINT_MAX / sp[-1].u.number < len)
)
ERROR("Result string too long.\n");
reslen = (size_t)sp[-1].u.number * len;
result = xalloc(reslen+1);
if (!result)
ERROR("Out of memory.\n");
if (sp[-1].u.number > 0 && len)
{
size_t curlen;
/* Seed result[] with one copy of the string */
memcpy(result, sp->u.string, len);
/* Repeatedly double the string in result */
curlen = len;
while (2*curlen < reslen)
{
memcpy(result+curlen, result, curlen);
curlen *= 2;
}
/* Fill up result to the full length */
if (reslen > curlen)
memcpy(result+curlen, result, reslen-curlen);
}
result[reslen] = '\0';
DYN_STRING_COST(reslen)
free_svalue(sp);
sp--;
/* No free_svalue(sp): it's just a number */
put_malloced_string(sp, result);
break;
}
if (sp->type == T_POINTER)
{
vector_t *result;
mp_int reslen;
size_t len;
if (sp[-1].u.number < 0)
goto bad_arg_1;
inter_sp = sp;
inter_pc = pc;
len = VEC_SIZE(sp->u.vec);
reslen = sp[-1].u.number * (mp_int)len;
result = allocate_uninit_array(reslen);
if (sp[-1].u.number > 0 && len)
{
size_t left;
svalue_t *from, *to;
/* Seed result[] with one copy of the array.
* To save memory we make sure that all strings
* are made shared.
*/
for ( from = sp->u.vec->item, to = result->item, left = len
; left
; from++, to++, left--)
{
if (from->type == T_STRING
&& from->x.string_type == STRING_MALLOC
)
{
put_string(to, make_shared_string(from->u.string));
if (!to->u.string)
ERROR("Out of memory.\n");
}
else
assign_svalue_no_free(to, from);
} /* for() seed */
/* Now fill the remainder of the vector with
* the values already copied in there.
*/
for (from = result->item, left = reslen - len
; left
; to++, from++, left--
)
assign_svalue_no_free(to, from);
} /* if (len) */
free_svalue(sp);
sp--;
/* No free_svalue(sp): it's just a number */
put_array(sp, result);
break;
}
goto bad_arg_2;
case T_FLOAT:
{
STORE_DOUBLE_USED
double product;
if (sp->type == T_FLOAT)
{
product = READ_DOUBLE(sp-1) * READ_DOUBLE(sp);
if (product < (-DBL_MAX) || product > DBL_MAX)
ERRORF(("Numeric overflow: %g * %g\n"
, READ_DOUBLE(sp-1), READ_DOUBLE(sp)));
STORE_DOUBLE(sp-1, product);
sp--;
break;
}
if (sp->type == T_NUMBER)
{
product = READ_DOUBLE(sp-1) * sp->u.number;
if (product < (-DBL_MAX) || product > DBL_MAX)
ERRORF(("Numeric overflow: %g * %ld\n"
, READ_DOUBLE(sp-1), (long)sp->u.number));
STORE_DOUBLE(sp-1, product);
sp--;
break;
}
goto bad_arg_2;
}
case T_STRING:
{
if (sp->type == T_NUMBER)
{
char * result;
size_t reslen;
size_t len;
size_t curlen;
if (sp->u.number < 0)
goto bad_arg_2;
len = svalue_strlen(sp-1);
if (len > (size_t)PINT_MAX
|| (len != 0 && PINT_MAX / len < sp->u.number)
|| (sp->u.number != 0 && PINT_MAX / sp->u.number < len)
)
ERROR("Result string too long.\n");
reslen = (size_t)sp->u.number * len;
result = xalloc(reslen+1);
if (!result)
ERROR("Out of memory.\n");
if (sp->u.number > 0 && len)
{
/* Seed result[] with one copy of the string */
memcpy(result, sp[-1].u.string, len);
/* Repeatedly double the string in result */
curlen = len;
while (2*curlen < reslen)
{
memcpy(result+curlen, result, curlen);
curlen *= 2;
}
/* Fill up result to the full length */
if (reslen > curlen)
memcpy(result+curlen, result, reslen-curlen);
}
result[reslen] = '\0';
DYN_STRING_COST(reslen)
/* No free_svalue(sp): it's just a number */
sp--;
free_string_svalue(sp);
put_malloced_string(sp, result);
break;
}
goto bad_arg_2;
}
case T_POINTER:
{
if (sp->type == T_NUMBER)
{
vector_t *result;
mp_int reslen;
size_t len;
if (sp->u.number < 0)
goto bad_arg_2;
inter_sp = sp;
inter_pc = pc;
len = VEC_SIZE(sp[-1].u.vec);
reslen = sp->u.number * (mp_int)len;
result = allocate_uninit_array(reslen);
if (sp->u.number > 0 && len)
{
size_t left;
svalue_t *from, *to;
/* Seed result[] with one copy of the array.
* To save memory we make sure that all strings
* are made shared.
*/
for ( from = sp[-1].u.vec->item, to = result->item, left = len
; left
; from++, to++, left--)
{
if (from->type == T_STRING
&& from->x.string_type == STRING_MALLOC
)
{
put_string(to, make_shared_string(from->u.string));
if (!to->u.string)
ERROR("Out of memory.\n");
}
else
assign_svalue_no_free(to, from);
} /* for() seed */
/* Now fill the remainder of the vector with
* the values already copied in there.
*/
for (from = result->item, left = reslen - len
; left
; to++, from++, left--
)
assign_svalue_no_free(to, from);
} /* if (len) */
/* No free_svalue(sp): it's just a number */
sp--;
free_svalue(sp);
put_array(sp, result);
break;
}
goto bad_arg_2;
}
default:
goto bad_arg_1;
}
break;
}
CASE(F_DIVIDE); /* --- divide --- */
{
/* Divide sp[-1] by sp[0] pop both arguments from the stack
* and push the result.
* TODO: Could be extended to cover arrays and mappings.
* TODO:: array/string divided by element === explode.
*
* Possible type combinations:
* int / int -> int
* float / (int,float) -> float
* int / float -> float
*/
int i;
if ((sp-1)->type == T_NUMBER)
{
if (sp->type == T_NUMBER) {
if (sp->u.number == 0)
ERROR("Division by zero\n")
if ((sp-1)->u.number == PINT_MIN && sp->u.number == -1)
ERRORF(("Numeric overflow: %ld / -1\n"
, (long)(sp-1)->u.number
));
i = (sp-1)->u.number / sp->u.number;
sp--;
sp->u.number = i;
break;
}
if (sp->type == T_FLOAT)
{
double dtmp;
STORE_DOUBLE_USED
dtmp = READ_DOUBLE( sp );
if (dtmp == 0.)
ERROR("Division by zero\n")
sp--;
dtmp = (double)sp->u.number / dtmp;
if (dtmp < (-DBL_MAX) || dtmp > DBL_MAX)
ERRORF(("Numeric overflow: %ld / %g\n"
, (long)(sp)->u.number, READ_DOUBLE(sp+1)));
STORE_DOUBLE(sp, dtmp);
sp->type = T_FLOAT;
break;
}
goto bad_arg_2;
}
else if ((sp-1)->type == T_FLOAT)
{
double dtmp;
STORE_DOUBLE_USED
if (sp->type == T_FLOAT)
{
dtmp = READ_DOUBLE( sp );
if (dtmp == 0.) {
ERROR("Division by zero\n")
return MY_FALSE;
}
sp--;
dtmp = READ_DOUBLE(sp) / dtmp;
if (dtmp < (-DBL_MAX) || dtmp > DBL_MAX)
ERRORF(("Numeric overflow: %g / %g\n"
, READ_DOUBLE(sp), READ_DOUBLE(sp+1)));
STORE_DOUBLE(sp, dtmp);
break;
}
if (sp->type == T_NUMBER)
{
if (sp->u.number == 0) {
ERROR("Division by zero\n")
return MY_FALSE;
}
dtmp = (double)sp->u.number;
sp--;
dtmp = READ_DOUBLE(sp) / dtmp;
if (dtmp < (-DBL_MAX) || dtmp > DBL_MAX)
ERRORF(("Numeric overflow: %g / %ld\n"
, READ_DOUBLE(sp), (long)(sp+1)->u.number));
STORE_DOUBLE(sp, dtmp);
break;
}
goto bad_arg_2;
}
goto bad_arg_1;
break;
}
CASE(F_MOD); /* --- mod --- */
{
/* Compute sp[-1] modulus sp[0] pop both arguments from the stack
* and push the result.
* TODO: Could be extended to cover floats(!), arrays and mappings.
* TODO: Define properly and add the rem operation.
*
* Possible type combinations:
* int % int -> int
*/
int i;
if ((sp-1)->type != T_NUMBER)
goto bad_arg_1;
if (sp->type != T_NUMBER)
goto bad_arg_2;
if (sp->u.number == 0)
{
ERROR("Modulus by zero.\n")
return MY_FALSE;
}
else if (sp->u.number == 1
|| sp->u.number == -1
)
i = 0;
/* gcc 2.91 on Linux/x86 generates buggy code
* for MIN_INT % -1. Might as well catch it all.
*/
else
i = (sp-1)->u.number % sp->u.number;
sp--;
sp->u.number = i;
break;
}
CASE(F_GT); /* --- gt --- */
{
/* Test if sp[-1] > sp[0]. If yes, push 1 onto the stack,
* else 0 (of course after popping both arguments).
*
* Comparable types are int, string and float, each only
* to its own type.
*/
int i;
if ((sp-1)->type == T_STRING && sp->type == T_STRING)
{
i = strcmp((sp-1)->u.string, sp->u.string) > 0;
free_string_svalue(sp);
sp--;
free_string_svalue(sp);
put_number(sp, i);
break;
}
if ((sp-1)->type == T_NUMBER && sp->type == T_NUMBER)
{
i = (sp-1)->u.number > sp->u.number;
sp--;
sp->u.number = i;
break;
}
if ((sp-1)->type == T_FLOAT && sp->type == T_FLOAT)
{
i = READ_DOUBLE( sp-1 ) > READ_DOUBLE( sp );
sp--;
put_number(sp, i);
break;
}
if ((sp-1)->type == T_NUMBER && sp->type == T_FLOAT)
{
i = (double)((sp-1)->u.number) > READ_DOUBLE( sp );
sp--;
put_number(sp, i);
break;
}
if ((sp-1)->type == T_FLOAT && sp->type == T_NUMBER)
{
i = READ_DOUBLE( sp-1 ) > (double)(sp->u.number);
sp--;
put_number(sp, i);
break;
}
if (!( (sp-1)->type & (T_NUMBER|T_STRING|T_FLOAT) ))
goto bad_arg_1;
if (!( sp ->type & (T_NUMBER|T_STRING|T_FLOAT) ))
goto bad_arg_2;
ERROR("Arguments to > don't match\n")
/* TODO: Give type and value */
}
CASE(F_GE); /* --- ge --- */
{
/* Test if sp[-1] >= sp[0]. If yes, push 1 onto the stack,
* else 0 (of course after popping both arguments).
*
* Comparable types are int, string and float, each only
* to its own type.
*/
int i;
if ((sp-1)->type == T_STRING && sp->type == T_STRING)
{
i = strcmp((sp-1)->u.string, sp->u.string) >= 0;
free_string_svalue(sp);
sp--;
free_string_svalue(sp);
put_number(sp, i);
break;
}
if ((sp-1)->type == T_NUMBER && sp->type == T_NUMBER)
{
i = (sp-1)->u.number >= sp->u.number;
sp--;
sp->u.number = i;
break;
}
if ((sp-1)->type == T_FLOAT && sp->type == T_FLOAT)
{
i = READ_DOUBLE( sp-1 ) >= READ_DOUBLE( sp );
sp--;
put_number(sp, i);
break;
}
if ((sp-1)->type == T_NUMBER && sp->type == T_FLOAT)
{
i = (double)((sp-1)->u.number) >= READ_DOUBLE( sp );
sp--;
put_number(sp, i);
break;
}
if ((sp-1)->type == T_FLOAT && sp->type == T_NUMBER)
{
i = READ_DOUBLE( sp-1 ) >= (double)(sp->u.number);
sp--;
put_number(sp, i);
break;
}
if (!( (sp-1)->type & (T_NUMBER|T_STRING|T_FLOAT) ))
goto bad_arg_1;
if (!( sp ->type & (T_NUMBER|T_STRING|T_FLOAT) ))
goto bad_arg_2;
ERROR("Arguments to >= don't match\n")
/* TODO: Give type and value */
}
CASE(F_LT); /* --- lt --- */
{
/* Test if sp[-1] < sp[0]. If yes, push 1 onto the stack,
* else 0 (of course after popping both arguments).
*
* Comparable types are int, string and float, each only
* to its own type.
*/
int i;
if ((sp-1)->type == T_STRING && sp->type == T_STRING)
{
i = strcmp((sp-1)->u.string, sp->u.string) < 0;
free_string_svalue(sp);
sp--;
free_string_svalue(sp);
put_number(sp, i);
break;
}
if ((sp-1)->type == T_NUMBER && sp->type == T_NUMBER)
{
i = (sp-1)->u.number < sp->u.number;
sp--;
sp->u.number = i;
break;
}
if ((sp-1)->type == T_FLOAT && sp->type == T_FLOAT)
{
i = READ_DOUBLE( sp-1 ) < READ_DOUBLE( sp );
sp--;
put_number(sp, i);
break;
}
if ((sp-1)->type == T_NUMBER && sp->type == T_FLOAT)
{
i = (double)((sp-1)->u.number) < READ_DOUBLE( sp );
sp--;
put_number(sp, i);
break;
}
if ((sp-1)->type == T_FLOAT && sp->type == T_NUMBER)
{
i = READ_DOUBLE( sp-1 ) < (double)(sp->u.number);
sp--;
put_number(sp, i);
break;
}
if (!( (sp-1)->type & (T_NUMBER|T_STRING|T_FLOAT) ))
goto bad_arg_1;
if (!( sp ->type & (T_NUMBER|T_STRING|T_FLOAT) ))
goto bad_arg_2;
ERROR("Arguments to < don't match\n")
/* TODO: Give error and type */
}
CASE(F_LE); /* --- le --- */
{
/* Test if sp[-1] <= sp[0]. If yes, push 1 onto the stack,
* else 0 (of course after popping both arguments).
*
* Comparable types are int, string and float, each only
* to its own type.
*/
int i;
if ((sp-1)->type == T_STRING && sp->type == T_STRING)
{
i = strcmp((sp-1)->u.string, sp->u.string) <= 0;
free_string_svalue(sp);
sp--;
free_string_svalue(sp);
put_number(sp, i);
break;
}
if ((sp-1)->type == T_NUMBER && sp->type == T_NUMBER)
{
i = (sp-1)->u.number <= sp->u.number;
sp--;
sp->u.number = i;
break;
}
if ((sp-1)->type == T_FLOAT && sp->type == T_FLOAT)
{
i = READ_DOUBLE( sp-1 ) <= READ_DOUBLE( sp );
sp--;
put_number(sp, i);
break;
}
if ((sp-1)->type == T_NUMBER && sp->type == T_FLOAT)
{
i = (double)((sp-1)->u.number) <= READ_DOUBLE( sp );
sp--;
put_number(sp, i);
break;
}
if ((sp-1)->type == T_FLOAT && sp->type == T_NUMBER)
{
i = READ_DOUBLE( sp-1 ) <= (double)(sp->u.number);
sp--;
put_number(sp, i);
break;
}
if (!( (sp-1)->type & (T_NUMBER|T_STRING|T_FLOAT) ))
goto bad_arg_1;
if (!( sp ->type & (T_NUMBER|T_STRING|T_FLOAT) ))
goto bad_arg_2;
ERROR("Arguments to <= don't match\n")
/* TODO: Give type and value */
}
CASE(F_EQ); /* --- eq --- */
{
/* Test if sp[-1] == sp[0]. If yes, push 1 onto the stack,
* else 0 (of course after popping both arguments).
*
* Comparable types are all types, each to its own. Comparisons
* between distinct types (with the exception of int vs float)
* always yield 'unequal'.
* Vectors and mappings are compared by ref only.
*/
int i;
if ((sp-1)->type == T_FLOAT && sp->type == T_NUMBER)
{
i = READ_DOUBLE( sp-1 ) == (double)(sp->u.number);
}
else if ((sp-1)->type == T_NUMBER && sp->type == T_FLOAT)
{
i = (double)((sp-1)->u.number) == READ_DOUBLE( sp );
}
else if ((sp-1)->type != sp->type)
{
i = 0;
}
else /* types are equal */
{
switch(sp->type)
{
case T_NUMBER:
i = (sp-1)->u.number == sp->u.number;
break;
case T_POINTER:
i = (sp-1)->u.vec == sp->u.vec;
break;
case T_STRING:
i = strcmp((sp-1)->u.string, sp->u.string) == 0;
break;
case T_OBJECT:
i = (sp-1)->u.ob == sp->u.ob;
break;
case T_FLOAT:
i = READ_DOUBLE( sp-1 ) == READ_DOUBLE( sp );
break;
case T_CLOSURE:
i = closure_eq(sp-1, sp);
break;
case T_SYMBOL:
case T_QUOTED_ARRAY:
i = (sp-1)->u.string == sp->u.string &&
(sp-1)->x.generic == sp->x.generic;
break;
case T_MAPPING:
i = (sp-1)->u.map == sp->u.map;
break;
default:
if (sp->type == T_LVALUE)
error("Reference passed to !=\n");
fatal("Illegal type to !=\n");
/* TODO: Give type and value */
/* NOTREACHED */
return MY_FALSE;
}
}
pop_stack();
free_svalue(sp);
put_number(sp, i);
break;
}
CASE(F_NE); /* --- ne --- */
{
/* Test if sp[-1] != sp[0]. If yes, push 1 onto the stack,
* else 0 (of course after popping both arguments).
*
* Comparable types are all types, each to its own. Comparisons
* between distinct types always yield 'unequal'.
* Vectors and mappings are compared by ref only.
*/
int i;
if ((sp-1)->type == T_FLOAT && sp->type == T_NUMBER)
{
i = READ_DOUBLE( sp-1 ) != (double)(sp->u.number);
}
else if ((sp-1)->type == T_NUMBER && sp->type == T_FLOAT)
{
i = (double)((sp-1)->u.number) != READ_DOUBLE( sp );
}
else if ((sp-1)->type != sp->type)
{
i = 1;
}
else /* types are equal */
{
switch(sp->type)
{
case T_NUMBER:
i = (sp-1)->u.number != sp->u.number;
break;
case T_STRING:
i = strcmp((sp-1)->u.string, sp->u.string);
break;
case T_POINTER:
i = (sp-1)->u.vec != sp->u.vec;
break;
case T_OBJECT:
i = (sp-1)->u.ob != sp->u.ob;
break;
case T_FLOAT:
i = READ_DOUBLE( sp-1 ) != READ_DOUBLE( sp );
break;
case T_CLOSURE:
i = !closure_eq(sp-1, sp);
break;
case T_SYMBOL:
case T_QUOTED_ARRAY:
i = (sp-1)->u.string != sp->u.string ||
(sp-1)->x.generic != sp->x.generic;
break;
case T_MAPPING:
i = (sp-1)->u.map != sp->u.map;
break;
default:
if (sp->type == T_LVALUE)
error("Reference passed to !=\n");
fatal("Illegal type to !=\n");
/* NOTREACHED */
return MY_FALSE;
}
}
pop_stack();
free_svalue(sp);
put_number(sp, i);
break;
}
CASE(F_COMPL); /* --- compl --- */
/* Compute the binary complement of number sp[0] and leave
* that on the stack.
*/
if (sp->type != T_NUMBER)
ERROR("Bad argument to ~\n")
/* TODO: Give type and value */
sp->u.number = ~ sp->u.number;
break;
CASE(F_AND); /* --- and --- */
{
/* Compute the intersection of sp[-1] and sp[0] and leave
* the result on the stack.
*
* Possible type combinations:
* int & int -> int
* string & string -> string
* vector & vector -> vector
*
* TODO: Extend this to mappings.
*/
int i;
if (sp->type == T_POINTER && (sp-1)->type == T_POINTER)
{
inter_sp = sp - 2;
(sp-1)->u.vec = intersect_array(sp->u.vec, (sp-1)->u.vec);
sp--;
break;
}
if (sp->type == T_STRING && (sp-1)->type == T_STRING)
{
char * result;
inter_sp = sp;
result = intersect_strings(sp[-1].u.string, sp->u.string, MY_FALSE);
free_string_svalue(sp-1);
free_string_svalue(sp);
put_malloced_string(sp-1, result);
sp--;
break;
}
if ((sp-1)->type != T_NUMBER)
goto bad_arg_1;
if (sp->type != T_NUMBER)
goto bad_arg_2;
i = (sp-1)->u.number & sp->u.number;
sp--;
sp->u.number = i;
break;
}
CASE(F_OR); /* --- or --- */
{
/* Compute the binary-or of sp[-1] and sp[0] and leave
* the result on the stack.
*
* Possible type combinations:
* int | int -> int
*
* TODO: Extend this to vectors and mappings.
*/
int i;
if ((sp-1)->type != T_NUMBER)
goto bad_arg_1;
if (sp->type != T_NUMBER)
goto bad_arg_2;
i = (sp-1)->u.number | sp->u.number;
sp--;
sp->u.number = i;
break;
}
CASE(F_XOR); /* --- xor --- */
{
/* Compute the binary-xor of sp[-1] and sp[0] and leave
* the result on the stack.
*
* Possible type combinations:
* int ^ int -> int
*
* TODO: Extend this to vectors and mappings.
*/
int i;
if ((sp-1)->type != T_NUMBER)
goto bad_arg_1;
if (sp->type != T_NUMBER)
goto bad_arg_2;
i = (sp-1)->u.number ^ sp->u.number;
sp--;
sp->u.number = i;
break;
}
CASE(F_LSH); /* --- lsh --- */
{
/* Shift number sp[-1] left by sp[0] bits and leave
* the result on the stack.
*
* Possible type combinations:
* int << int -> int
*
* TODO: Extend this to vectors and mappings.
* TODO: Implement an arithmetic shift.
*/
int i;
if ((sp-1)->type != T_NUMBER)
goto bad_arg_1;
if (sp->type != T_NUMBER)
goto bad_arg_2;
i = sp->u.number;
sp--;
sp->u.number = (uint)i > MAX_SHIFT ? 0 : sp->u.number << i;
break;
}
CASE(F_RSH); /* --- rsh --- */
{
/* Arithmetically shift number sp[-1] right by sp[0] bits and leave
* the result on the stack.
*
* Possible type combinations:
* int >> int -> int
*
* TODO: Extend this to vectors and mappings.
*/
int i;
if ((sp-1)->type != T_NUMBER)
goto bad_arg_1;
if (sp->type != T_NUMBER)
goto bad_arg_2;
i = sp->u.number;
sp--;
if ((uint)i <= MAX_SHIFT)
sp->u.number >>= i;
else if (sp->u.number >= 0)
sp->u.number = 0;
else
sp->u.number = -1;
break;
}
CASE(F_RSHL); /* --- rshl --- */
{
/* Logically shift number sp[-1] right by sp[0] bits and leave
* the result on the stack.
*
* Possible type combinations:
* int >> int -> int
*
* TODO: Extend this to vectors and mappings.
*/
int i;
if ((sp-1)->type != T_NUMBER)
goto bad_arg_1;
if (sp->type != T_NUMBER)
goto bad_arg_2;
i = sp->u.number;
sp--;
if ((uint)i > MAX_SHIFT)
sp->u.number = 0;
else
sp->u.number = (p_uint)sp->u.number >> i;
break;
}
CASE(F_NOT); /* --- not --- */
/* Compute the logical negation of sp[0] and put it onto the stack.
* Every value != 0 is replaced by 0, just number 0 is replaced by 1.
*/
if (sp->type == T_NUMBER)
{
if (sp->u.number == 0)
{
sp->u.number = 1;
break;
}
} else
free_svalue(sp);
put_number(sp, 0);
break;
CASE(F_NX_RANGE); /* --- nx_range --- */
CASE(F_RX_RANGE); /* --- rx_range --- */
/* Push '1' onto the stack to make up for the missing
* upper range bound, then fall through to the normal
* range handling.
*/
sp++;
put_number(sp, 1);
/* FALLTHROUGH */
CASE(F_RANGE); /* --- range --- */
CASE(F_NR_RANGE); /* --- nr_range --- */
CASE(F_RN_RANGE); /* --- rn_range --- */
CASE(F_RR_RANGE); /* --- rr_range --- */
{
/* Compute the range sp[-1]..sp[0] from string/array sp[-2]
* and leave it on the stack.
* This code also handles the NX/RX_RANGE, pretending that
* they are NR/RR_RANGEs.
*/
if (sp[-1].type != T_NUMBER)
ERROR("Bad type of start interval to [..] range.\n")
/* TODO: Give type and value */
if (sp[0].type != T_NUMBER)
ERROR("Bad type of end interval to [..] range.\n")
/* TODO: Give type and value */
if (sp[-2].type == T_POINTER)
{
/* Slice a range from an array */
vector_t *v;
int size, i1, i2;
size = (int)VEC_SIZE(sp[-2].u.vec);
if (instruction == F_RANGE
|| instruction == F_NR_RANGE
|| instruction == F_NX_RANGE)
i1 = sp[-1].u.number;
else
i1 = size - sp[-1].u.number;
if (instruction == F_RANGE
|| instruction == F_RN_RANGE)
i2 = sp[0].u.number;
else
i2 = size - sp[0].u.number;
if (i2 >= size)
i2 = size - 1;
pop_stack();
pop_stack();
v = slice_array(sp->u.vec, i1, i2);
free_array(sp->u.vec);
if (v)
{
sp->u.vec = v;
}
else
{
put_number(sp, 0);
}
}
else if (sp[-2].type == T_STRING)
{
/* Slice a range from string */
int len, from, to;
char *res;
len = (int)svalue_strlen(&sp[-2]);
if (instruction == F_RANGE
|| instruction == F_NR_RANGE
|| instruction == F_NX_RANGE)
from = sp[-1].u.number;
else
from = len - sp[-1].u.number;
if (from < 0)
{
from = 0;
}
if (instruction == F_RANGE
|| instruction == F_RN_RANGE)
to = sp[0].u.number;
else
to = len - sp[0].u.number;
if (to >= len)
to = len-1;
if (to < from)
{
pop_n_elems(3);
push_volatile_string("");
break;
}
if (to == len-1)
{
res = string_copy(sp[-2].u.string + from);
pop_n_elems(3);
push_malloced_string(res);
break;
}
res = xalloc((size_t)(to - from + 2));
strncpy(res, sp[-2].u.string + from, (size_t)(to - from + 1));
res[to - from + 1] = '\0';
pop_n_elems(3);
push_malloced_string(res);
}
else
{
ERROR("Bad argument to [..] range operand: neither string nor array.\n")
/* TODO: Give type and value */
}
break;
}
CASE(F_ADD_EQ); /* --- add_eq --- */
CASE(F_VOID_ADD_EQ); /* --- void_add_eq --- */
{
/* Add sp[-1] to the value designated by lvalue sp[0] (the order
* is important) and assign the result to sp[0].
* For F_ADD_EQ, the result is also left on the stack.
*
* Possible type combinations:
* string + (string,int,float) -> string
* int + string -> string
* int + int -> int
* int + float -> float
* float + (float,int) -> float
* vector + vector -> vector
* mapping + mapping -> mapping
* TODO: This type mapping should be documented in 2-dim-arrays,
* TODO:: one each for F_ADD_EQ, F_MULT_EQ, etc. This would
* TODO:: also make the checks in the compiler simpler.
*/
short type2; /* type and value of sp[-1] */
union u u2;
svalue_t *argp; /* the actual value of sp[0] */
type2 = sp[-1].type;
u2 = sp[-1].u;
#ifdef DEBUG
if (sp->type != T_LVALUE)
goto bad_arg_1;
#endif
/* Set argp to the actual value designated by sp[0] */
for ( argp = sp->u.lvalue
; T_LVALUE == argp->type || T_PROTECTED_LVALUE == argp->type
; argp = argp->u.lvalue)
NOOP;
/* Now do it */
switch(argp->type)
{
case T_STRING: /* Adding to a string */
{
char *new_string;
/* Perform the addition, creating new_string */
/* TODO: Make this use memcpy() instead of strcpy() */
if (type2 == T_STRING)
{
size_t l = _svalue_strlen(argp);
size_t l2 = _svalue_strlen(sp-1);
DYN_STRING_COST(l+l2)
inter_pc = pc;
if ( !(new_string = xalloc(l + l2 + 1)) )
ERROR("Out of memory\n");
memcpy(new_string, argp->u.string, l);
memcpy(new_string+l, u2.string, l2+1);
free_string_svalue(sp-1);
sp -= 2;
}
else if (type2 == T_NUMBER)
{
char buff[80];
size_t l = _svalue_strlen(argp);
DYN_STRING_COST(l)
buff[sizeof(buff)-1] = '\0';
sprintf(buff, "%ld", (long)u2.number);
if (buff[sizeof(buff)-1] != '\0')
FATAL("Buffer overflow in F_ADD_EQ: int number too big.\n")
inter_pc = pc;
if ( !(new_string =
xalloc(l + strlen(buff) + 1)) )
ERROR("Out of memory\n");
strcpy(new_string, argp->u.string);
strcat(new_string, buff);
sp -= 2;
}
else if (type2 == T_FLOAT)
{
char buff[160];
size_t l = _svalue_strlen(argp);
DYN_STRING_COST(l)
buff[sizeof(buff)-1] = '\0';
sprintf(buff, "%g", READ_DOUBLE(sp-1) );
if (buff[sizeof(buff)-1] != '\0')
FATAL("Buffer overflow in F_ADD_EQ: float number too big.\n")
inter_pc = pc;
if ( !(new_string =
xalloc(l + strlen(buff) + 1)) )
ERROR("Out of memory\n");
strcpy(new_string, argp->u.string);
strcat(new_string, buff);
sp -= 2;
}
else
{
goto bad_arg_2;
}
/* Replace *argp by the new string */
free_string_svalue(argp);
argp->x.string_type = STRING_MALLOC;
argp->u.string = new_string;
break;
}
case T_NUMBER: /* Add to a number */
if (type2 == T_NUMBER)
{
p_int left = argp->u.number;
p_int right = u2.number;
if ((left >= 0 && right >= 0 && PINT_MAX - left < right)
|| (left < 0 && right < 0 && PINT_MIN - left > right)
)
{
ERRORF(("Numeric overflow: %ld += %ld\n"
, (long)left, (long)right));
/* NOTREACHED */
break;
}
if (instruction == F_VOID_ADD_EQ)
{
argp->u.number += u2.number;
sp -= 2;
goto again;
}
(--sp)->u.number = argp->u.number += u2.number;
goto again;
}
else if (type2 == T_FLOAT)
{
STORE_DOUBLE_USED
double sum;
sum = (double)(argp->u.number) + READ_DOUBLE(sp-1);
if (sum < (-DBL_MAX) || sum > DBL_MAX)
ERRORF(("Numeric overflow: %ld + %g\n"
, (long)argp->u.number, READ_DOUBLE(sp-1)));
argp->type = T_FLOAT;
STORE_DOUBLE(argp, sum);
if (instruction == F_VOID_ADD_EQ)
{
sp -= 2;
goto again;
}
--sp;
sp->type = T_FLOAT;
STORE_DOUBLE(sp, sum);
goto again;
}
else if (type2 == T_STRING)
{
char buff[80], *res;
size_t len1;
buff[sizeof(buff)-1] = '\0';
sprintf(buff, "%ld", argp->u.number);
if (buff[sizeof(buff)-1] != '\0')
FATAL("Buffer overflow in F_ADD_EQ: int number too big.\n")
inter_pc = pc;
inter_sp = sp;
res = xalloc(svalue_strlen(sp-1) + (len1 = strlen(buff)) + 1);
if (!res)
ERROR("Out of memory\n")
strcpy(res, buff);
strcpy(res+len1, u2.string);
free_string_svalue(sp-1);
/* Overwrite the number in argp */
put_malloced_string(argp, res);
if (instruction == F_VOID_ADD_EQ)
{
sp -= 2;
goto again;
}
--sp;
res = string_copy(res);
if (!res)
ERROR("Out of memory\n")
put_malloced_string(sp, res);
goto again;
}
else
{
ERROR("Bad type number to rhs +=.\n")
/* TODO: Give type and value */
}
break;
case T_CHAR_LVALUE: /* Add to a character in a string */
if (type2 == T_NUMBER)
{
p_int left = *argp->u.string;
p_int right = u2.number;
if ((left >= 0 && right >= 0 && PINT_MAX - left < right)
|| (left < 0 && right < 0 && PINT_MIN - left > right)
)
{
ERRORF(("Numeric overflow: %ld += %ld\n"
, (long)left, (long)right));
/* NOTREACHED */
break;
}
if (((left + right) & 0xff) == 0)
ERROR("Can't set string character to 0.\n");
if (instruction == F_VOID_ADD_EQ)
{
*argp->u.string += u2.number;
sp -= 2;
goto again;
}
(--sp)->u.number = *argp->u.string += u2.number;
goto again;
}
else
{
ERROR("Bad type number to rhs +=.\n")
/* TODO: Give type and value */
}
break;
case T_MAPPING: /* Add to a mapping */
if (type2 != T_MAPPING)
{
ERROR("Bad type to rhs +=.\n")
/* TODO: Give type and value */
}
else
{
check_map_for_destr(u2.map);
add_to_mapping(argp->u.map, u2.map);
sp -= 2;
free_mapping(u2.map);
if (max_mapping_size && MAP_SIZE(argp->u.map) > max_mapping_size)
{
check_map_for_destr(argp->u.map);
if (max_mapping_size && MAP_SIZE(argp->u.map) > max_mapping_size)
ERRORF(("Illegal mapping size: %ld\n", MAP_SIZE(argp->u.map)));
}
}
break;
case T_POINTER: /* Add to an array */
if (type2 != T_POINTER)
{
ERROR("Bad type to rhs +=.\n")
/* TODO: Give type and value */
}
else
{
vector_t *v;
inter_sp = sp;
inter_pc = pc;
v = inter_add_array(u2.vec, &argp->u.vec);
if (instruction == F_VOID_ADD_EQ)
{
sp -= 2;
goto again;
}
sp--;
sp->u.vec = ref_array(v);
goto again;
}
break;
case T_FLOAT: /* Add to a float */
if (type2 == T_FLOAT)
{
STORE_DOUBLE_USED
double d;
/* don't use the address of u2, this would prevent putting
* it in a register
*/
d = READ_DOUBLE(argp) + READ_DOUBLE(sp-1);
if (d < (-DBL_MAX) || d > DBL_MAX)
ERRORF(("Numeric overflow: %g + %g\n"
, READ_DOUBLE(argp), READ_DOUBLE(sp-1)));
STORE_DOUBLE(argp, d);
sp -= 2;
}
else if (type2 == T_NUMBER)
{
STORE_DOUBLE_USED
double d;
d = READ_DOUBLE(argp) + (double)sp[-1].u.number;
if (d < (-DBL_MAX) || d > DBL_MAX)
ERRORF(("Numeric overflow: %g + %ld\n"
, READ_DOUBLE(argp), (long)(sp-1)->u.number));
STORE_DOUBLE(argp, d);
sp -= 2;
}
else
{
goto bad_right;
}
break;
default:
ERROR("Bad type to lhs +=\n")
/* TODO: Give type and value */
} /* end of switch */
/* If the instruction is F_ADD_EQ, leave the result on the stack */
if (instruction != F_VOID_ADD_EQ)
{
sp++;
assign_svalue_no_free(sp, argp);
}
break;
}
CASE(F_SUB_EQ); /* --- sub_eq --- */
{
/* Subtract sp[-1] from the value designated by lvalue sp[0] (the
* order is important), assign the result to sp[0] and also leave
* it on the stack.
*
* Possible type combinations:
* int - int -> int
* float - (float,int) -> float
* string - string -> string
* vector - vector -> vector
* mapping - mapping -> mapping
*/
short type2; /* type and value of sp[-1] */
union u u2;
svalue_t *argp; /* the actual value of sp[0] */
type2 = sp[-1].type;
u2 = sp[-1].u;
#ifdef DEBUG
if (sp->type != T_LVALUE)
goto bad_arg_1;
#endif
/* Set argp to the actual value designated by sp[0] */
for ( argp = sp->u.lvalue
; T_LVALUE == argp->type || T_PROTECTED_LVALUE == argp->type
; argp = argp->u.lvalue)
NOOP;
/* Now do it */
switch (argp->type)
{
case T_NUMBER: /* Subtract from a number */
if (type2 != T_NUMBER)
goto bad_right;
sp--;
{
p_int left = argp->u.number;
p_int right = u2.number;
if ((left >= 0 && right < 0 && PINT_MAX + right < left)
|| (left < 0 && right >= 0 && PINT_MIN + right > left)
)
{
ERRORF(("Numeric overflow: %ld -= %ld\n"
, (long)left, (long)right));
/* NOTREACHED */
break;
}
}
sp->u.number = argp->u.number -= u2.number;
break;
case T_CHAR_LVALUE: /* Subtract from a char in a string */
if (type2 != T_NUMBER)
goto bad_right;
{
p_int left = *argp->u.string;
p_int right = u2.number;
if ((left >= 0 && right < 0 && PINT_MAX + right < left)
|| (left < 0 && right >= 0 && PINT_MIN + right > left)
)
{
ERRORF(("Numeric overflow: %ld -= %ld\n"
, (long)left, (long)right));
/* NOTREACHED */
break;
}
}
if (((*argp->u.string - u2.number) & 0xff) == 0)
ERROR("Can't set string character to 0.\n");
sp--;
sp->u.number = *argp->u.string -= u2.number;
break;
case T_STRING: /* Subtract from a string */
{
char * result;
if (type2 != T_STRING)
goto bad_right;
inter_sp = sp;
result = intersect_strings(argp->u.string, (sp-1)->u.string, MY_TRUE);
free_string_svalue(argp);
put_malloced_string(argp, result);
free_svalue(sp);
sp--;
free_string_svalue(sp);
put_malloced_string(sp, string_copy(result));
break;
}
case T_POINTER: /* Subtract from an array */
{
vector_t *v, *v_old;
if (type2 != T_POINTER)
goto bad_right;
v = u2.vec;
/* Duplicate the minuend array if necessary, as
* the subtraction will change and free it
*/
if (v->ref > 1)
{
deref_array(v);
v = slice_array(v, 0, (mp_int)VEC_SIZE(v)-1 );
}
sp--;
v_old = argp->u.vec;
v = subtract_array(v_old, v);
argp->u.vec = v;
put_ref_array(sp, v);
break;
}
case T_FLOAT: /* Subtract from a float */
if (type2 == T_FLOAT)
{
STORE_DOUBLE_USED
double d;
/* don't use the address of u2, this would prevent putting it
* in a register
*/
sp--;
d = READ_DOUBLE(argp) - READ_DOUBLE(sp);
if (d < (-DBL_MAX) || d > DBL_MAX)
ERRORF(("Numeric overflow: %g + %g\n"
, READ_DOUBLE(argp), READ_DOUBLE(sp)));
STORE_DOUBLE(argp, d);
*sp = *argp;
}
else if (type2 == T_NUMBER)
{
STORE_DOUBLE_USED
double d;
sp--;
d = READ_DOUBLE(argp) - (double)sp->u.number;
if (d < (-DBL_MAX) || d > DBL_MAX)
ERRORF(("Numeric overflow: %g + %ld\n"
, READ_DOUBLE(argp), (long)sp->u.number));
STORE_DOUBLE(argp, d);
*sp = *argp;
}
else
{
goto bad_right;
}
break;
case T_MAPPING: /* Subtract from a mapping */
if (type2 == T_MAPPING)
{
mapping_t *m;
sp--;
m = sp->u.map;
check_map_for_destr(m);
/* Test for the special case 'm - m' */
if (m == argp->u.map)
{
/* m->ref is > 1, because the content of the lvalue is
* associated with a ref
*/
deref_mapping(m);
m = copy_mapping(m);
}
walk_mapping(m, sub_from_mapping_filter, argp->u.map);
free_mapping(m);
sp->u.map = ref_mapping(argp->u.map);
}
else
{
goto bad_right;
}
break;
default:
goto bad_left;
} /* end of switch */
break;
}
CASE(F_MULT_EQ); /* --- mult_eq --- */
{
/* Multiply sp[-1] to the value designated by lvalue sp[0],
* assign the result to sp[0] and also leave it on the stack.
*
* Possible type combinations:
* int * int -> int
* float * (float,int) -> float
* string * int -> string
* array * int -> array
*
* TODO: Extend this to mappings.
*/
svalue_t *argp;
#ifdef DEBUG
if (sp->type != T_LVALUE)
goto bad_arg_1;
#endif
/* Set argp to the actual value designated by sp[0] */
for ( argp = sp->u.lvalue
; T_LVALUE == argp->type || T_PROTECTED_LVALUE == argp->type
; argp = argp->u.lvalue)
NOOP;
/* Now do it */
if (argp->type == T_NUMBER)
{
sp--;
if (sp->type != T_NUMBER)
goto bad_right;
{
p_int left = argp->u.number;
p_int right = sp->u.number;
if (left > 0 && right > 0)
{
if ((left != 0 && PINT_MAX / left < right)
|| (right != 0 && PINT_MAX / right < left)
)
{
ERRORF(("Numeric overflow: %ld *= %ld\n"
, (long)left, (long)right));
/* NOTREACHED */
break;
}
}
else if (left < 0 && right < 0)
{
if ((left != 0 && PINT_MAX / left > right)
|| (right != 0 && PINT_MAX / right > left)
)
{
ERRORF(("Numeric overflow: %ld *= %ld\n"
, (long)left, (long)right));
/* NOTREACHED */
break;
}
}
else if (left != 0 && right != 0)
{
if ((left > 0 && PINT_MIN / left > right)
|| (right > 0 && PINT_MIN / right > left)
)
{
ERRORF(("Numeric overflow: %ld *= %ld\n"
, (long)left, (long)right));
/* NOTREACHED */
break;
}
}
}
sp->u.number = argp->u.number *= sp->u.number;
break;
}
if (argp->type == T_FLOAT)
{
STORE_DOUBLE_USED
double d;
sp--;
if (sp->type == T_FLOAT)
{
d = READ_DOUBLE(argp) * READ_DOUBLE(sp);
if (d < (-DBL_MAX) || d > DBL_MAX)
ERRORF(("Numeric overflow: %g * %g\n"
, READ_DOUBLE(argp), READ_DOUBLE(sp)));
STORE_DOUBLE(argp, d);
*sp = *argp;
}
else if (sp->type == T_NUMBER)
{
d = READ_DOUBLE(argp) * (double)sp->u.number;
if (d < (-DBL_MAX) || d > DBL_MAX)
ERRORF(("Numeric overflow: %g * %ld\n"
, READ_DOUBLE(argp), (long)sp->u.number));
STORE_DOUBLE(argp, d);
*sp = *argp;
}
else
goto bad_right;
break;
}
if (argp->type == T_STRING)
{
char * result;
size_t reslen;
size_t len;
sp--;
if (sp->type != T_NUMBER || sp->u.number < 0)
goto bad_right;
len = svalue_strlen(argp);
if (len > (size_t)PINT_MAX
|| (len != 0 && PINT_MAX / len < sp->u.number)
|| (sp->u.number != 0 && PINT_MAX / sp->u.number < len)
)
ERROR("Result string too long.\n");
reslen = (size_t)sp->u.number * len;
result = xalloc(reslen+1);
if (!result)
ERROR("Out of memory.\n");
if (sp->u.number > 0 && len)
{
size_t curlen;
/* Seed result[] with one copy of the string */
memcpy(result, argp->u.string, len);
/* Repeatedly double the string in result */
curlen = len;
while (2*curlen < reslen)
{
memcpy(result+curlen, result, curlen);
curlen *= 2;
}
/* Fill up result to the full length */
if (reslen > curlen)
memcpy(result+curlen, result, reslen-curlen);
}
result[reslen] = '\0';
DYN_STRING_COST(reslen)
free_string_svalue(argp);
put_malloced_string(argp, result);
assign_svalue_no_free(sp, argp);
break;
}
if (argp->type == T_CHAR_LVALUE)
{
sp--;
if (sp->type != T_NUMBER)
goto bad_right;
{
p_int left = *argp->u.string;
p_int right = sp->u.number;
if (left > 0 && right > 0)
{
if ((left != 0 && PINT_MAX / left < right)
|| (right != 0 && PINT_MAX / right < left)
)
{
ERRORF(("Numeric overflow: %ld *= %ld\n"
, (long)left, (long)right));
/* NOTREACHED */
break;
}
}
else if (left < 0 && right < 0)
{
if ((left != 0 && PINT_MAX / left > right)
|| (right != 0 && PINT_MAX / right > left)
)
{
ERRORF(("Numeric overflow: %ld *= %ld\n"
, (long)left, (long)right));
/* NOTREACHED */
break;
}
}
else if (left != 0 && right != 0)
{
if ((left > 0 && PINT_MIN / left > right)
|| (right > 0 && PINT_MIN / right > left)
)
{
ERRORF(("Numeric overflow: %ld *= %ld\n"
, (long)left, (long)right));
/* NOTREACHED */
break;
}
}
}
if (((*argp->u.string * sp->u.number) & 0xff) == 0)
ERROR("Can't set string character to 0.\n");
sp->u.number = *argp->u.string *= sp->u.number;
break;
}
if (argp->type == T_POINTER)
{
vector_t *result;
mp_int reslen;
size_t len;
sp--;
if (sp->type != T_NUMBER || sp->u.number < 0)
goto bad_right;
inter_sp = sp;
inter_pc = pc;
len = VEC_SIZE(argp->u.vec);
reslen = sp->u.number * (mp_int)len;
result = allocate_uninit_array(reslen);
if (sp->u.number > 0 && len)
{
size_t left;
svalue_t *from, *to;
/* Seed result[] with one copy of the array.
* To save memory we make sure that all strings
* are made shared.
*/
for ( from = argp->u.vec->item, to = result->item, left = len
; left
; from++, to++, left--)
{
if (from->type == T_STRING
&& from->x.string_type == STRING_MALLOC
)
{
put_string(to, make_shared_string(from->u.string));
if (!to->u.string)
ERROR("Out of memory.\n");
}
else
assign_svalue_no_free(to, from);
} /* for() seed */
/* Now fill the remainder of the vector with
* the values already copied in there.
*/
for (from = result->item, left = reslen - len
; left
; to++, from++, left--
)
assign_svalue_no_free(to, from);
} /* if (len) */
free_svalue(argp);
put_array(argp, result);
assign_svalue_no_free(sp, argp);
break;
}
goto bad_left;
/* NOTREACHED */ break;
}
CASE(F_DIV_EQ); /* --- div_eq --- */
{
/* Divide the value designated by lvalue sp[0] by sp[-1],
* assign the result to sp[0] and also leave it on the stack.
*
* Possible type combinations:
* int / int -> int
* float / (float,int) -> float
*
* TODO: Extend this to arrays and mappings.
*/
svalue_t *argp;
#ifdef DEBUG
if (sp->type != T_LVALUE)
goto bad_arg_1;
#endif
/* Set argp to the actual value designated by sp[0] */
for ( argp = sp->u.lvalue
; T_LVALUE == argp->type || T_PROTECTED_LVALUE == argp->type
; argp = argp->u.lvalue)
NOOP;
/* Now do it */
if (argp->type == T_NUMBER)
{
sp--;
if (sp->type != T_NUMBER)
goto bad_right;
if (sp->u.number == 0)
ERROR("Division by 0\n")
if (argp->u.number == PINT_MIN && sp->u.number == -1)
ERRORF(("Numeric overflow: %ld / -1\n"
, (long)argp->u.number
));
sp->u.number = argp->u.number /= sp->u.number;
break;
}
if (argp->type == T_CHAR_LVALUE)
{
sp--;
if (sp->type != T_NUMBER)
goto bad_right;
if (sp->u.number == 0)
ERROR("Division by 0\n")
if (((*argp->u.string / sp->u.number) & 0xff) == 0)
ERROR("Can't set string character to 0.\n");
sp->u.number = *argp->u.string /= sp->u.number;
break;
}
if (argp->type == T_FLOAT)
{
STORE_DOUBLE_USED
double d;
sp--;
if (sp->type == T_FLOAT)
{
d = READ_DOUBLE(sp);
if (d == 0.0)
ERROR("Division by 0\n")
d = READ_DOUBLE(argp) / d;
if (d < (-DBL_MAX) || d > DBL_MAX)
ERRORF(("Numeric overflow: %g / %g\n"
, READ_DOUBLE(argp), READ_DOUBLE(sp)));
STORE_DOUBLE(argp, d);
*sp = *argp;
}
else if (sp->type == T_NUMBER)
{
p_int i;
i = sp->u.number;
if (i == 0)
ERROR("Division by 0\n")
d = READ_DOUBLE(argp) / (double)i;
if (d < (-DBL_MAX) || d > DBL_MAX)
ERRORF(("Numeric overflow: %g / %ld\n"
, READ_DOUBLE(argp), (long)sp->u.number));
STORE_DOUBLE(argp, d);
*sp = *argp;
}
else
goto bad_right;
break;
}
goto bad_left;
/* NOTREACHED */ break;
}
CASE(F_MOD_EQ); /* --- mod_eq --- */
{
/* Compute the modulus of the value designated by lvalue sp[0]
* divided by sp[-1], assign the result to sp[0] and also
* leave it on the stack.
*
* Possible type combinations:
* int % int -> int
*
* TODO: Extend this to arrays and mappings.
* TODO: Implement the other remainder function.
*/
svalue_t *argp;
#ifdef DEBUG
if (sp->type != T_LVALUE)
goto bad_arg_1;
#endif
/* Set argp to the actual value designated by sp[0] */
for ( argp = sp->u.lvalue
; T_LVALUE == argp->type || T_PROTECTED_LVALUE == argp->type
; argp = argp->u.lvalue)
NOOP;
/* Now do it */
if (argp->type == T_NUMBER)
{
sp--;
if (sp->type != T_NUMBER)
goto bad_right;
if (sp->u.number == 0)
ERROR("Division by 0\n")
sp->u.number = argp->u.number %= sp->u.number;
break;
}
if (argp->type == T_CHAR_LVALUE)
{
sp--;
if (sp->type != T_NUMBER)
goto bad_right;
if (sp->u.number == 0)
ERROR("Division by 0\n")
if (((*argp->u.string % sp->u.number) & 0xff) == 0)
ERROR("Can't set string character to 0.\n");
sp->u.number = *argp->u.string %= sp->u.number;
break;
}
goto bad_left;
/* NOTREACHED */ break;
}
CASE(F_AND_EQ); /* --- and_eq --- */
{
/* Intersect the value designated by lvalue sp[0] with sp[-1],
* assign the result to sp[0] and also leave it on the stack.
*
* Possible type combinations:
* int & int -> int
* string & string -> string
* array & array -> array
*
* TODO: Extend this to mappings.
*/
svalue_t *argp;
#ifdef DEBUG
if (sp->type != T_LVALUE)
goto bad_arg_1;
#endif
/* Set argp to the actual value designated by sp[0] */
for ( argp = sp->u.lvalue
; T_LVALUE == argp->type || T_PROTECTED_LVALUE == argp->type
; argp = argp->u.lvalue)
NOOP;
/* Now do it */
if (argp->type == T_NUMBER) /* Intersect a number */
{
if (sp[-1].type != T_NUMBER)
goto bad_right;
sp--;
sp->u.number = argp->u.number &= sp->u.number;
break;
}
if (argp->type == T_POINTER && sp[-1].type == T_POINTER)
{
/* Intersect an array */
vector_t *vec1, *vec2;
inter_sp = sp - 2;
vec1 = argp->u.vec;
vec2 = sp[-1].u.vec;
argp->type = T_NUMBER;
vec1 = intersect_array(vec1, vec2);
put_ref_array(argp, vec1);
sp--;
sp->u.vec = argp->u.vec;
free_svalue(sp+1);
break;
}
if (argp->type == T_STRING && (sp-1)->type == T_STRING)
{
char * result;
inter_sp = sp;
result = intersect_strings(argp->u.string, (sp-1)->u.string, MY_FALSE);
free_string_svalue(argp);
put_malloced_string(argp, result);
free_svalue(sp);
sp--;
free_string_svalue(sp);
put_malloced_string(sp, string_copy(result));
break;
}
if (argp->type == T_CHAR_LVALUE)
{
if (sp[-1].type != T_NUMBER)
goto bad_right;
sp--;
if (((*argp->u.string & sp->u.number) & 0xff) == 0)
ERROR("Can't set string character to 0.\n");
sp->u.number = *argp->u.string &= sp->u.number;
break;
}
goto bad_left;
/* NOTREACHED */ break;
}
CASE(F_OR_EQ); /* --- or_eq --- */
{
/* Binary-Or the value designated by lvalue sp[0] with sp[-1],
* assign the result to sp[0] and also leave it on the stack.
*
* Possible type combinations:
* int & int -> int
*
* TODO: Extend this to mappings and arrays.
*/
svalue_t *argp;
#ifdef DEBUG
if (sp->type != T_LVALUE)
goto bad_arg_1;
#endif
/* Set argp to the actual value designated by sp[0] */
for ( argp = sp->u.lvalue
; T_LVALUE == argp->type || T_PROTECTED_LVALUE == argp->type
; argp = argp->u.lvalue)
NOOP;
/* Now do it */
if (argp->type == T_NUMBER)
{
sp--;
if (sp->type != T_NUMBER)
goto bad_right;
sp->u.number = argp->u.number |= sp->u.number;
break;
}
if (argp->type == T_CHAR_LVALUE)
{
if (sp[-1].type != T_NUMBER)
goto bad_right;
sp--;
if (((*argp->u.string | sp->u.number) & 0xff) == 0)
ERROR("Can't set string character to 0.\n");
sp->u.number = *argp->u.string |= sp->u.number;
break;
}
goto bad_left;
/* NOTREACHED */ break;
}
CASE(F_XOR_EQ); /* --- xor_eq --- */
{
/* Binary-XOr the value designated by lvalue sp[0] with sp[-1],
* assign the result to sp[0] and also leave it on the stack.
*
* Possible type combinations:
* int ^ int -> int
*
* TODO: Extend this to mappings and arrays.
*/
svalue_t *argp;
#ifdef DEBUG
if (sp->type != T_LVALUE)
goto bad_arg_1;
#endif
/* Set argp to the actual value designated by sp[0] */
for ( argp = sp->u.lvalue
; T_LVALUE == argp->type || T_PROTECTED_LVALUE == argp->type
; argp = argp->u.lvalue)
NOOP;
/* Now do it */
if (argp->type == T_NUMBER)
{
sp--;
if (sp->type != T_NUMBER)
goto bad_right;
sp->u.number = argp->u.number ^= sp->u.number;
break;
}
if (argp->type == T_CHAR_LVALUE)
{
if (sp[-1].type != T_NUMBER)
goto bad_right;
sp--;
if (((*argp->u.string ^ sp->u.number) & 0xff) == 0)
ERROR("Can't set string character to 0.\n");
sp->u.number = *argp->u.string ^= sp->u.number;
break;
}
goto bad_left;
/* NOTREACHED */ break;
}
CASE(F_LSH_EQ); /* --- lsh_eq --- */
{
/* Shift the value designated by lvalue sp[0] left by sp[-1],
* assign the result to sp[0] and also leave it on the stack.
*
* Possible type combinations:
* int << int -> int
*
* TODO: Implement an arithmetic shift.
*/
int i;
svalue_t *argp;
#ifdef DEBUG
if (sp->type != T_LVALUE)
goto bad_arg_1;
#endif
/* Set argp to the actual value designated by sp[0] */
for ( argp = sp->u.lvalue
; T_LVALUE == argp->type || T_PROTECTED_LVALUE == argp->type
; argp = argp->u.lvalue)
NOOP;
/* Now do it */
if (argp->type == T_NUMBER)
{
sp--;
if (sp->type != T_NUMBER)
goto bad_right;
i = sp->u.number;
argp->u.number <<= (uint)i > MAX_SHIFT ? MAX_SHIFT : i;
sp->u.number = argp->u.number;
break;
}
if (argp->type == T_CHAR_LVALUE)
{
if (sp[-1].type != T_NUMBER)
goto bad_right;
sp--;
i = sp->u.number;
if (((*argp->u.string << ((uint)i > MAX_SHIFT ? MAX_SHIFT : i))
& 0xff) == 0)
ERROR("Can't set string character to 0.\n");
*argp->u.string <<= (uint)i > MAX_SHIFT ? MAX_SHIFT : i;
sp->u.number = *argp->u.string;
break;
}
goto bad_left;
/* NOTREACHED */ break;
}
CASE(F_RSH_EQ); /* --- rsh_eq --- */
{
/* Arithmetically shift the value designated by lvalue sp[0] right by
* sp[-1], assign the result to sp[0] and also leave it on the stack.
*
* Possible type combinations:
* int << int -> int
*/
int i;
svalue_t *argp;
#ifdef DEBUG
if (sp->type != T_LVALUE)
goto bad_arg_1;
#endif
/* Set argp to the actual value designated by sp[0] */
for ( argp = sp->u.lvalue
; T_LVALUE == argp->type || T_PROTECTED_LVALUE == argp->type
; argp = argp->u.lvalue)
NOOP;
/* Now do it */
if (argp->type == T_NUMBER)
{
sp--;
if (sp->type != T_NUMBER)
goto bad_right;
i = sp->u.number;
argp->u.number >>= (uint)i > MAX_SHIFT ? (MAX_SHIFT+1) : i;
sp->u.number = argp->u.number;
break;
}
if (argp->type == T_CHAR_LVALUE)
{
if (sp[-1].type != T_NUMBER)
goto bad_right;
sp--;
i = sp->u.number;
if (((*argp->u.string >> ((uint)i > MAX_SHIFT ? MAX_SHIFT : i))
& 0xff) == 0)
ERROR("Can't set string character to 0.\n");
*argp->u.string >>= (uint)i > MAX_SHIFT ? MAX_SHIFT : i;
sp->u.number = *argp->u.string;
break;
}
goto bad_left;
/* NOTREACHED */ break;
}
CASE(F_RSHL_EQ); /* --- rshl_eq --- */
{
/* Logically shift the value designated by lvalue sp[0] right by
* sp[-1], assign the result to sp[0] and also leave it on the stack.
*
* Possible type combinations:
* int << int -> int
*/
int i;
svalue_t *argp;
#ifdef DEBUG
if (sp->type != T_LVALUE)
goto bad_arg_1;
#endif
/* Set argp to the actual value designated by sp[0] */
for ( argp = sp->u.lvalue
; T_LVALUE == argp->type || T_PROTECTED_LVALUE == argp->type
; argp = argp->u.lvalue)
NOOP;
/* Now do it */
if (argp->type == T_NUMBER)
{
sp--;
if (sp->type != T_NUMBER)
goto bad_right;
i = sp->u.number;
if ((uint)i > MAX_SHIFT)
argp->u.number = 0;
else
argp->u.number = (p_uint)argp->u.number >> i;
sp->u.number = argp->u.number;
break;
}
if (argp->type == T_CHAR_LVALUE)
{
if (sp[-1].type != T_NUMBER)
goto bad_right;
sp--;
i = sp->u.number;
if ((uint)i > MAX_SHIFT
|| ((*argp->u.string >> i) & 0xff) == 0)
ERROR("Can't set string character to 0.\n");
*argp->u.string = (p_uint)*argp->u.string >> i;
sp->u.number = *argp->u.string;
break;
}
goto bad_left;
/* NOTREACHED */ break;
}
/* --- Machine internal instructions --- */
CASE(F_POP_VALUE); /* --- pop_value --- */
/* Pop the topmost value from the stack (freeing it).
* Simple, huh?
*/
pop_stack();
break;
CASE(F_DUP); /* --- dup --- */
/* Push a duplicate of sp[0] onto the stack.
*/
sp++;
assign_svalue_no_free(sp, sp-1);
break;
CASE(F_LDUP); /* --- ldup --- */
{
/* Push a duplicate of sp[0] onto the stack.
* If sp[0] is an lvalue, it is derefenced first.
*/
svalue_t * svp = sp;
sp++;
while (svp->type == T_LVALUE || svp->type == T_PROTECTED_LVALUE)
svp = svp->u.lvalue;
assign_svalue_no_free(sp, svp);
break;
}
CASE(F_SWAP_VALUES); /* --- swap_values --- */
{
/* Swap sp[0] and sp[-1] on the stack.
*/
svalue_t sv = sp[0];
sp[0] = sp[-1];
sp[-1] = sv;
break;
}
CASE(F_CLEAR_LOCALS); /* --- clear_locals <first> <num> --- */
{
/* Set the local variables <first> .. <first>+<num>-1 back
* to svalue-0. This is used to initalize local variables
* of nested scopes.
*/
int first, num;
svalue_t *plocal;
first = LOAD_UINT8(pc);
num = LOAD_UINT8(pc);
for (plocal = fp+first; num > 0; num--, plocal++)
{
free_svalue(plocal);
*plocal = const0;
}
break;
}
CASE(F_LBRANCH); /* --- lbranch <offset> --- */
{
/* Jump by (16-Bit) short <offset> bytes.
* The <offset> is counted from its first byte (TODO: Ugh).
*/
short offset;
GET_SHORT(offset, pc);
pc += offset;
break;
}
CASE(F_LBRANCH_WHEN_ZERO); /* --- lbranch_when_zero <offset> --- */
{
/* Jump by (16-Bit) short <offset> bytes if sp[0] is number 0.
* The <offset> is counted from its first byte (TODO: Ugh).
* sp[0] is popped from the stack.
*/
short offset;
if (sp->type == T_NUMBER && sp->u.number == 0)
{
GET_SHORT(offset, pc);
pc += offset;
sp--;
break;
}
pc += 2;
pop_stack();
break;
}
CASE(F_LBRANCH_WHEN_NON_ZERO); /* --- lbranch_when_non_zero <offset> --- */
{
/* Jump by (16-Bit) short <offset> bytes if sp[0] is not number 0.
* The <offset> is counted from its first byte (TODO: Ugh).
* sp[0] is popped from the stack.
*/
short offset;
if (sp->type != T_NUMBER || sp->u.number != 0)
{
GET_SHORT(offset, pc);
pc += offset;
pop_stack();
break;
}
pc += 2;
sp--;
break;
}
CASE(F_BRANCH); /* --- branch <offset> --- */
{
/* Jump forward by uint8 <offset> bytes.
* The <offset> is counted from the next instruction.
*/
pc += GET_UINT8(pc)+1;
break;
}
CASE(F_BRANCH_WHEN_ZERO); /* --- branch_when_zero <offset> --- */
{
/* Jump forward by uint8 <offset> bytes if sp[0] is number 0.
* The <offset> is counted from the next instruction.
* sp[0] is popped from the stack.
*/
if (sp->type == T_NUMBER)
{
if (sp->u.number == 0)
{
sp--;
pc += GET_UINT8(pc) + 1;
break;
}
sp--;
pc++;
break;
}
else
{
free_svalue(sp);
sp--;
pc++;
break;
}
}
CASE(F_BRANCH_WHEN_NON_ZERO); /* --- branch_when_non_zero <offset> --- */
{
/* Jump forward by uint8 <offset> bytes if sp[0] is not number 0.
* The <offset> is counted from the next instruction.
* sp[0] is popped from the stack.
*/
if (sp->type == T_NUMBER)
{
if (sp->u.number == 0)
{
sp--;
pc++;
break;
}
}
else
{
free_svalue(sp);
}
sp--;
pc += GET_UINT8(pc) + 1;
break;
}
CASE(F_BBRANCH_WHEN_ZERO); /* --- bbranch_when_zero <offset> --- */
{
/* Jump backward by uint8 <offset> bytes if sp[0] is number 0.
* The <offset> is counted from its first byte (TODO: Ugh).
* sp[0] is popped from the stack.
*/
if (sp->type == T_NUMBER && sp->u.number == 0)
{
sp--;
pc -= GET_UINT8(pc);
break;
}
pc += 1;
pop_stack();
break;
}
CASE(F_BBRANCH_WHEN_NON_ZERO); /* --- branch_when_non_zero <offset> --- */
{
/* Jump backward by uint8 <offset> bytes if sp[0] is not number 0.
* The <offset> is counted from its first byte (TODO: Ugh).
* sp[0] is popped from the stack.
*/
if (sp->type == T_NUMBER)
{
if (sp->u.number == 0)
{
pc += 1;
sp--;
break;
}
}
else
free_svalue(sp);
sp--;
pc -= GET_UINT8(pc);
break;
}
/* --- call_function_by_address <index> <num> --- */
CASE(F_CALL_FUNCTION_BY_ADDRESS);
{
/* Call the function <index> with <num> args on the stack.
* <index> is a (16-Bit) unsigned short, giving the index within
* the programs function table. <num> a uint8.
*
* Since the function may be redefined through inheritance, the
* function must be searched in the current_objects program, which
* might not be the current_program.
*
* The code is used to implement calls to non-private functions.
*/
unsigned short func_index; /* function index within program */
unsigned short func_offset;
/* function index within the current object's program.
* This way local function may be redefined through inheritance.
*/
funflag_t flags; /* the function flags */
fun_hdr_p funstart; /* the actual function (code) */
/* Get the function's index */
LOAD_SHORT(func_index, pc);
func_offset = (unsigned short)(func_index + function_index_offset);
/* Find the function in the function table. As the function may have
* been redefined by inheritance, we must look in the last table,
* which is pointed to by current_object.
*/
#ifdef DEBUG
if (func_offset >= current_object->prog->num_functions)
fatal("call_function_by_address: "
"Illegal function index: offset %hu (index %hu), %d functions\n"
, func_offset, func_index
, current_object->prog->num_functions);
#endif
/* NOT current_prog, which can be an inherited object. */
flags = current_object->prog->functions[func_offset];
/* If the function was cross-defined, get the real offset */
if (flags & NAME_CROSS_DEFINED)
{
func_offset += CROSSDEF_NAME_OFFSET(flags);
}
/* Save all important global stack machine registers */
push_control_stack(sp, pc+1, fp);
/* Set the current program back to the objects program _after_
* the control stack push, since here is where we search for
* the function.
*/
current_prog = current_object->prog;
/* Search for the function definition and determine the offsets.
*/
csp->num_local_variables = GET_UINT8(pc);
flags = setup_new_frame1(func_offset, 0, 0);
funstart = (fun_hdr_p)(current_prog->program + (flags & FUNSTART_MASK));
csp->funstart = funstart;
/* Setup the stack, arguments and local vars */
sp = setup_new_frame2(funstart, sp, MY_FALSE, MY_FALSE);
/* Finish the setup */
#ifdef DEBUG
if (!current_object->variables && variable_index_offset)
fatal("%s Fatal: call function for object %p '%s' w/o variables, "
"but offset %d\n"
, time_stamp(), current_object, current_object->name
, variable_index_offset);
#endif
current_variables = current_object->variables;
if (current_variables)
current_variables += variable_index_offset;
current_strings = current_prog->strings;
fp = inter_fp;
pc = FUNCTION_CODE(funstart);
csp->extern_call = MY_FALSE;
break;
}
/* --- call_explicit_inherited <prog> <index> <num> --- */
CASE(F_CALL_EXPLICIT_INHERITED);
{
/* Call the (inherited) function <index> in program <prog> with
* <num> arguments on the stack.
*
* <index> is a (16-Bit) unsigned short, giving the index within
* the programs function table.
* <prog> is a (16-Bit) unsigned short, giving the index within
* the current programs inherit table.
* <num> a uint8.
*/
unsigned short prog_index; /* Index within the inherit table */
unsigned short func_index; /* Index within the function table */
funflag_t flags; /* the functions flags */
fun_hdr_p funstart; /* the actual function (code) */
inherit_t *inheritp; /* the inheritance descriptor */
/* Get the program and function index, and determine the
* inheritance descriptor
*/
LOAD_SHORT(prog_index, pc);
LOAD_SHORT(func_index, pc);
inheritp = ¤t_prog->inherit[prog_index];
#ifdef DEBUG
if (func_index >= inheritp->prog->num_functions)
{
fatal("call_explicit_inherited: Illegal function index: "
"program %d, func %d, %d functions\n"
, prog_index, func_index, inheritp->prog->num_functions);
}
#endif
/* Save all important global stack machine registers */
push_control_stack(sp, pc+1, fp);
/* If we do an explicit call into a virtually inherited base class we
* have to find the first instance of the inherited variables.
* This cannot be done at compile time because it depends on the
* _object_ (i.e. the runtime environment) in which current_prog
* is running.
* TODO: A better compiler might do some backpatching and at least
* TODO:: leave hints where the variables are, so that we can omit
* TODO:: the explicite search. Or some load-time patching.
*/
if (current_prog != current_object->prog
&& inheritp->prog->num_variables
&& (current_prog->variable_names[inheritp->variable_index_offset
+inheritp->prog->num_variables-1
].flags & TYPE_MOD_VIRTUAL)
&& !(inheritp->prog->variable_names[inheritp->prog->num_variables-1
].flags & TYPE_MOD_VIRTUAL)
)
{
/* Now search for the first virtual inheritance of the program
* in the inherit list of the topmost program.
* Don't get confused by normal inherits, though.
*/
int i = current_object->prog->num_inherited;
inherit_t *inh = current_object->prog->inherit;
while (i)
{
if (inh->prog == inheritp->prog
&& current_object->prog
->variable_names[inh->variable_index_offset
+inh->prog->num_variables-1
].flags&TYPE_MOD_VIRTUAL
)
break;
inh++;
i--;
}
if (i)
{
/* found, so adjust the inheritp and the offsets
* to start with
*/
inheritp = inh;
current_variables = current_object->variables;
function_index_offset = 0;
}
#ifdef DEBUG
else { /* this shouldn't happen! */
char *ts;
ts = time_stamp();
fprintf(stderr,
"%s Adjusting variable offsets because of virtual "
"inheritance for call\n"
"%s from %s into %s (topmost program %s) FAILED.\n"
"%s Please check the inherit tree and report it.\n"
, ts, ts
, current_prog->name, inheritp->prog->name
, current_object->prog->name
, ts);
}
#endif
}
/* Set the current program to the inherited program _after_
* the control stack push, since there is where we search for
* the function.
*/
current_prog = inheritp->prog;
/* Search for the function definition and determine the offsets.
*/
csp->num_local_variables = EXTRACT_UCHAR(pc);
flags = setup_new_frame1(
func_index,
function_index_offset + inheritp->function_index_offset,
inheritp->variable_index_offset
);
funstart = (fun_hdr_p)(current_prog->program + (flags & FUNSTART_MASK));
csp->funstart = funstart;
/* Setup the stack, arguments and local vars */
sp = setup_new_frame2(funstart, sp, MY_FALSE, MY_FALSE);
/* Finish the setup */
fp = inter_fp;
pc = FUNCTION_CODE(funstart);
current_variables += variable_index_offset;
current_strings = current_prog->strings;
csp->extern_call = MY_FALSE;
break;
}
CASE(F_PUSH_IDENTIFIER_LVALUE); /* --- push_identifier_lvalue <num> --- */
/* Push an lvalue onto the stack pointing to object-global variable
* <num>.
*
* <num> is an uint8 and used as index in the current objects
* variable table.
*/
sp++;
sp->type = T_LVALUE;
sp->u.lvalue = find_value((int)(LOAD_UINT8(pc) ));
break;
CASE(F_VIRTUAL_VARIABLE); /* --- virtual_variable <num> --- */
{
/* Push the virtual object-global variable <num> onto the stack.
* It is possible that it is a variable that points to
* a destructed object. In that case, it has to be replaced by 0.
*
* <num> is an uint8 and used as index in the current objects
* variable table.
*/
svalue_t * val = find_virtual_value((int)(LOAD_UINT8(pc)));
sp++;
assign_checked_svalue_no_free(sp, val, sp, pc);
break;
}
/* --- push_virtual_variable_lvalue <num> --- */
CASE(F_PUSH_VIRTUAL_VARIABLE_LVALUE);
/* Push an lvalue onto the stack pointing to virtual object-global
* variable <num>.
*
* <num> is an uint8 and used as index in the current objects
* variable table.
*/
sp++;
sp->type = T_LVALUE;
sp->u.lvalue = find_virtual_value((int)(LOAD_UINT8(pc) ));
break;
#ifdef F_IDENTIFIER16
CASE(F_IDENTIFIER16); /* --- identifier16 <var_ix> --- */
{
/* Push value of object variable <var_ix>.
* It is possible that it is a variable that points to
* a destructed object. In that case, it has to be replaced by 0.
*
* <var_ix> is a (16-Bit) unsigned short.
*/
unsigned short var_index;
LOAD_SHORT(var_index, pc);
sp++;
assign_checked_svalue_no_free(sp, find_value((int)var_index), sp, pc);
break;
}
/* --- push_identifier16_lvalue <var_ix> --- */
CASE(F_PUSH_IDENTIFIER16_LVALUE);
{
/* Push an lvalue onto the stack pointing to object-global variable
* <num>.
*
* <num> is an uint8 and used as index in the current objects
* variable table.
*/
unsigned short var_index;
LOAD_SHORT(var_index, pc);
sp++;
sp->type = T_LVALUE;
sp->u.lvalue = find_value((int)var_index);
break;
}
#endif /* F_IDENTIFIER16 */
/* --- push_local_variable_lvalue <num> --- */
CASE(F_PUSH_LOCAL_VARIABLE_LVALUE);
/* Push an lvalue onto the stack pointing to local variable <num>.
*
* <num> is an uint8 and used as index onto the framepointer.
*/
sp++;
sp->type = T_LVALUE;
sp->u.lvalue = fp + LOAD_UINT8(pc);
break;
CASE(F_PUSH_INDEXED_LVALUE); /* --- push_indexed_lvalue --- */
/* Operator F_PUSH_INDEXED_LVALUE(vector v=sp[-1], int i=sp[0])
* Operator F_PUSH_INDEXED_LVALUE(mapping v=sp[-1], mixed i=sp[0])
*
* Compute the lvalue &(v[i]) and push it into the stack. If v has
* just one ref left, the indexed item is stored in indexing_quickfix
* and the lvalue refers to that variable.
*/
sp = push_indexed_lvalue(sp, pc);
break;
CASE(F_PUSH_RINDEXED_LVALUE); /* --- push_rindexed_lvalue --- */
/* Operator F_PUSH_RINDEXED_LVALUE(vector v=sp[-1], int i=sp[0])
*
* Compute the lvalue &(v[<i]) and push it into the stack. If v has
* just one ref left, the indexed item is stored in indexing_quickfix
* and the lvalue refers to that variable.
*/
sp = push_rindexed_lvalue(sp, pc);
break;
CASE(F_INDEX_LVALUE); /* --- index_lvalue --- */
/* Operator F_INDEX_LVALUE (string|vector &v=sp[0], int i=sp[-1])
* F_INDEX_LVALUE (mapping &v=sp[0], mixed i=sp[-1])
*
* Compute the index &(v[i]) of lvalue <v> and push it into the stack.
* The computed index is a lvalue itself. If <v> is a string-lvalue,
* it is made a malloced string if necessary, and the pushed result
* will be a lvalue pointing to a CHAR_LVALUE stored in
* <special_lvalue>.
*/
sp = index_lvalue(sp, pc);
break;
CASE(F_RINDEX_LVALUE); /* --- rindex_lvalue --- */
/* Operator F_RINDEX_LVALUE (string|vector &v=sp[0], int i=sp[-1])
*
* Compute the index &(v[<i]) of lvalue <v> and push it into the
* stack. The computed index is a lvalue itself.
* If <v> is a string-lvalue, it is made a malloced string if
* necessary, and the pushed result will be a lvalue pointing to a
* CHAR_LVALUE stored in <special_lvalue>.
*/
sp = rindex_lvalue(sp, pc);
break;
CASE(F_INDEX); /* --- index --- */
/* Operator F_INDEX (string|vector v=sp[0], int i=sp[-1])
* F_INDEX (mapping v=sp[0], mixed i=sp[-1])
*
* Compute the value (v[i]) and push it onto the stack. If the value
* would be a destructed object, 0 is pushed onto the stack and the
* ref to the object is removed from the vector/mapping.
*
* Mapping indices may use <indexing_quickfix> for temporary storage.
*/
sp = push_indexed_value(sp, pc);
break;
CASE(F_RINDEX); /* --- rindex --- */
/* Operator F_RINDEX (string|vector v=sp[0], int i=sp[-1])
*
* Compute the value (v[<i]) and push it onto the stack. If the value
* would be a destructed object, 0 is pushed onto the stack and the
* ref to the object is removed from the vector/mapping.
*/
sp = push_rindexed_value(sp, pc);
break;
CASE(F_RANGE_LVALUE); /* --- range_lvalue --- */
/* Operator F_RANGE_LVALUE (string|vector &v=sp[0]
* , int i2=sp[-1], i1=sp[-2])
*
* Compute the range &(v[i1..i2]) of lvalue <v> and push it into the
* stack. The value pushed is a lvalue pointing to <special_lvalue>.
* <special_lvalue> then is the POINTER_RANGE_- resp.
* STRING_RANGE_LVALUE.
*
* TODO: Four different instructions for this? A single instruction plus
* TODO:: argument would be as well.
*/
inter_pc = pc;
sp = range_lvalue(0x000, sp);
break;
CASE(F_NR_RANGE_LVALUE); /* --- nr_range_lvalue --- */
/* Operator F_NR_RANGE_LVALUE (string|vector &v=sp[0]
* , int i2=sp[-1], i1=sp[-2])
*
* Compute the range &(v[i1..<i2]) of lvalue <v> and push it into the
* stack. The value pushed is a lvalue pointing to <special_lvalue>.
* <special_lvalue> then is the POINTER_RANGE_- resp.
* STRING_RANGE_LVALUE.
*/
inter_pc = pc;
sp = range_lvalue(0x001, sp);
break;
CASE(F_RN_RANGE_LVALUE); /* --- rn_range_lvalue --- */
/* Operator F_RN_RANGE_LVALUE (string|vector &v=sp[0]
* , int i2=sp[-1], i1=sp[-2])
*
* Compute the range &(v[<i1..i2]) of lvalue <v> and push it into the
* stack. The value pushed is a lvalue pointing to <special_lvalue>.
* <special_lvalue> then is the POINTER_RANGE_- resp.
* STRING_RANGE_LVALUE.
*/
inter_pc = pc;
sp = range_lvalue(0x100, sp);
break;
CASE(F_RR_RANGE_LVALUE); /* --- rr_range_lvalue --- */
/* Operator F_RR_RANGE_LVALUE (string|vector &v=sp[0]
* , int i2=sp[-1], i1=sp[-2])
*
* Compute the range &(v[<i1..<i2]) of lvalue <v> and push it into the
* stack. The value pushed is a lvalue pointing to <special_lvalue>.
* <special_lvalue> then is the POINTER_RANGE_- resp.
* STRING_RANGE_LVALUE.
*/
inter_pc = pc;
sp = range_lvalue(0x101, sp);
break;
CASE(F_NX_RANGE_LVALUE); /* --- nx_range_lvalue --- */
/* Operator F_NX_RANGE_LVALUE (string|vector &v=sp[0]
* , int i1=sp[-1])
*
* Compute the range &(v[i1..]) of lvalue <v> and push it into the
* stack. The value pushed is a lvalue pointing to <special_lvalue>.
* <special_lvalue> then is the POINTER_RANGE_- resp.
* STRING_RANGE_LVALUE.
*
* We implement this by pushing '1' onto the stack and then
* call F_NR_RANGE_LVALUE, effectively computing &(v[i1..<1]).
*/
inter_pc = pc;
sp++;
sp[0] = sp[-1]; /* Pull up the 'v' */
put_number(sp-1, 1); /* 'Push' the 1 for the upper bound */
sp = range_lvalue(0x001, sp);
break;
CASE(F_RX_RANGE_LVALUE); /* --- rx_range_lvalue --- */
/* Operator F_RX_RANGE_LVALUE (string|vector &v=sp[0]
* , int i1=sp[-1])
*
* Compute the range &(v[<i1..]) of lvalue <v> and push it into the
* stack. The value pushed is a lvalue pointing to <special_lvalue>.
* <special_lvalue> then is the POINTER_RANGE_- resp.
* STRING_RANGE_LVALUE.
*
* We implement this by pushing '1' onto the stack and then
* call F_RR_RANGE_LVALUE, effectively computing &(v[<i1..<1]).
*/
inter_pc = pc;
sp++;
sp[0] = sp[-1]; /* Pull up the 'v' */
put_number(sp-1, 1); /* 'Push' the 1 for the upper bound */
sp = range_lvalue(0x101, sp);
break;
CASE(F_SIMUL_EFUN); /* --- simul_efun <code> [<num_arg>] --- */
{
/* Call the simul_efun <code>. If it's a function taking varargs,
* <num_arg> gives the number of arguments, otherwise the compiler
* already took care of fixing the stack.
*
* <code> is an uint8 and indexes the function list *simul_efunp.
* <num_arg> is an uint8.
* TODO: Add a F_SIMUL_EFUN for codes > 0xff; right now this
* TODO:: is compiled as CALL_OTHER. Affected are prolang.y and
* TODO:: simul_efun.c
*/
int code; /* the function index */
fun_hdr_p funstart; /* the actual function */
object_t *ob; /* the simul_efun object */
simul_efun_table_t *entry;
ASSIGN_EVAL_COST /* we're changing objects */
/* Get the sefun code and the number of arguments on the stack */
code = (int)LOAD_UINT8(pc);
num_arg = simul_efunp[code].num_arg;
if (num_arg == SIMUL_EFUN_VARARGS
|| simul_efunp[code].flags & TYPE_MOD_XVARARGS
)
{
num_arg = (int)LOAD_UINT8(pc);
}
/* No external calls may be done when this object is destructed.
*/
if (current_object->flags & O_DESTRUCTED)
{
pop_n_elems(num_arg);
push_number(0);
break;
}
/* Make sure the simul_efun object exists; loading it when
* necessary.
*/
if ( !(ob = simul_efun_object) )
{
inter_sp = sp;
inter_pc = pc;
if ( !(ob = get_simul_efun_object()) )
{
error("Couldn't load simul_efun object\n");
}
}
/* Get the function code information */
entry = &simul_efun_table[code];
if ( NULL != (funstart = entry->funstart) )
{
/* The entry is valid: call the sefun by recursing into
* eval_instruction(), so we can get the result from the
* stack.
* We recurse because some simul_efuns are called with
* F_CALL_OTHER, and the functions should not be able
* to see any difference.
*/
program_t *prog;
svalue_t *new_sp;
push_control_stack(sp, pc, fp);
csp->ob = current_object;
csp->prev_ob = previous_ob;
csp->funstart = funstart;
csp->num_local_variables = num_arg;
current_prog = prog = entry->program;
function_index_offset = entry->function_index_offset;
#ifdef DEBUG
if (!ob->variables && entry->variable_index_offset)
fatal("%s Fatal: call sefun for object %p '%s' w/o variables, "
"but offset %ld\n"
, time_stamp(), ob, ob->name
, (long)(entry->variable_index_offset));
#endif
current_variables = ob->variables;
if (current_variables)
current_variables += entry->variable_index_offset;
new_sp = setup_new_frame2(funstart, sp, MY_TRUE, MY_FALSE);
/* The simul_efun object should not use simul_efuns itself... */
previous_ob = current_object;
current_object = ob;
current_strings = prog->strings;
eval_instruction(FUNCTION_CODE(funstart), new_sp);
sp -= num_arg - 1;
/*
* The result of the function call is on the stack.
*/
break;
}
/* The simul_efun was discarded meanwhile and not recreated
* - call it the old fashioned way with apply() in case it exists
* in a slightly different form.
*/
inter_sp = sp;
inter_pc = pc;
call_simul_efun(code, ob, num_arg);
sp = inter_sp;
/*
* The result of the function call is on the stack.
*/
break;
}
CASE(F_AGGREGATE); /* --- aggregate <size> --- */
{
/* Create an array ({ sp[<-size>+1], ..., sp[0] }), remove the
* single values from the stack and leave the array as result.
*
* <size> is a (16-Bit) unsigned short.
*
* TODO: It is tempting to introduce flat 'literal arrays',
* TODO:: which can be copied quickly and just need a few
* TODO:: slots to filled in, if any.
*/
int i;
vector_t *v;
unsigned short num;
svalue_t *value, *item;
/* Get the size */
LOAD_SHORT(num, pc);
/* Allocate the array */
i = num;
v = allocate_uninit_array(i);
/* Set sp and value to the first single value on the stack */
sp = value = sp - i + 1;
/* Move the single values into the array.
* Volatile strings are made shared during this.
*/
item = v->item;
while (--i >= 0)
transfer_svalue_no_free_spc(item++, value++, sp, pc);
/* Leave the array on the stack (ref count is already ok) */
put_array(sp, v);
break;
}
CASE(F_M_AGGREGATE); /* --- m_aggregate <size> <width> --- */
CASE(F_M_CAGGREGATE); /* --- m_caggregate <size> <width> --- */
{
/* Create a mapping from the <size>*<width> single values on the
* stack, remove the single values and leave the mapping as result.
* Starting at the lowest entry (sp[-(<size>*<width>)]), the values
* are laid out in <key>:<data 1>...<data <width>> order.
* Keys may appear several times.
*
* m_aggregate: <size> and <width> are (16-Bit) unsigned shorts.
* m_caggregate: <size> and <width> are uint8.
*
* TODO: It is tempting to introduce flat 'literal mappings',
* TODO:: which can be copied quickly and just need a few
* TODO:: slots to filled in, if any.
*/
int i, j;
mapping_t *m;
svalue_t *data;
int num_values;
svalue_t *value;
/* Get the size and width from the code.
*/
if (instruction == F_M_CAGGREGATE)
{
i = LOAD_UINT8(pc);
num_values = LOAD_UINT8(pc);
}
else
{
unsigned short num[2];
LOAD_SHORT(num[0], pc);
LOAD_SHORT(num[1], pc);
i = num[0];
num_values = num[1];
}
if (max_mapping_size && i > max_mapping_size)
ERRORF(("Illegal mapping size: %ld\n", (long)i));
/* Get the mapping */
m = allocate_mapping(i, num_values);
if (!m)
ERROR("Out of memory\n")
/* Set sp and value to the first single value on the stack.
*/
sp = value = sp - (i * (num_values+1)) + 1;
while (--i >= 0)
{
/* Create/reget the mapping entry */
data = get_map_lvalue_unchecked(m, value);
if (!data)
{
outofmemory("literal mapping");
/* NOTREACHED */
return MY_FALSE;
}
free_svalue(value++);
for (j = num_values; --j >= 0;)
{
/* Copy over the entry data */
if (data->type != T_NUMBER)
free_svalue(data);
transfer_svalue_no_free_spc(data++, value++, sp, pc);
}
}
/* Put the mapping onto the stack */
put_mapping(sp, m);
break;
}
CASE(F_PREVIOUS_OBJECT0); /* --- previous_object0 --- */
/* EFUN previous_object(void)
*
* Push the previous_object onto the stack, if existing and
* not destructed.
*
* The compiler generates this code when it sees the previous_object()
* efun used with no arguments.
*
* (Reminder: the efun previous_object(int) has a different meaning.)
* TODO: How do other driver handle this?
*/
if (previous_ob == 0 || (previous_ob->flags & O_DESTRUCTED))
push_number(0);
else
push_object(previous_ob);
break;
CASE(F_LAMBDA_CCONSTANT); /* --- lambda_cconstant <num> --- */
{
/* Push the constant value <num> of this lambda closure onto
* the stack.
*
* The values are stored in an svalue[] before the actual
* function code and uint8 <num> is used to index that array
* from the end.
*/
#define MY_LAMBDA_VALUE_OFFSET (sizeof(svalue_t) + \
((PTRTYPE)(&((lambda_t *)0)->function.code[1])-(PTRTYPE) 0) )
int ix;
svalue_t * cstart;
/* Get the value index */
ix = LOAD_UINT8(pc);
/* Get the pointer to the last constant value */
cstart = (svalue_t *)((char *)(csp->funstart)
- LAMBDA_VALUE_OFFSET);
sp++;
assign_checked_svalue_no_free(sp, cstart - ix, sp, pc);
break;
}
CASE(F_LAMBDA_CONSTANT); /* --- lambda_constant <num> --- */
{
/* Push the constant value <num> of this lambda closure onto
* the stack.
*
* The values are stored in an svalue[] before the actual
* function code and (16-Bit) ushort <num> is used to index
* that array from the end.
*/
unsigned short ix;
svalue_t * cstart;
/* Get the value index */
LOAD_SHORT(ix, pc);
/* Get the pointer to the last constant value */
cstart = (svalue_t *)((char *)(csp->funstart)
- LAMBDA_VALUE_OFFSET);
sp++;
assign_checked_svalue_no_free(sp, cstart - ix, sp, pc);
break;
}
CASE(F_MAP_INDEX); /* --- map_index --- */
{
/* Operator F_MAP_INDEX( mapping m=sp[-2], mixed i=sp[-1], int j=sp[0])
*
* Compute m[i,j] and push it onto the stack.
*/
mapping_t *m;
mp_int n;
svalue_t *data;
if (sp[-2].type != T_MAPPING)
ERRORF(("Illegal value for <mapping>[,]: not a mapping.\n"));
/* TODO: Give type and value */
if (sp->type != T_NUMBER)
ERRORF(("Illegal sub-index for <mapping>[,]: not a number.\n"));
/* TODO: Give type and value */
m = sp[-2].u.map;
n = sp->u.number;
if (n < 0 || n >= m->num_values)
{
ERRORF(("Illegal sub-index %ld, mapping width is %ld.\n"
, (long)n, (long)m->num_values))
}
sp--; /* the key */
data = get_map_value(m, sp);
pop_stack();
if (data == &const0)
{
put_number(sp, 0);
}
else
{
assign_checked_svalue_no_free(sp, data + n, sp, pc);
}
free_mapping(m);
break;
}
CASE(F_PUSH_INDEXED_MAP_LVALUE); /* --- push_indexed_map_lvalue --- */
{
/* Operator F_PUSH_INDEXED_MAP_LVALUE( mapping m=sp[-2]
* , mixed i=sp[-1], int j=sp[0])
*
* Compute the lvalue &(m[i,j]) and push it into the stack. If v has
* just one ref left, the indexed item is stored in indexing_quickfix
* and the lvalue refers to that variable.
*/
svalue_t *data;
mapping_t *m;
mp_int n;
TYPE_TEST1(sp-2, T_MAPPING)
TYPE_TEST3(sp, T_NUMBER)
m = sp[-2].u.map;
n = sp->u.number;
if (n < 0 || n >= m->num_values)
{
ERRORF(("Illegal sub-index %ld, mapping width is %ld.\n"
, (long)n, (long)m->num_values))
}
sp--; /* the key */
data = get_map_lvalue(m, sp);
if (!data)
{
outofmemory("indexed lvalue");
/* NOTREACHED */
return MY_FALSE;
}
pop_stack();
if (!m->ref)
{
assign_svalue (&indexing_quickfix, data + n);
sp->type = T_LVALUE;
sp->u.lvalue = &indexing_quickfix;
break;
}
else
{
sp->type = T_LVALUE;
sp->u.lvalue = data + n;
}
free_mapping(m);
break;
}
CASE(F_FOREACH); /* --- foreach <nargs> <offset> --- */
{
/* Initialize a foreach() loop. On the stack are <nargs>-1
* lvalues where the value(s) are to be stored. The last
* value on the stack is the value to loop over. (Do not
* confuse <nargs> with the normal NUM_ARG!).
*
* ushort <offset> is the distance to the FOREACH_NEXT
* instruction follwing the codeblock after the instruction,
* counted from the byte following this instruction.
*
* The instruction pushes two or three more values onto
* the stack to store its internal status.
*
* sp[0] -> number 'next': index of the next value to assign (0).
* sp[-1] -> number 'count': number of values to loop over.
* x.u.generic: <nargs>, or -<nargs> if the value
* is mapping
* sp[-2] -> array 'm_indices': if the value is a mapping, this
* is the array with the indices.
*
* After pushing the values onto the stack, the instruction
* branches to the FOREACH_NEXT instruction to start the first
* iteration.
*/
int vars_required;
int nargs;
p_int count;
unsigned short offset;
nargs = LOAD_UINT8(pc);
LOAD_SHORT(offset, pc);
if (sp->type != T_STRING
&& sp->type != T_POINTER
&& sp->type != T_MAPPING)
ERROR("foreach() requires a string, array or mapping.\n");
/* TODO: What type did it get? */
/* Find out how many variables we require */
if (sp->type == T_STRING)
{
count = (p_int)svalue_strlen(sp);
vars_required = 1;
}
else if (sp->type == T_POINTER)
{
check_for_destr(sp->u.vec);
count = (p_int)VEC_SIZE(sp->u.vec);
vars_required = 1;
}
else
{
mapping_t *m;
vector_t *indices;
m = sp->u.map;
vars_required = 1 + m->num_values;
indices = m_indices(m);
count = (p_int)MAP_SIZE(m);
/* after m_indices(), else we'd count destructed entries */
if (m->num_values == 0 || nargs-1 == 1)
{
/* Special case: we can replace the mapping
* by its indices only
*/
free_svalue(sp);
put_array(sp, indices);
}
else
{
/* Normal case: push the indices array and
* remember the fact in nargs.
*/
sp++;
put_array(sp, indices);
nargs = -nargs;
}
}
/* Push the count and the starting index */
push_number(count); sp->x.generic = nargs;
push_number(0);
#ifdef DEBUG
/* The <nargs> lvalues and our temporaries act as hidden
* local variables. We therefore adapt the variable count
* so that a F_RETURN won't complain.
*/
if (nargs >= 0)
csp->num_local_variables += 2 + nargs;
else
csp->num_local_variables += 3 + (-nargs);
#endif
/* Now branch to the FOREACH_NEXT */
pc += offset;
break;
}
CASE(F_FOREACH_NEXT); /* --- foreach_next <offset> --- */
{
/* Start the next (resp. the first) iteration of a foreach()
* loop. ushort <offset> is the distance to branch back to the
* loop body, counted from the first byte of the next instruction.
* For the stack layout, see F_FOREACH.
*/
unsigned short offset;
p_int ix;
svalue_t *lvalue; /* Pointer to the first lvalue */
LOAD_SHORT(offset, pc);
/* Is there something left to iterate? */
ix = sp->u.number;
sp->u.number++;
if (ix >= sp[-1].u.number)
break; /* Nope */
if (sp[-1].x.generic < 0)
{
/* We loop over a mapping */
mapping_t *m;
vector_t *indices;
svalue_t *values;
int left;
lvalue = sp + sp[-1].x.generic - 2;
m = sp[-3].u.map;
indices = sp[-2].u.vec;
values = get_map_value(m, indices->item+ix);
if (values == &const0)
{
/* Whoops, the entry has vanished.
* Start over with this instruction again, the
* index on the stack has been incremented already.
*/
pc -= 3;
break;
}
/* Assign the index we used */
{
svalue_t *dest;
#ifdef DEBUG
if (lvalue->type != T_LVALUE)
fatal("Bad argument to foreach(): not a lvalue\n");
/* TODO: Give type and value */
#endif
dest = lvalue->u.lvalue;
assign_svalue(dest, indices->item+ix);
lvalue++;
}
/* Loop over the values and assign them */
left = -(sp[-1].x.generic) - 2;
if (left > m->num_values)
left = m->num_values;
for ( ; left > 0; left--, lvalue++, values++)
{
svalue_t *dest;
#ifdef DEBUG
if (lvalue->type != T_LVALUE)
fatal("Bad argument to foreach(): not a lvalue\n");
/* TODO: Give type and value */
#endif
dest = lvalue->u.lvalue;
assign_svalue(dest, values);
}
/* Ta-Da! */
}
else
{
lvalue = sp - sp[-1].x.generic - 1;
#ifdef DEBUG
if (lvalue->type != T_LVALUE)
fatal("Bad argument to foreach(): not a lvalue\n");
/* TODO: Give type and value */
#endif
lvalue = lvalue->u.lvalue;
if (sp[-2].type == T_STRING)
{
free_svalue(lvalue);
put_number(lvalue, sp[-2].u.string[ix]);
}
else if (sp[-2].type == T_POINTER)
{
if (ix >= (p_int)VEC_SIZE(sp[-2].u.vec))
break;
/* Oops, this array shrunk while we're looping over it.
* We stop processing and continue with the following
* FOREACH_END instruction.
*/
assign_svalue(lvalue, sp[-2].u.vec->item+ix);
}
else
fatal("foreach() requires a string, array or mapping.\n");
/* If this happens, the check in F_FOREACH failed. */
}
/* All that is left is to branch back. */
pc -= offset;
break;
}
CASE(F_FOREACH_END); /* --- foreach_end --- */
{
/* The foreach() loop ended or was terminated by a break.
* All there's left to do is cleaning up the stack.
*/
int nargs;
nargs = sp[-1].x.generic;
if (nargs < 0)
pop_n_elems(-nargs + 3);
else
pop_n_elems(nargs+2);
#ifdef DEBUG
/* The <nargs> lvalues and our temporaries acted as hidden
* local variables. We now count back the variable count
* so that a F_RETURN won't complain.
*/
if (nargs >= 0)
csp->num_local_variables -= 2 + nargs;
else
csp->num_local_variables -= 3 + (-nargs);
#endif
break;
}
#ifdef F_JUMP
CASE(F_JUMP); /* --- jump <dest> --- */
{
/* Jump to the (48-Bit) ushort address <dest> (absolute jump).
*/
unsigned long desth;
unsigned short destl;
desth = LOAD_UINT8(pc);
GET_SHORT(destl, pc);
pc = current_prog->program + (desth << 16) + destl;
break;
}
#endif /* F_JUMP */
/* --- Efuns: Miscellaneous --- */
CASE(F_CLONEP); /* --- clonep --- */
{
/* EFUN clonep()
*
* int clonep()
* int clonep (object obj)
* int clonep (string obj)
*
* The efun returns 1 if <obj> is a clone, and 0 if it is not.
* The <obj> can be given as the object itself, or by its name.
* If <obj> is omitted, the current object is tested.
* Arguments of other types return 0.
*/
int i;
if (sp->type == T_OBJECT)
{
i = (sp->u.ob->flags & O_CLONE);
}
else if (sp->type == T_STRING)
{
object_t *o;
o = find_object(sp->u.string);
if (!o)
ERRORF(("No such object '%s'.\n", sp->u.string));
i = o->flags & O_CLONE;
}
else
{
i = 0;
}
free_svalue(sp);
put_number(sp, i ? 1 : 0);
break;
}
CASE(F_CLOSUREP); /* --- closurep --- */
{
/* EFUN closurep()
*
* int closurep(mixed)
*
* Returns 1 if the argument is a closure.
*/
int i;
i = sp->type == T_CLOSURE;
free_svalue(sp);
put_number(sp, i);
break;
}
CASE(F_FLOATP); /* --- floatp --- */
{
/* EFUN floatp()
*
* int floatp(mixed)
*
* Returns 1 if the argument is a float.
*/
int i;
i = sp->type == T_FLOAT;
free_svalue(sp);
put_number(sp, i);
break;
}
CASE(F_INTP); /* --- intp --- */
{
/* EFUN intp()
*
* int intp(mixed)
*
* Returns 1 if the argument is an integer.
*/
int i;
i = sp->type == T_NUMBER;
free_svalue(sp);
put_number(sp, i);
break;
}
CASE(F_MAPPINGP); /* --- mappingp --- */
{
/* EFUN mappingp()
*
* int mappingp(mixed)
*
* Returns 1 if the argument is a mapping.
*/
int i;
i = sp->type == T_MAPPING;
free_svalue(sp);
put_number(sp, i);
break;
}
CASE(F_OBJECTP); /* --- objectp --- */
{
/* EFUN objectp()
*
* int objectp(mixed)
*
* Returns 1 if the argument is an object.
*/
int i;
i = sp->type == T_OBJECT;
free_svalue(sp);
put_number(sp, i);
break;
}
CASE(F_POINTERP); /* --- pointerp --- */
{
/* EFUN pointerp()
*
* int pointerp(mixed)
*
* Returns 1 if the argument is an array.
*/
int i;
i = sp->type == T_POINTER;
free_svalue(sp);
put_number(sp, i);
break;
}
CASE(F_STRINGP); /* --- stringp --- */
{
/* EFUN stringp()
*
* int stringp(mixed)
*
* Returns 1 if the argument is a string.
*/
int i;
i = sp->type == T_STRING;
free_svalue(sp);
put_number(sp, i);
break;
}
CASE(F_SYMBOLP); /* --- symbolp --- */
{
/* EFUN symbolp()
*
* int symbolp(mixed)
*
* Returns 1 if the argument is a symbol.
*/
int i;
i = sp->type == T_SYMBOL;
free_svalue(sp);
put_number(sp, i);
break;
}
CASE(F_CTIME); /* --- ctime --- */
{
/* EFUN ctime()
*
* string ctime(int clock = time())
* string ctime(int* uclock)
*
* Interpret the argument clock as number of seconds since Jan,
* 1st, 1970, 0.00 and convert it to a nice date and time string.
*
* Alternatively, accept an array of two ints: the first is <clock>
* value as in the first form, the second int is the number of
* microseconds elapsed in the current second.
*/
char *ts, *cp;
if (sp->type != T_NUMBER)
{
TYPE_TEST1(sp, T_POINTER)
if (VEC_SIZE(sp->u.vec) != 2)
ERRORF(("Invalid array size for argument 1: %ld, expected 2\n"
, (long)VEC_SIZE(sp->u.vec)));
if (sp->u.vec->item[0].type != T_NUMBER
|| sp->u.vec->item[1].type != T_NUMBER)
ERROR("Invalid array for argument 1\n");
ts = utime_string(sp->u.vec->item[0].u.number, sp->u.vec->item[1].u.number);
}
else
{
ts = time_string(sp->u.number);
}
/* If the string contains nl characters, extract the substring
* before the first one. Else just copy the (volatile) result
* we got.
*/
cp = strchr(ts, '\n');
if (cp)
{
int len = cp - ts;
cp = xalloc((size_t)(len + 1));
if (!cp)
ERROR("Out of memory\n")
strncpy(cp, ts, (size_t)len);
cp[len] = 0;
}
else
{
cp = string_copy(ts);
if (!cp)
ERROR("Out of memory\n")
}
free_svalue(sp);
put_malloced_string(sp, cp);
break;
}
CASE(F_ED); /* --- ed <nargs> --- */
/* EFUN ed()
*
* int ed()
* int ed(string file)
* int ed(string file, string func)
*
* Calling without arguments will start the editor ed with the
* name of the error file, that was returned by
* master->valid_read(0, geteuid(this_player()), "ed_start",
* this_player()), usually something like ~/.err. If that file is
* empty, ed will immediatly exit again.
* Calling ed() with argument file will start the editor on the
* file. If the optional argument func is given, this function
* will be called after exiting the editor.
*
* Result is 1 if the editor could be started, else 0. TODO: ???
*/
if (current_object->flags & O_DESTRUCTED)
{
/* could confuse the master... */
ERROR("Calling ed from destructed object.\n")
}
GET_NUM_ARG
assign_eval_cost();
inter_pc = pc;
inter_sp = sp;
if (num_arg == 0)
{
ed_start(NULL, NULL, NULL);
push_number(1);
break;
}
else if (num_arg == 1)
{
TYPE_TEST1(sp, T_STRING)
ed_start(sp->u.string, NULL, NULL);
break;
}
else
{
TYPE_TEST1(sp-1, T_STRING)
if (sp->type == T_STRING)
ed_start((sp-1)->u.string, sp->u.string, current_object);
else if (sp->type == T_NUMBER)
ed_start((sp-1)->u.string, NULL, NULL);
else
goto bad_arg_2;
pop_stack();
break;
}
CASE(F_NEGATE); /* --- negate --- */
/* EFUN negate()
*
* int|float negate(int|float arg)
*
* Negate the value <arg> and leave it on the stack.
* Calls to this efun are mainly generated by the compiler when
* it sees the unary '-' used.
*/
if (sp->type == T_NUMBER)
{
if (sp->u.number == PINT_MIN)
ERRORF(("Numeric overflow: - %ld\n", sp->u.number));
sp->u.number = - sp->u.number;
break;
}
else if (sp->type == T_FLOAT)
{
STORE_DOUBLE_USED
double d;
d = -READ_DOUBLE(sp);
if (d < (-DBL_MAX) || d > DBL_MAX)
ERRORF(("Numeric overflow: -(%g)\n", READ_DOUBLE(sp)));
STORE_DOUBLE(sp,d);
break;
}
ERROR("Bad argument to unary minus\n")
CASE(F_PRINTF); /* --- printf <nargs> --- */
{
/* EFUN printf()
*
* void printf(string format, ...)
*
* A cross between sprintf() and write(). Returns void and prints
* the result string to the user.
*/
char *str;
assign_eval_cost();
GET_NUM_ARG
inter_pc = pc;
inter_sp = sp;
TYPE_TEST1(sp - num_arg + 1, T_STRING)
str = string_print_formatted((sp-num_arg+1)->u.string
, num_arg-1, sp-num_arg+2);
if (command_giver)
tell_object(command_giver, str);
else
add_message("%s", str);
pop_n_elems(num_arg);
break;
}
CASE(F_RANDOM); /* --- random --- */
/* EFUN random()
*
* int random(int n)
*
* Returns a number in the random range [0 .. n-1].
*
* The random number generator is proven to deliver an equal
* distribution of numbers over a big range, with no repetition of
* number sequences for a long time.
*/
TYPE_TEST1(sp, T_NUMBER)
if (sp->u.number <= 0)
{
sp->u.number = 0;
break;
}
sp->u.number = (p_int)random_number((uint32)sp->u.number);
break;
CASE(F_THROW); /* --- throw --- */
/* EFUN throw()
*
* void throw(mixed arg)
*
* Abort execution. If the current program execution was initiated by
* catch(), that catch expression will return arg as error code.
*/
assign_eval_cost();
sp--;
transfer_svalue_no_free_spc(&catch_value, sp+1, sp, pc);
inter_sp = sp;
inter_pc = pc;
throw_error(); /* do the longjump, with extra checks... */
break;
CASE(F_TIME); /* --- time --- */
/* EFUN time()
*
* int time()
*
* Return number of seconds ellapsed since 1. Jan 1970, 0.0:0 GMT
*
* Actually the time is updated only once in every backend cycle.
*/
push_number(current_time);
break;
CASE(F_UTIME); /* --- utime --- */
{
/* EFUN utime()
*
* int* utime()
*
* Return the time since 1. Jan 1970, 00:00:00 GMT in microsecond
* precision.
*
* Return is an array:
* int[0]: number of seconds elapsed
* int[1]: number of microseconds within the current second.
*/
svalue_t *v;
vector_t *res;
struct timeval tv;
res = allocate_array(2);
v = res->item;
if (!gettimeofday(&tv, NULL))
{
v[0].u.number = tv.tv_sec;
v[1].u.number = tv.tv_usec;
}
else
{
int errnum = errno;
fprintf(stderr, "%s gettimeofday() failed: %d %s\n"
, time_stamp(), errnum, strerror(errnum));
v[0].u.number = current_time;
v[1].u.number = 0;
}
(void)push_referenced_vector(res);
break;
}
/* --- Efuns: Strings --- */
CASE(F_CAPITALIZE); /* --- capitalize --- */
/* EFUN capitalize()
*
* string capitalize(string str)
*
* Convert the first character in str to upper case, and return
* the new string.
*/
TYPE_TEST1(sp, T_STRING)
if (islower((unsigned char)(sp->u.string[0])))
{
char *str;
/* Change malloc'ed strings in place, for others
* make a copy.
*/
if (STRING_MALLOC == sp->x.string_type)
sp->u.string[0] = toupper((unsigned char)sp->u.string[0]);
else
{
str = string_copy(sp->u.string);
str[0] = toupper((unsigned char)str[0]);
pop_stack();
push_malloced_string(str);
}
}
break;
CASE(F_CRYPT); /* --- crypt --- */
{
/* EFUN crypt()
*
* string crypt(string str, int seed)
* string crypt(string str, string seed)
*
* Crypt the string str using the integer seed or two characters
* from the string seed as a seed. If seed is equal 0, then
* a random seed is used.
*
* The result has the first two characters as the seed.
*/
char *salt;
char *res;
char temp[3];
static char choise[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./";
TYPE_TEST1(sp-1, T_STRING)
if (sp->type == T_STRING && svalue_strlen(sp) >= 2)
{
salt = sp->u.string;
}
else if (sp->type == T_NUMBER)
{
temp[0] = choise[random_number((sizeof choise) - 1)];
temp[1] = choise[random_number((sizeof choise) - 1)];
temp[2] = '\0';
salt = temp;
}
else
goto bad_arg_2;
res = string_copy(crypt((sp-1)->u.string, salt));
pop_n_elems(2);
push_malloced_string(res);
break;
}
CASE(F_EXPLODE); /* --- explode --- */
{
/* EFUN explode()
*
* string *explode(string str, string del)
*
* Return an array of strings, created when the string str is
* split into substrings as divided by del.
*/
vector_t *v;
TYPE_TEST1(sp-1, T_STRING)
TYPE_TEST2(sp, T_STRING)
inter_sp = sp;
inter_pc = pc;
v = explode_string((sp-1)->u.string, sp->u.string);
free_string_svalue(sp);
sp--;
free_string_svalue(sp);
put_array(sp,v);
break;
}
CASE(F_IMPLODE); /* --- implode --- */
{
/* EFUN implode()
*
* string implode(mixed *arr, string del)
*
* Concatenate all strings found in array arr, with the string
* del between each element. Only strings are used from the array.
*/
char *str;
TYPE_TEST1(sp-1, T_POINTER)
TYPE_TEST2(sp, T_STRING)
str = implode_string((sp-1)->u.vec, sp->u.string);
if (!str)
ERROR("Out of memory\n")
free_string_svalue(sp);
sp--;
free_array(sp->u.vec);
if (str)
put_malloced_string(sp, str);
else
put_number(sp, 0);
break;
}
CASE(F_LOWER_CASE); /* --- lower_case --- */
{
/* EFUN lower_case()
*
* string lower_case(string str)
*
* Convert all characters in str to lower case, and return the
* new string.
*/
char *str, *s, *d, c;
ptrdiff_t initial_len;
TYPE_TEST1(sp, T_STRING)
/* Set s to the first uppercase character and store it in c */
for ( s = sp->u.string
; '\0' != (c = *s) && !isupper((unsigned char)c)
; s++) NOOP;
if (c)
{
/* Yes, there is something to change... */
if (STRING_MALLOC == sp->x.string_type)
{
/* Scan the rest of the string and lower it */
for ( ; '\0' != (c = *s); s++)
if (isupper((unsigned char)c))
*s = (char)tolower((unsigned char)c);
}
else
{
/* We need to make a copy of the shared string.
* so fold the copying with the case changing.
*/
initial_len = s - sp->u.string;
str = xalloc(svalue_strlen(sp)+1);
if (initial_len)
memcpy(str, sp->u.string, (size_t)initial_len);
for(d = str + initial_len; '\0' != (c = *s++) ; )
{
if (isupper((unsigned char)c))
c = (char)tolower((unsigned char)c);
*d++ = c;
}
*d = '\0';
free_string_svalue(sp);
put_malloced_string(sp, str);
}
}
break;
}
CASE(F_REGEXP); /* --- regexp --- */
{
/* EFUN regexp()
*
* string *regexp(string *list, string pattern)
*
* Match the pattern pattern against all strings in list, and return a
* new array with all strings that matched. This function uses the
* same syntax for regular expressions as ed().
*/
vector_t *v;
TYPE_TEST1(sp-1, T_POINTER)
TYPE_TEST2(sp, T_STRING)
v = match_regexp((sp-1)->u.vec, sp->u.string);
pop_stack();
free_svalue(sp);
if (v == NULL)
put_number(sp, 0);
else
put_array(sp,v);
break;
}
CASE(F_SPRINTF); /* --- sprintf <nargs> --- */
{
/* EFUN sprintf()
*
* string sprintf(string fmt, ...)
*
* Generate a string according to the <fmt> and the following
* arguments and put it onto the stack.
*
* <fmt> follows the C-style sprintf-format string in style,
* and partly in meaning, too.
*/
char *s;
/*
* string_print_formatted() returns a pointer to it's internal
* buffer, or to an internal constant... Either way, it must
* be copied before it's returned as a string.
*/
assign_eval_cost();
GET_NUM_ARG
inter_pc = pc;
inter_sp = sp;
TYPE_TEST1(sp - num_arg + 1, T_STRING)
s = string_print_formatted((sp-num_arg+1)->u.string,
num_arg-1, sp-num_arg+2);
pop_n_elems(num_arg);
if (!s)
push_number(0);
else
/* string_print_formatted() owns the string returned,
* so copy it.
*/
push_malloced_string(string_copy(s));
break;
}
CASE(F_STRLEN); /* --- strlen --- */
{
/* EFUN strlen()
*
* int strlen(string str)
*
* Returns the length of the string str.
*/
size_t i;
if (sp->type == T_STRING)
{
i = _svalue_strlen(sp);
free_string_svalue(sp);
put_number(sp, (p_int)i);
break;
}
if (sp->type == T_NUMBER && sp->u.number == 0)
break;
goto bad_arg_1;
}
CASE(F_TERMINAL_COLOUR); /* --- terminal_colour <nargs> --- */
{
/* EFUN terminal_colour()
*
* varargs string terminal_colour( string str, mapping|closure map,
* int wrap, int indent )
*
* Expands all colour-defines from the input-string and replaces them
* by the apropriate values found for the color-key inside the given
* mapping. The mapping has the format "KEY" : "value", non-string
* contents are ignored.
*/
int indent = 0;
int wrap = 0;
mapping_t *map = NULL;
svalue_t *cl = NULL;
char * str;
GET_NUM_ARG;
if ( num_arg >= 3 )
{
if ( num_arg == 4 )
{
TYPE_TEST4(sp, T_NUMBER );
indent = (sp--)->u.number;
if (indent < 0)
{
ERROR("terminal_colour() requires an indent >= 0.\n");
break;
}
}
TYPE_TEST3(sp,T_NUMBER);
wrap = (sp--)->u.number;
if (wrap < 0)
{
ERROR("terminal_colour() requires a wrap >= 0.\n");
break;
}
}
TYPE_TEST1(sp-1, T_STRING);
if (sp->type == T_MAPPING)
{
if (sp->u.map->num_values < 1)
{
ERROR("terminal_colour() requires a mapping with values.\n");
break;
}
map = sp->u.map;
cl = NULL;
}
else if (sp->type == T_CLOSURE)
{
map = NULL;
cl = sp;
}
else if (sp->type != T_NUMBER || sp->u.number != 0)
{
goto bad_arg_2;
}
inter_sp = sp;
inter_pc = pc;
str = e_terminal_colour(sp[-1].u.string, map, cl, indent, wrap);
pop_stack();
if (str != sp->u.string)
{
/* terminal_colour() actually changed the string */
free_svalue(sp);
put_malloced_string(sp, str);
}
break;
}
CASE(F_CLEAR_BIT); /* --- clear_bit --- */
{
/* EFUN clear_bit()
*
* string clear_bit(string str, int n)
*
* Return the new string where bit n is cleared in string str.
* Note that the old string str is not modified.
*
* Each character contains 6 bits. So you can store a value
* between 0 and 63 ( 2^6=64) in one character. Starting
* character is the blank character " " which has the value 0.
* The first charcter in the string is the one with the lowest
* bits (0-5).
*/
char *str;
size_t len, ind, bitnum;
svalue_t *strp;
/* Get and test the arguments */
TYPE_TEST1(sp-1, T_STRING)
TYPE_TEST2(sp, T_NUMBER)
bitnum = (size_t)sp->u.number;
if (sp->u.number < 0)
ERRORF(("clear_bit: negative bit number: %ld\n", (long)sp->u.number))
if (bitnum > MAX_BITS)
ERRORF(("clear_bit: too big bit number: %ld\n", (long)bitnum))
sp = strp = sp-1;
len = svalue_strlen(strp);
ind = bitnum/6;
if (ind >= len)
{
/* Return first argument unmodified! */
break;
}
/* Malloc'ed strings are modified in place, others are copied first.
*/
if (strp->x.string_type == STRING_MALLOC)
{
str = strp->u.string;
}
else
{
str = xalloc(len+1);
memcpy(str, strp->u.string, len+1); /* Including null byte */
free_string_svalue(strp);
strp->x.string_type = STRING_MALLOC;
strp->u.string = str;
}
if (str[ind] > 0x3f + ' ' || str[ind] < ' ')
ERRORF(("Illegal bit pattern in clear_bit character %ld\n", (long)ind))
str[ind] = (char)(((str[ind] - ' ') & ~(1 << (bitnum % 6))) + ' ');
break;
}
CASE(F_SET_BIT); /* --- set_bit --- */
{
/* EFUN set_bit()
*
* string set_bit(string str, int n)
*
* Return the new string where bit n is set in string str. Note
* that the old string str is not modified.
*
* The new string will automatically be extended if needed.
* TODO: Apply to an optional range (start, length) only.
*/
char *str;
size_t len, old_len, ind, bitnum;
svalue_t *strp;
TYPE_TEST1(sp-1, T_STRING)
TYPE_TEST2(sp, T_NUMBER)
bitnum = (size_t)sp->u.number;
if (sp->u.number < 0)
ERRORF(("set_bit: negative bit number: %ld\n", (long)sp->u.number))
if (bitnum > MAX_BITS)
ERRORF(("set_bit: too big bit number: %ld\n", (long)bitnum))
sp = strp = sp-1;
len = svalue_strlen(strp);
old_len = len;
ind = bitnum/6;
/* Malloc'ed strings of the right size are modified in place,
* others are copied first.
*/
if ( (ind < len || (len = ind + 1, MY_FALSE) )
&& strp->x.string_type == STRING_MALLOC )
{
str = strp->u.string;
}
else
{
str = xalloc(len+1);
str[len] = '\0';
if (old_len)
memcpy(str, strp->u.string, old_len);
if (len > old_len)
memset(str + old_len, ' ', len - old_len);
free_string_svalue(strp);
strp->x.string_type = STRING_MALLOC;
strp->u.string = str;
}
if (str[ind] > 0x3f + ' ' || str[ind] < ' ')
ERRORF(("Illegal bit pattern in set_bit character %ld\n", (long)ind))
str[ind] = (char)(((str[ind] - ' ') | 1 << (bitnum % 6) ) + ' ');
sp = strp;
break;
}
CASE(F_TEST_BIT); /* --- test_bit --- */
{
/* EFUN test_bit()
*
* int test_bit(string str, int n)
*
* Return 0 or 1 of bit n was set in string str.
* TODO: Apply to an optional range (start, length) only.
*/
size_t len;
TYPE_TEST1(sp-1, T_STRING)
TYPE_TEST2(sp, T_NUMBER)
if (sp->u.number < 0)
ERRORF(("test_bit: negative bit number: %ld\n", (long)sp->u.number))
len = svalue_strlen(sp-1);
if (sp->u.number/6 >= len)
{
sp--;
free_string_svalue(sp);
put_number(sp, 0);
break;
}
if ( ((sp-1)->u.string[sp->u.number/6] - ' ')
& 1 << (sp->u.number % 6) )
{
sp--;
free_string_svalue(sp);
put_number(sp, 1);
}
else
{
sp--;
free_string_svalue(sp);
put_number(sp, 0);
}
break;
}
CASE(F_OR_BITS); /* --- or_bits --- */
CASE(F_AND_BITS); /* --- and_bits --- */
CASE(F_XOR_BITS); /* --- xor_bits --- */
{
/* EFUN or_bits(), and_bits(), xor_bits()
*
* string or_bits(string str1, string str2)
* string and_bits(string str1, string str2)
* string xor_bits(string str1, string str2)
*
* Perform a binary operation on the bitstrings <str1> and <str2>
* and return the resulting string.
*
* Each character contains 6 bits. So you can store a value
* between 0 and 63 ( 2^6=64) in one character. Starting
* character is the blank character " " which has the value 0.
* The first charcter in the string is the one with the lowest
* bits (0-5).
* TODO: Apply to an optional range (start, length) only.
*/
size_t len1, len2, result_len, arg_len;
char *arg, *result, *to_copy;
Bool use_short; /* TRUE for AND: use shorter string for result */
result_len = 0;
to_copy = NULL;
use_short = (instruction == F_AND_BITS);
/* Get and test the arguments */
TYPE_TEST1(sp-1, T_STRING)
TYPE_TEST2(sp, T_STRING)
/* Sort the two arguments in shorter and longer string.
* We will try to modify one of the two strings in-place.
*/
result = NULL;
len1 = svalue_strlen(sp-1);
len2 = svalue_strlen(sp);
if ((len1 >= len2 && !use_short)
|| (len1 < len2 && use_short)
)
{
/* AND: sp-1 is the shorter result; sp the longer argument
* else: sp-1 is the longer result; sp the shorter argument
*/
arg = sp->u.string;
if ((sp-1)->x.string_type == STRING_MALLOC)
{
result = (sp-1)->u.string;
*(sp-1) = const0;
}
else
{
result_len = len1;
to_copy = (sp-1)->u.string;
}
}
else
{
/* AND: sp is the shorter result; sp-1 the longer argument
* else: sp is the longer result; sp-1 the shorter argument
*/
arg = (sp-1)->u.string;
if (sp->x.string_type == STRING_MALLOC)
{
result = sp->u.string;
*sp = const0;
}
else
{
result_len = len2;
to_copy = sp->u.string;
}
}
/* If needed, allocate a copy of the result string.
*/
if (!result)
{
inter_pc = pc;
inter_sp = sp;
result = xalloc(result_len+1);
if (!result)
ERROR("Out of memory.\n")
memcpy(result, to_copy, result_len+1);
}
/* Now perform the operation. */
arg_len = (len2 > len1) ? len1 : len2;
while (arg_len-- != 0)
{
char c1, c2;
c1 = result[arg_len];
c2 = arg[arg_len];
if (c1 > 0x3f + ' ' || c1 < ' ')
ERRORF(("Illegal bit pattern in or_bits character %d\n"
, (int)c1))
if (c2 > 0x3f + ' ' || c2 < ' ')
ERRORF(("Illegal bit pattern in or_bits character %d\n"
, (int)c2))
if (instruction == F_AND_BITS)
result[arg_len] = (char)((c1-' ') & (c2-' ')) + ' ';
else if (instruction == F_OR_BITS)
result[arg_len] = (char)((c1-' ') | (c2-' ')) + ' ';
else if (instruction == F_XOR_BITS)
result[arg_len] = (char)((c1-' ') ^ (c2-' ')) + ' ';
}
/* Clean up the stack and push the result. */
free_svalue(sp--);
free_svalue(sp);
put_malloced_string(sp, result);
break;
}
CASE(F_INVERT_BITS); /* --- invert_bits --- */
{
/* EFUN invert_bits()
*
* string invert_bits(string str)
*
* Invert all bits in the bitstring <str> and return the
* new string.
*
* TODO: Apply to an optional range (start, length) only.
*/
char * src, * dest, * result;
long len;
/* Get and test the arguments */
TYPE_TEST1(sp, T_STRING)
src = sp->u.string;
len = (size_t)_svalue_strlen(sp);
/* If it is a malloced string, modify it in place,
* otherwise allocate a copy.
*/
if (sp->x.string_type == STRING_MALLOC)
{
dest = src;
result = NULL;
}
else
{
inter_pc = pc;
inter_sp = sp;
result = dest = xalloc((size_t)len+1);
if (!dest)
ERROR("Out of memory\n")
}
/* Invert the string */
while (len-- > 0)
{
*dest++ = (char)(' ' + (~(*src++ - ' ') & 0x3F));
}
*dest = '\0';
/* Push the result */
if (result != NULL)
{
free_svalue(sp);
put_malloced_string(sp, result);
}
break;
}
CASE(F_LAST_BIT); /* --- last_bit --- */
{
/* EFUN last_bit()
*
* int last_bit(string str)
*
* Return the number of the last set bit in bitstring <str>.
* If no bit is set, return -1.
*
* TODO: extend this to true int-bitflags?
*
* Each character contains 6 bits. So you can store a value
* between 0 and 63 ( 2^6=64) in one character. Starting
* character is the blank character " " which has the value 0.
* The first charcter in the string is the one with the lowest
* bits (0-5).
* TODO: Apply to an optional range (start, length) only.
*/
mp_int pos;
long len;
char * str;
int c;
pos = -1;
/* Get and test the arguments */
TYPE_TEST1(sp, T_STRING)
str = sp->u.string;
len = (long)_svalue_strlen(sp);
/* First, find the last non-zero character */
c = 0;
while (len-- > 0 && (c = str[len]) == ' ') NOOP;
if (len >= 0)
{
int pattern;
/* Found a character, now determine the bit position */
c -= ' ';
pos = 6 * len + 5;
for ( pattern = 1 << 5
; pattern && !(c & pattern)
; pattern >>= 1, pos--)
NOOP;
}
/* Clear the stack and push the result */
free_svalue(sp);
put_number(sp, pos);
break;
}
CASE(F_NEXT_BIT); /* --- next_bit --- */
{
/* EFUN next_bit()
*
* int next_bit(string str, int start, int find_zero)
*
* Return the number of the next bit after position <start>
* in bitstring <str>. If <find_zero> is non-null, the next
* unset bit is found, else the next set bit.
* If there is no such bit, return -1.
*
* If <start> is negative, the string is searched from the
* beginning.
*
* TODO: extend this to true int-bitflags?
*
* Each character contains 6 bits. So you can store a value
* between 0 and 63 ( 2^6=64) in one character. Starting
* character is the blank character " " which has the value 0.
* The first charcter in the string is the one with the lowest
* bits (0-5).
*/
mp_int found; /* Resultvalue */
size_t pos; /* Searchposition */
size_t search; /* Searchindex */
int pattern; /* Pattern for next bit to test */
long len; /* Length of the string */
long start; /* Startposition */
char * str; /* the bitstring */
int invert; /* when looking for 0 bits, an inverter mask */
/* Get and test the arguments */
TYPE_TEST1(sp-2, T_STRING)
TYPE_TEST2(sp-1, T_NUMBER)
str = (sp-2)->u.string;
len = (long)_svalue_strlen(sp-2);
start = (sp-1)->u.number;
if (start < 0)
{
pattern = 0x01;
pos = 0;
search = 0;
}
else if (start % 6 == 5)
{
pattern = 0x01;
pos = (size_t)start + 1;
search = (size_t)start / 6 + 1;
}
else
{
pattern = 1 << (start % 6 + 1);
pos = (size_t)start + 1;
search = (size_t)start / 6;
}
invert = 0;
if (!sp->type == T_NUMBER || sp->u.number)
invert = 0x3f;
/* Now search for the next bit */
found = -1;
while (found < 0 && search < len)
{
int c = str[search] - ' ';
if (c < 0 || c > 0x3f)
ERRORF(("Illegal bit pattern in next_bit character %d\n"
, c+' '))
c ^= invert;
while (found < 0 && pattern < (1 << 6))
{
if (c & pattern)
{
found = (mp_int)pos;
break;
}
pattern <<= 1;
pos++;
}
pattern = 0x01;
search++;
}
/* Cleanup the stack and push the result */
free_svalue(sp--);
free_svalue(sp--);
free_svalue(sp);
put_number(sp, found);
break;
}
CASE(F_COUNT_BITS); /* --- count_bits --- */
{
/* EFUN count_bits()
*
* int count_bits(string str)
*
* Return the number of set bits in bitstring <str>.
*
* TODO: Apply to an optional range (start, length) only.
*/
char * str;
long count;
/* Get and test the arguments */
TYPE_TEST1(sp, T_STRING)
str = sp->u.string;
for (count = 0; *str; str++)
{
int c = *str - ' ';
if (c > 0x3F || c < 0)
ERRORF(("Illegal character in count_bits: %d\n", (int)c + ' '))
/* Count the bits in this character */
count += ( (c & 0x01) )
+ ( (c & 0x02) >> 1)
+ ( (c & 0x04) >> 2)
+ ( (c & 0x08) >> 3)
+ ( (c & 0x10) >> 4)
+ ( (c & 0x20) >> 5);
}
/* Return the result */
free_svalue(sp);
put_number(sp, count);
break;
}
/* --- Efuns: Arrays and Mappings --- */
CASE(F_ALLOCATE); /* --- allocate <nargs> --- */
{
/* EFUN allocate()
*
* mixed *allocate(int|int* size)
* mixed *allocate(int|int* size, mixed init_value)
*
* Allocate an array of <size> elements (if <size> is an array,
* the result will be a multidimensional array), either empty or all
* elements initialized with <init_value>. If <init_value> is a
* mapping or array, allocate will create shallow copies of them.
*/
vector_t *v;
svalue_t *argp;
Bool hasInitValue = MY_FALSE;
GET_NUM_ARG
argp = sp - num_arg + 1;
inter_sp = sp;
inter_pc = pc;
if (num_arg == 2 && (sp->type != T_NUMBER || sp->u.number != 0))
{
hasInitValue = MY_TRUE;
/* If the initialisation value is not a shared string, we
* better make it shared.
*/
if (sp->type == T_STRING && sp->x.string_type != STRING_SHARED)
{
char * str = make_shared_string(sp->u.string);
if (!str)
{
ERRORF(("Out of memory for string (%lu bytes)\n"
, (unsigned long)strlen(sp->u.string) ));
/* NOTREACHED */
}
free_string_svalue(sp);
put_string(sp, str);
}
/* If the initialisation value is a mapping, remove all
* destructed elements so that we can use copy_mapping()
* later on.
*/
if (sp->type == T_MAPPING)
{
check_map_for_destr(sp->u.map);
}
}
if (argp->type == T_NUMBER)
{
if (!hasInitValue)
v = allocate_array(argp->u.number);
else
{
int i;
svalue_t *svp;
v = allocate_uninit_array(argp->u.number);
for (svp = v->item, i = 0; i < argp->u.number; i++, svp++)
copy_svalue_no_free(svp, sp);
}
}
else if (argp->type == T_POINTER
&& ( VEC_SIZE(argp->u.vec) == 0
|| ( VEC_SIZE(argp->u.vec) == 1
&& argp->u.vec->item->type == T_NUMBER
&& argp->u.vec->item->u.number == 0)
)
)
{
/* Special case: result is the empty array.
* The condition catches ( ({}) ) as well as ( ({0}) )
* (the generic code below can't handle either of them).
*/
v = allocate_array(0);
}
else if (argp->type == T_POINTER)
{
svalue_t *svp;
size_t dim, num_dim;
size_t count;
size_t * curpos = NULL;
size_t * sizes = NULL;
vector_t ** curvec = NULL;
num_dim = VEC_SIZE(argp->u.vec);
curpos = xalloc(num_dim * sizeof(*curpos));
if (!curpos)
{
error("Out of memory (%lu bytes).\n"
, (unsigned long)num_dim * sizeof(*curpos));
/* NOTREACHED */
}
sizes = xalloc(num_dim * sizeof(*sizes));
if (!sizes)
{
xfree(curpos);
error("Out of memory (%lu bytes).\n"
, (unsigned long)num_dim * sizeof(*sizes));
/* NOTREACHED */
}
curvec = xalloc(num_dim * sizeof(*curvec));
if (!curvec)
{
xfree(curpos);
xfree(sizes);
error("Out of memory (%lu bytes).\n"
, (unsigned long)num_dim * sizeof(*curvec));
/* NOTREACHED */
}
/* Check the size array for consistency, and also count how many
* elements we're going to allocate.
*/
for ( dim = 0, count = 0, svp = argp->u.vec->item
; dim < num_dim
; dim++, svp++
)
{
p_int size;
if (svp->type != T_NUMBER)
{
xfree(curpos);
xfree(sizes);
xfree(curvec);
error("Bad argument to allocate(): size[%d] is not an int.\n"
, (int)dim);
/* NOTREACHED */
}
size = svp->u.number;
if (size < 0 || (max_array_size && (size_t)size > max_array_size))
{
xfree(curpos);
xfree(sizes);
xfree(curvec);
error("Illegal array size: %ld\n", (long)size);
/* NOTREACHED */
}
if (size == 0 && dim < num_dim-1)
{
error("Only the last dimension can have empty arrays.\n");
/* NOTREACHED */
}
count *= (size_t)size;
if (max_array_size && count > max_array_size)
{
xfree(curpos);
xfree(sizes);
xfree(curvec);
error("Illegal total array size: %lu\n", (unsigned long)count);
/* NOTREACHED */
}
sizes[dim] = (size_t)size;
curvec[dim] = NULL;
}
/* Now loop over the dimensions, creating the array structure */
dim = 0;
curpos[0] = 0;
while (dim > 0 || curpos[0] < sizes[0])
{
if (!curvec[dim])
{
/* We just entered this dimension.
* Create a new array and initialise the loop.
*/
if (hasInitValue || dim+1 < num_dim)
{
curvec[dim] = allocate_uninit_array(sizes[dim]);
}
else
{
curvec[dim] = allocate_array(sizes[dim]);
/* This is the last dimension, and there is nothing
* to initialize: return immediately to the higher level
*/
curpos[dim] = sizes[dim]; /* In case dim == 0 */
if (dim > 0)
dim--;
continue;
}
curpos[dim] = 0;
}
/* curvec[dim] is valid, and we have to put the next
* element in at index curpos[dim].
*/
if (dim == num_dim-1)
{
/* Last dimension: assign the init value */
if (hasInitValue && curpos[dim] < sizes[dim])
{
copy_svalue_no_free( curvec[dim]->item + curpos[dim]
, sp);
}
}
else if (!curvec[dim+1])
{
/* We need a vector from a lower dimension, but it doesn't
* exist yet: setup the loop parameters to go into
* that lower level.
*/
dim++;
continue;
}
else if (curpos[dim] < sizes[dim])
{
/* We got a vector from a lower lever */
put_array(curvec[dim]->item+curpos[dim], curvec[dim+1]);
curvec[dim+1] = NULL;
}
/* Continue to the next element. If we are at the end
* of this dimension, return to the next higher one.
*/
curpos[dim]++;
if (curpos[dim] >= sizes[dim] && dim > 0)
{
dim--;
}
} /* while() */
/* The final vector is now in curvec[0] */
v = curvec[0];
xfree(curpos);
xfree(sizes);
xfree(curvec);
}
else
{
error("Illegal arg 1 to allocate(): neither 'int' nore 'int*'.\n");
} /* if (argp->type) */
if (num_arg > 1)
free_svalue(sp--);
free_svalue(sp);
put_array(sp, v);
break;
}
#ifdef F_MEMBER_ARRAY
CASE (F_MEMBER_ARRAY); /* --- member_array --- */
{
/* EFUN member_array()
*
* int member_array(mixed item, mixed *arr)
* int member_array(mixed item, string arr)
*
* Returns the index of the first occurence of item in array arr,
* or occurence of a character in a string. If not found, then -1
* is returned.
* TODO: Practically obsoleted by member().
*/
/* Search in an array */
if (sp->type == T_POINTER)
{
vector_t *vec; /* Vector searched */
svalue_t *item; /* Pointer into the vector array */
svalue_t *key; /* Item searched */
long cnt; /* Size of vec */
vec = sp->u.vec;
item = vec->item;
key = sp - 1;
cnt = (long)VEC_SIZE(vec);
switch(key->type)
{
case T_STRING:
{
char *str;
str = key->u.string;
for(; --cnt >= 0; item++)
{
if (item->type == T_STRING
&& !strcmp(key->u.string, item->u.string)
)
break;
}
break;
}
case T_CLOSURE:
{
short type;
type = key->type;
for(; --cnt >= 0; item++)
{
if (item->type == type
&& !closure_cmp(key, item)
)
break;
}
break;
}
case T_FLOAT:
case T_SYMBOL:
case T_QUOTED_ARRAY:
{
short type;
short x_generic;
type = key->type;
x_generic = key->x.generic;
for(; --cnt >= 0; item++)
{
if (key->u.string == item->u.string
&& x_generic == item->x.generic
&& item->type == type
)
break;
}
break;
}
case T_NUMBER:
if (!key->u.number)
{
/* Search for 0 is special: it also finds destructed
* objects resp. closures on destructed objects.
*/
short type;
for (; --cnt >= 0; item++)
{
if ( (type = item->type) == T_NUMBER)
{
if ( !item->u.number )
break;
}
else if (destructed_object_ref(item))
{
assign_svalue(item, &const0);
break;
}
}
break;
}
/* FALLTHROUGH */
case T_MAPPING:
case T_OBJECT:
case T_POINTER:
{
short type = key->type;
for(; --cnt >= 0; item++)
{
if (key->u.number == item->u.number
&& item->type == type)
break;
}
break;
}
default:
if (sp[-1].type == T_LVALUE)
error("Reference passed to member_array()\n");
fatal("Bad type to member_array(): %d\n", sp[-1].type);
}
if (cnt >= 0)
{
cnt = (long)VEC_SIZE(vec) - cnt - 1;
}
/* else return -1 for failure */
pop_stack();
free_svalue(sp);
put_number(sp, cnt);
break;
}
/* Or search in a string */
if (sp->type == T_STRING)
{
char *str, *str2;
int i;
if (sp[-1].type != T_NUMBER)
goto bad_arg_1;
str = sp->u.string;
i = sp[-1].u.number;
str2 = i & ~0xff ? NULL : strchr(str, i);
i = str2 ? str2 - str : -1;
pop_stack();
free_svalue(sp);
put_number(sp, i);
break;
}
/* Otherwise, it's something unsearchable */
goto bad_arg_2;
}
#endif /* F_MEMBER_ARRAY */
CASE (F_MEMBER); /* --- member --- */
{
/* EFUN member()
*
* int member(mixed *array, mixed elem)
* int member(mapping m, mixed key)
* int member(string s, int elem)
*
* For arrays and strings, returns the index of the second arg in
* the first arg, or -1 if none found. For mappings it checks, if
* key is present in mapping m and returns 1 if so, 0 if key is
* not in m.
*/
/* --- Search an array --- */
if (sp[-1].type == T_POINTER)
{
vector_t *vec;
union u sp_u;
long cnt;
vec = sp[-1].u.vec;
cnt = (long)VEC_SIZE(vec);
sp_u = sp->u;
switch(sp->type)
{
case T_STRING:
{
char *str;
svalue_t *item;
str = sp_u.string;
for(item = vec->item; --cnt >= 0; item++)
{
if (item->type == T_STRING
&& !strcmp(sp_u.string, item->u.string))
break;
}
break;
}
case T_CLOSURE:
{
short type;
svalue_t *item;
type = sp->type;
for(item = vec->item; --cnt >= 0; item++)
{
if (item->type == type
&& !closure_cmp(sp, item)
)
break;
}
break;
}
case T_FLOAT:
case T_SYMBOL:
case T_QUOTED_ARRAY:
{
short x_generic;
short type;
svalue_t *item;
type = sp->type;
x_generic = sp->x.generic;
for(item = vec->item; --cnt >= 0; item++)
{
if (sp_u.string == item->u.string
&& x_generic == item->x.generic
&& item->type == type)
break;
}
break;
}
case T_NUMBER:
if (!sp_u.number)
{
/* Search for 0 is special: it also finds destructed
* objects and closures on destructed objects.
*/
svalue_t *item;
short type;
for (item = vec->item; --cnt >= 0; item++)
{
if ( (type = item->type) == T_NUMBER)
{
if ( !item->u.number )
break;
}
else if (destructed_object_ref(item))
{
assign_svalue(item, &const0);
break;
}
}
break;
}
/* FALLTHROUGH */
case T_MAPPING:
case T_OBJECT:
case T_POINTER:
{
svalue_t *item;
short type = sp->type;
for (item = vec->item; --cnt >= 0; item++)
{
if (sp_u.number == item->u.number
&& item->type == type)
break;
}
break;
}
default:
if (sp->type == T_LVALUE)
error("Reference passed to member()\n");
fatal("Bad type to member(): %d\n", sp->type);
}
if (cnt >= 0)
{
cnt = (long)VEC_SIZE(vec) - cnt - 1;
}
/* else return -1 for failure */
pop_stack();
free_svalue(sp);
put_number(sp, cnt);
break;
}
/* --- Search a string --- */
if (sp[-1].type == T_STRING)
{
char *str, *str2;
int i;
if (sp->type != T_NUMBER)
goto bad_arg_2;
str = sp[-1].u.string;
i = sp->u.number;
str2 = i & ~0xff ? NULL : strchr(str, i);
i = str2 ? str2 - str : -1;
pop_stack();
free_svalue(sp);
put_number(sp, i);
break;
}
/* --- Search a string --- */
if (sp[-1].type == T_MAPPING)
{
int i;
i = get_map_value(sp[-1].u.map, sp) != &const0;
pop_stack();
free_svalue(sp);
put_number(sp, i);
break;
}
/* Otherwise it's not searchable */
goto bad_arg_1;
}
CASE(F_MKMAPPING); /* mkmapping <nargs> --- */
{
/* EFUN mkmapping()
*
* mapping mkmapping(mixed *arr1, mixed *arr2,...)
*
* Returns a mapping with indices from 'arr1' and values from
* 'arr2'... . arr1[0] will index arr2...[0], arr1[1] will index
* arr2...[1], etc. If the arrays are of unequal size, the mapping
* will only contain as much elements as are in the smallest
* array.
*/
long i, length, num_values;
mapping_t *m;
svalue_t *key;
GET_NUM_ARG
/* Check the arguments and set length to the size of
* the shortest array.
*/
length = LONG_MAX;
for (i = -num_arg; ++i <= 0; )
{
if ( sp[i].type != T_POINTER )
bad_arg_pc(i+num_arg, instruction, sp, pc);
if (length > (long)VEC_SIZE(sp[i].u.vec))
length = (long)VEC_SIZE(sp[i].u.vec);
}
if (max_mapping_size && length > max_mapping_size)
ERRORF(("Illegal mapping size: %ld\n", length));
/* Allocate the mapping */
num_values = num_arg - 1;
m = allocate_mapping(length, num_values);
if (!m)
ERROR("Out of memory\n")
/* Shift key through the first array and assign the values
* from the others.
*/
key = &(sp-num_values)->u.vec->item[length];
while (--length >= 0)
{
svalue_t *dest;
dest = get_map_lvalue_unchecked(m, --key);
if (!dest)
{
outofmemory("mapping entry");
/* NOTREACHED */
return MY_FALSE;
}
for (i = -num_values; ++i <= 0; )
{
/* If a key value appears multiple times, we have to free
* a previous assigned value to avoid a memory leak
*/
assign_svalue(dest++, &sp[i].u.vec->item[length]);
}
}
/* Clean up the stack and push the result */
pop_n_elems(num_arg);
push_mapping(m); /* Adds the second ref */
deref_mapping(sp->u.map); /* This will make ref count == 1 */
break;
}
CASE(F_M_ADD); /* m_add <nargs> --- */
{
/* EFUN m_add()
*
* mapping m_add(mapping m, mixed key, mixed data...)
*
* Add (or replace) an entry with index <key> in mapping <map>.
* The modified mapping is also returned as result.
*
* The values for the entry are taken from the <data> arguments.
* Unassigned entry values default to 0, extraneous <data> arguments
* are ignore.
*/
mapping_t *m;
svalue_t *argp;
svalue_t *entry;
int num_values;
GET_NUM_ARG
argp = sp - num_arg + 1;
/* Check the arguments. */
TYPE_TEST1(argp, T_MAPPING);
m = argp->u.map;
/* Get (or create) the mapping entry */
entry = get_map_lvalue(m, argp+1);
/* Transfer the given values from the stack into the mapping
* entry.
*/
num_values = m->num_values;
if (num_values > num_arg - 2)
num_values = num_arg - 2;
for ( argp += 2
; num_values > 0 && argp <= sp
; num_values--, argp++, entry++
)
{
transfer_svalue_no_free(entry, argp);
/* And since we take out values from under sp, play it
* safe:
*/
put_number(argp, 0);
}
/* We leave the reference to the mapping on the stack as result,
* but pop everything else.
*/
pop_n_elems(num_arg-1);
break;
}
CASE(F_M_DELETE); /* --- m_delete --- */
{
/* EFUN m_delete()
*
* mapping m_delete(mapping map, mixed key)
*
* Remove the entry with index 'key' from mapping 'map'. The
* changed mapping 'map' is also returned as result.
* If the mapping does not have an entry with index 'key',
* nothing is changed.
*/
mapping_t *m;
TYPE_TEST1(sp-1, T_MAPPING)
m = (sp-1)->u.map;
remove_mapping(m, sp);
pop_stack();
/* leave the mapping on the stack */
break;
}
CASE(F_M_INDICES); /* --- m_indices --- */
{
/* EFUN m_indices()
*
* mixed *m_indices(mapping map)
*
* Returns an array containing the indices of mapping 'map'.
*/
mapping_t *m;
vector_t *v;
TYPE_TEST1(sp, T_MAPPING)
m = sp->u.map;
inter_pc = pc;
inter_sp = sp;
v = m_indices(m);
free_mapping(m);
put_array(sp,v);
break;
}
CASE(F_M_VALUES); /* --- m_values --- */
{
/* EFUN m_values()
*
* mixed *m_values(mapping map)
* mixed *m_values(mapping map, int index)
*
* Returns an array with the values of mapping 'map'.
* If <index> is given as a number between 0 and the width of
* the mapping, the values from the given column are returned,
* else the values of the first column.
*/
mapping_t *m;
vector_t *v;
struct mvf_info vip;
mp_int size;
int num;
/* Get and check the arguments */
TYPE_TEST2(sp, T_NUMBER);
num = sp->u.number;
sp--;
if (sp->type != T_MAPPING || (m = sp->u.map)->num_values < 1)
goto bad_arg_1;
if (num < 0 || num >= m->num_values)
ERROR("Illegal index to m_values().\n");
/* Get the size of the mapping */
check_map_for_destr(m);
size = (mp_int)MAP_SIZE(m);
v = allocate_array(size);
/* Extract the desired column from the mapping */
vip.svp = v->item;
vip.num = num;
walk_mapping(m, m_values_filter, &vip);
free_mapping(m);
/* Push the result */
put_array(sp,v);
break;
}
CASE(F_SIZEOF); /* --- sizeof --- */
{
/* EFUN sizeof()
*
* int sizeof(mixed arr)
*
* Returns the number of elements of an array, the number of
* keys in a mapping, or the number of characters in a string.
*
* As a special case, the number 0 can be passed, and the function
* will return 0.
*/
long i;
if (sp->type == T_STRING)
{
i = (long)svalue_strlen(sp);
free_svalue(sp);
put_number(sp, i);
break;
}
if (sp->type == T_POINTER)
{
i = (long)VEC_SIZE(sp->u.vec);
free_svalue(sp);
put_number(sp, i);
break;
}
if (sp->type == T_MAPPING)
{
mapping_t *m = sp->u.map;
check_map_for_destr(m);
i = (long)MAP_SIZE(m);
free_svalue(sp);
put_number(sp, i);
break;
}
if (sp->type == T_NUMBER && sp->u.number == 0)
break;
goto bad_arg_1;
}
CASE(F_UNIQUE_ARRAY); /* --- unique_array --- */
{
/* EFUN unique_array()
*
* mixed unique_array(object *obarr, string seperator)
* mixed unique_array(object *obarr, string seperator, mixed skip)
*
* Groups objects together for which the separator function
* returns the same value. obarr should be an array of objects,
* other types are ignored. The separator function is called only
* once in each object in obarr. If no separator function is
* given, 0 is used instead of a return value.
* If a 3rd argument is given and this argument matches the
* return value of the separator function this object will not be
* included in the returned array.
*/
vector_t *res;
assign_eval_cost();
inter_pc = pc;
inter_sp = sp;
TYPE_TEST1(sp-2, T_POINTER)
TYPE_TEST2(sp-1, T_STRING)
check_for_destr((sp-2)->u.vec);
res = make_unique((sp-2)->u.vec, (sp-1)->u.string, sp);
/* Clean up the stack and push the result */
pop_stack();
pop_stack();
free_svalue(sp);
if (res)
{
put_array(sp, res);
}
else
put_number(sp, 0);
break;
}
CASE(F_UNMKMAPPING); /* --- unmkmapping --- */
{
/* EFUN unmkmapping()
*
* mixed* unmkmapping(mapping map)
*
* Take mapping <map> and return an array of arrays with the keys
* and values from the mapping.
*
* The return array has the form ({ keys[], data0[], data1[], ... }).
*/
svalue_t *svp;
mapping_t *m;
vector_t *v;
struct mvf_info vip;
mp_int size;
int i;
/* Get the arguments */
if (sp->type != T_MAPPING)
goto bad_arg_1;
m = sp->u.map;
/* Determine the size of the mapping and allocate the result vector */
check_map_for_destr(m);
size = (mp_int)MAP_SIZE(m);
v = allocate_array(m->num_values+1);
/* Allocate the sub vectors */
for (i = 0, svp = v->item; i <= m->num_values; i++, svp++)
{
vector_t *v2;
v2 = allocate_array(size);
put_array(svp, v2);
}
/* Copy the elements from the mapping into the vector brush */
vip.svp = v->item;
vip.num = 0;
vip.width = m->num_values;
walk_mapping(m, m_unmake_filter, &vip);
/* Clean up the stack and push the result */
free_mapping(m);
put_array(sp,v);
break;
}
CASE(F_WIDTHOF); /* --- widthof --- */
{
/* EFUN widthof()
*
* int widthof (mapping map)
*
* Returns the number of values per key of mapping <map>.
* If <map> is 0, the result is 0.
*/
int width;
if (sp->type == T_NUMBER && sp->u.number == 0)
break;
TYPE_TEST1(sp, T_MAPPING)
width = sp->u.map->num_values;
free_mapping(sp->u.map);
put_number(sp, width);
break;
}
/* --- Efuns: Functions and Closures --- */
CASE(F_APPLY); /* --- apply <nargs> --- */
{
/* EFUN apply()
*
* mixed apply(closure cl, ...)
*
* Call the closure <cl> and pass it all the extra arguments
* given in the call. If the last argument is an array, it
* is flattened, ie. passed as a bunch of single arguments.
* TODO: Use the MudOS-Notation '(*f)(...)' as alternative.
*/
svalue_t *args;
GET_NUM_ARG
args = sp -num_arg +1;
if (args->type != T_CLOSURE)
{
/* Not a closure: pop the excess args and return <cl>
* as result.
*/
while (--num_arg)
free_svalue(sp--);
break;
}
if (sp->type == T_POINTER)
{
/* The last argument is an array: flatten it */
vector_t *vec; /* the array */
svalue_t *svp; /* pointer into the array */
long i; /* (remaining) vector size */
vec = sp->u.vec;
i = (long)VEC_SIZE(vec);
num_arg += i - 1;
/* Check if the target closure can handle
* all the arguments without overflowing the stack.
*/
switch( (sp - num_arg + i)->x.closure_type )
{
default:
if ((sp - num_arg + i)->x.closure_type >= 0)
bad_arg_pc(num_arg - i + 1, instruction, sp, pc);
/* TODO: Be more specific */
/* else: operator/sefun/efun closure: FALLTHROUGH */
case CLOSURE_LFUN:
case CLOSURE_ALIEN_LFUN:
case CLOSURE_LAMBDA:
case CLOSURE_BOUND_LAMBDA:
if (num_arg + (sp - VALUE_STACK) < EVALUATOR_STACK_SIZE)
break;
ERRORF(("VM Stack overflow: %ld too high.\n"
, (long)(num_arg + (sp - VALUE_STACK) - EVALUATOR_STACK_SIZE) ));
break;
}
/* Push the array elements onto the stack, overwriting the
* array value itself.
*/
if (deref_array(vec))
{
for (svp = vec->item; --i >= 0; )
{
if (destructed_object_ref(svp))
{
put_number(sp, 0);
sp++;
svp++;
}
else
assign_svalue_no_free(sp++, svp++);
}
}
else
{
/* The array will be freed, so use a faster function */
for (svp = vec->item; --i >= 0; ) {
if (destructed_object_ref(svp))
{
put_number(sp, 0);
sp++;
svp++;
}
else
{
sp++;
transfer_svalue_no_free_spc(sp-1, svp++, sp, pc);
}
}
free_empty_vector(vec);
}
sp--; /* undo the last extraneous sp++ */
}
/* Prepare to call the closure */
args = sp -num_arg +1;
TYPE_TEST1(args, T_CLOSURE)
/* No external calls may be done when this object is
* destructed.
*/
if (current_object->flags & O_DESTRUCTED)
{
pop_n_elems(num_arg);
push_number(0);
break;
}
inter_pc = pc;
inter_sp = sp;
ASSIGN_EVAL_COST
call_lambda(args, num_arg - 1);
/* Cleanup the stack (note that the closure might have
* been removed through object destruction)
*/
sp = args;
free_svalue(sp);
*sp = sp[1];
break;
}
CASE(F_BIND_LAMBDA); /* --- bind_lambda --- */
{
/* EFUN bind_lambda()
*
* closure bind_lambda(closure cl, object ob = 1)
*
* Binds an unbound closure <cl> to object <ob> and return the
* bound closure.
*
* If the optional argument ob is not this_object(), the privilege
* violation ("bind_lambda", this_object(), ob) occurs.
*
* If the argument <ob> is omitted, the closure is bound to
* this_object(), and additionally the function accepts unbindable
* closures without complaint.
*
* Note: the 'default' value for <ob> is const1 so that the omittal
* can be detected.
*/
object_t *ob;
TYPE_TEST1(sp-1, T_CLOSURE)
if (sp->type == T_OBJECT)
{
/* If <ob> is given, check for a possible privilege breach */
ob = sp->u.ob;
if (ob != current_object
&& !privilege_violation("bind_lambda", sp))
{
free_object(ob, "bind_lambda");
sp--;
break;
}
}
else if (sp->type == T_NUMBER && sp->u.number == 1)
{
/* this_object is ok */
ob = ref_object(current_object, "bind_lambda");
}
else
goto bad_arg_2;
sp--; /* points to the closure now */
switch(sp->x.closure_type)
{
case CLOSURE_LFUN:
case CLOSURE_LAMBDA:
case CLOSURE_IDENTIFIER:
case CLOSURE_PRELIMINARY:
/* Unbindable closures. Free the ob reference and
* throw an error (unless <ob> has been omitted)
*/
free_object(ob, "bind_lambda");
if (sp[1].type == T_NUMBER)
break;
goto bad_arg_1;
case CLOSURE_ALIEN_LFUN:
/* Rebind an alien lfun to the given object */
free_object(sp->u.lambda->ob, "bind_lambda");
sp->u.lambda->ob = ob;
break;
default:
/* efun, simul_efun, operator closures: rebind it */
free_object(sp->u.ob, "bind_lambda");
sp->u.ob = ob;
break;
case CLOSURE_BOUND_LAMBDA:
{
/* Rebind an already bound lambda closure */
lambda_t *l;
if ( (l = sp->u.lambda)->ref == 1)
{
/* We are the only user of the lambda: simply rebind it.
*/
object_t **obp;
obp = &l->ob;
free_object(*obp, "bind_lambda");
*obp = ob;
break;
}
else
{
/* We share the closure with others: create our own
* copy, bind it and put it onto the stack in place of
* the original one.
*/
lambda_t *l2;
l->ref--;
l2 = xalloc(sizeof *l);
l2->ref = 1;
l2->ob = ob;
l2->function.lambda = l->function.lambda;
l->function.lambda->ref++;
sp->u.lambda = l2;
break;
}
}
case CLOSURE_UNBOUND_LAMBDA:
{
/* Whee, an unbound lambda: create the bound-lambda structure
* and put it onto the stack in place of the unbound one.
*/
lambda_t *l;
l = xalloc(sizeof *l);
l->ref = 1;
l->ob = ob;
l->function.lambda = sp->u.lambda;
/* The ref to the unbound closure is just transferred from
* sp to l->function.lambda.
*/
sp->x.closure_type = CLOSURE_BOUND_LAMBDA;
sp->u.lambda = l;
break;
}
}
break;
}
CASE(F_CALL_OTHER); /* --- call_other <nargs> --- */
{
/* EFUN call_other()
*
* unknown call_other(object|string ob, string str, mixed arg, ...)
* unknown ob->fun(mixed arg, ...)
*
* Call a member function in another object with an argument. The
* return value is returned from the other object. The object can be
* given directly or as a string (i.e. its file name). If it is given
* by a string and the object does not exist yet, it will be loaded.
*
* unknown * call_other(object|string *ob, string str, mixed arg, ...)
* unknown * ob->fun(mixed arg, ...)
*
* Call a member function in other objects with the given arguments.
* The return values is returned collected in an array.
* Every object can be given directly or as a string (i.e. its file name).
* If it is given by a string and the object does not exist yet, it will
* be loaded.
*
* TODO: A VOID_CALL_OTHER would be nice to have when the result
* TODO:: is not used.
*/
svalue_t *arg;
object_t *ob;
GET_NUM_ARG
inter_pc = pc;
inter_sp = sp;
arg = sp - num_arg + 1;
/* Test the arguments */
if (arg[0].type != T_OBJECT
&& arg[0].type != T_STRING
&& arg[0].type != T_POINTER
)
goto bad_arg_1;
TYPE_TEST2(arg+1, T_STRING)
if (arg[1].u.string[0] == ':')
ERRORF(("Illegal function name in call_other: %s\n",
arg[1].u.string))
/* No external calls may be done when this object is
* destructed.
*/
if (current_object->flags & O_DESTRUCTED)
{
pop_n_elems(num_arg);
push_number(0);
break;
}
if (arg[0].type != T_POINTER)
{
/* --- The normal call other to a single object --- */
ASSIGN_EVAL_COST
if (arg[0].type == T_OBJECT)
ob = arg[0].u.ob;
else if (arg[0].type == T_STRING)
{
ob = get_object(arg[0].u.string);
if (ob == NULL)
ERROR("call_other() failed\n")
}
else
goto bad_arg_1;
/* Traceing, if necessary */
if (TRACEP(TRACE_CALL_OTHER) && TRACE_IS_INTERACTIVE())
{
if (!++traceing_recursion)
{
do_trace("Call other ", arg[1].u.string, "\n");
}
traceing_recursion--;
}
/* Call the function with the remaining args on the stack.
*/
if (!apply_low(arg[1].u.string, ob, num_arg-2, MY_FALSE))
{
/* Function not found */
pop_n_elems(num_arg);
push_number(0);
break;
}
sp -= num_arg - 3;
/* The result of the function call is on the stack. But so
* is the function name and object that was called.
* These have to be removed.
*/
arg = sp; /* Remember where the function call result is */
free_string_svalue(--sp);
free_svalue(--sp); /* Remove old arguments to call_other */
*sp = *arg; /* Re-insert function result */
}
else
{
/* --- The other call other to an array of objects --- */
svalue_t *svp;
size_t size;
/* The array with the objects will also hold the results.
* For that, it mustn't be shared, therefore we create a
* copy if necessary.
*/
size = VEC_SIZE(arg->u.vec);
if (arg->u.vec->ref != 1 && size != 0)
{
vector_t *vec;
svalue_t *to;
vec = allocate_array_unlimited(size);
if (!vec)
ERROR("Out of memory.\n");
for (svp = arg->u.vec->item, to = vec->item
; size != 0
; size--, svp++, to++)
assign_svalue_no_free(to, svp);
free_array(arg->u.vec);
arg->u.vec = vec; /* adopts the reference */
}
/* Now loop over the array of objects and call the function
* in each of it. For that, the arguments are duly replicated
* for every call.
*/
size = VEC_SIZE(arg->u.vec);
svp = arg->u.vec->item;
for ( ; size != 0; size--, svp++)
{
int i;
ASSIGN_EVAL_COST
inter_sp = sp; /* Might be clobbered from previous loop */
if (svp->type == T_OBJECT)
ob = svp->u.ob;
else if (svp->type == T_STRING)
{
ob = get_object(svp->u.string);
if (ob == NULL)
{
ERROR("call_other() failed\n")
/* NOTREACHED */
continue;
}
}
else if (svp->type == T_NUMBER && svp->u.number == 0)
{
free_svalue(svp);
put_number(svp, 0);
continue;
}
else
ERRORF(("Bad argument for call_other() at index %ld\n"
, (long)(svp - arg->u.vec->item)));
/* Destructed objects yield 0 */
if (ob->flags & O_DESTRUCTED)
{
free_svalue(svp);
put_number(svp, 0);
continue;
}
/* Traceing, if necessary */
if (TRACEP(TRACE_CALL_OTHER) && TRACE_IS_INTERACTIVE())
{
if (!++traceing_recursion)
{
do_trace("Call other ", arg[1].u.string, "\n");
}
traceing_recursion--;
}
/* Duplicate the arguments to pass, increasing sp on
* the way. Optimizing this for the last pass is
* dangerous as not every iteration will come here.
*/
for (i = 2; i < num_arg; i++)
assign_svalue_no_free(++sp, arg+i);
/* Call the function with the remaining args on the stack.
*/
inter_sp = sp; /* update to new setting */
if (!apply_low(arg[1].u.string, ob, num_arg-2, MY_FALSE))
{
/* Function not found: clean up the stack and
* assign 0 as result.
*/
pop_n_elems(num_arg-2);
free_svalue(svp);
put_number(svp, 0);
}
else
{
/* Function found - assign the result from the stack */
sp -= num_arg-3;
free_svalue(svp);
transfer_svalue_no_free(svp, sp--);
}
} /* for (objects in array) */
/* Remove the function call arguments from the stack.
*/
pop_n_elems(num_arg-2);
/* Calls complete, left on the stack are now the function name
* and, in arg, the final result.
*/
free_string_svalue(sp); sp--;
}
break;
}
CASE(F_FUNCALL); /* --- funcall <nargs> --- */
{
/* EFUN funcall()
*
* mixed funcall(closure cl, mixed arg ...)
*
* Evaluates the closure. The extra args will be passed as args
* to the closure. If cl is not a closure, it will simply be
* returned.
*/
svalue_t *args;
GET_NUM_ARG
args = sp -num_arg +1;
if (args->type == T_CLOSURE)
{
/* No external calls may be done when this object is
* destructed.
*/
if (current_object->flags & O_DESTRUCTED) {
pop_n_elems(num_arg);
push_number(0);
break;
}
inter_pc = pc;
inter_sp = sp;
ASSIGN_EVAL_COST
/* Call the closure and push the result */
call_lambda(args, num_arg - 1);
sp = args;
free_svalue(sp);
*sp = sp[1];
}
else
{
/* Not a closure: pop the excess args and return <cl>
* as result.
*/
while (--num_arg)
free_svalue(sp--);
}
break;
}
CASE(F_LAMBDA); /* --- lambda --- */
{
/* EFUN lambda()
*
* closure lambda(mixed *arr, mixed)
*
* Constructs a lambda closure, like lambda function in LISP.
* The closure is bound the creating object, and thus can contain
* references to global variables.
*
* The first argument is an array describing the arguments
* (symbols) passed to the closure upon evaluation by funcall()
* or apply(). It may be 0 if no arguments are required.
*/
lambda_t *l;
vector_t *args;
inter_pc = pc;
inter_sp = sp;
if (sp[-1].type != T_POINTER)
{
/* If '0' is given for the args array, replace it
* with the null-vector.
*/
if (sp[-1].type != T_NUMBER || sp[-1].u.number)
goto bad_arg_1;
args = ref_array(&null_vector);
}
else
{
args = sp[-1].u.vec;
}
/* Create the lambda closure */
l = lambda(args, sp, current_object);
l->ob = ref_object(current_object, "lambda");
/* Clean up the stack and push the result */
pop_stack();
free_array(args);
sp->type = T_CLOSURE;
sp->x.closure_type = CLOSURE_LAMBDA;
sp->u.lambda = l;
break;
}
CASE(F_QUOTE); /* --- quote --- */
{
/* EFUN quote()
*
* mixed quote(mixed)
*
* Converts arrays to quoted arrays and strings to symbols.
* Symbols and quoted arrays get quoted once more.
*/
switch (sp->type)
{
case T_QUOTED_ARRAY:
case T_SYMBOL:
sp->x.quotes++;
break;
case T_POINTER:
sp->type = T_QUOTED_ARRAY;
sp->x.quotes = 1;
break;
case T_STRING:
if (sp->x.string_type != STRING_SHARED)
{
/* Symbols must be shared strings */
char *str = sp->u.string;
sp->u.string = make_shared_string(str);
if (sp->x.string_type == STRING_MALLOC)
xfree(str);
}
sp->type = T_SYMBOL;
sp->x.quotes = 1;
break;
default:
goto bad_arg_1;
}
break;
}
CASE(F_UNQUOTE); /* --- unquote --- */
{
/* EFUN unquote()
*
* mixed unquote(mixed)
*
* Removes a quote from quoted arrays and symbols. When the
* last quote from a symbol is removed, the result is a string.
*/
switch (sp->type)
{
case T_QUOTED_ARRAY:
sp->x.quotes--;
if (!sp->x.quotes)
sp->type = T_POINTER;
break;
case T_SYMBOL:
sp->x.quotes--;
if (!sp->x.quotes)
{
sp->type = T_STRING;
sp->x.string_type = STRING_SHARED;
}
break;
default:
goto bad_arg_1;
}
break;
}
CASE(F_SYMBOL_FUNCTION); /* --- symbol_function --- */
{
/* EFUN symbol_function()
*
* closure symbol_function(symbol arg)
* closure symbol_function(string arg)
* closure symbol_function(string arg, object|string ob)
*
* Constructs a lfun closure, efun closure or operator closure
* from the first arg (string or symbol). For lfuns, the second
* arg is the object that the lfun belongs to, specified by
* the object itself or by its name (the object will be loaded
* in the second case)
*
* Private lfuns can never be accessed this way, static and
* protected lfuns only if <ob> is the current object.
*/
object_t *ob;
program_t *prog;
int i;
/* If 'arg' is not a symbol, make sure it's a shared string. */
if (sp[-1].type != T_SYMBOL)
{
if (sp[-1].type == T_STRING)
{
if (sp[-1].x.string_type != STRING_SHARED)
{
char *str;
str = sp[-1].u.string;
sp[-1].u.string = make_shared_string(str);
if (sp[-1].x.string_type == STRING_MALLOC)
xfree(str);
sp[-1].x.string_type = STRING_SHARED;
}
}
else
goto bad_arg_1;
}
/* If 'ob' is not of type object, it might be the name of
* an object to load, or we need to make an efun symbol.
*/
if (sp->type != T_OBJECT)
{
/* If it's the number 0, an efun symbol is desired */
if (sp->type == T_NUMBER && sp->u.number == 0)
{
sp--;
inter_pc = pc;
symbol_efun(sp);
break;
}
/* Find resp. load the object by name */
TYPE_TEST2(sp, T_STRING)
inter_sp = sp;
inter_pc = pc;
ob = get_object(sp->u.string);
if (!ob)
error("Object '%s' not found.\n", sp->u.string);
free_svalue(sp);
put_ref_object(sp, ob, "symbol_function");
}
else
{
ob = sp->u.ob;
}
/* We need the object's program */
if (O_PROG_SWAPPED(ob))
{
ob->time_of_ref = current_time;
if (load_ob_from_swap(ob) < 0)
{
inter_sp = sp;
error("Out of memory\n");
}
}
/* Find the function in the program */
prog = ob->prog;
i = find_function(sp[-1].u.string, prog);
/* If the function exists and is visible, create the closure
*/
if ( i >= 0
&& ( !(prog->functions[i] & (TYPE_MOD_STATIC|TYPE_MOD_PROTECTED|TYPE_MOD_PRIVATE) )
|| ( !(prog->functions[i] & TYPE_MOD_PRIVATE)
&& current_object == ob)
)
)
{
lambda_t *l;
ph_int closure_type;
l = xalloc(sizeof *l);
if (!l)
{
error("Out of memory.\n");
/* NOTREACHED */
break;
}
l->ref = 1;
l->ob = current_object;
/* If ob == current_object, adopt the reference,
* otherwise a reference will be added below.
*/
/* Set the closure */
if (ob == current_object)
{
if (!(prog->flags & P_REPLACE_ACTIVE)
|| !lambda_ref_replace_program( l
, CLOSURE_ALIEN_LFUN
, 0, NULL, NULL)
)
{
current_object->flags |= O_LAMBDA_REFERENCED;
l->function.index = (unsigned short)i;
closure_type = CLOSURE_LFUN;
}
else
{
l->function.alien.ob
= ref_object(current_object, "symbol_function");
l->function.alien.index = (unsigned short)i;
closure_type = CLOSURE_ALIEN_LFUN;
}
}
else
{
object_t *curobj = current_object; /* saved */
l->function.alien.ob = ob; /* adopt the ref */
l->function.alien.index = (unsigned short)i;
closure_type = CLOSURE_ALIEN_LFUN;
ref_object(current_object, "symbol_function");
/* The ref missing from l->ob=current_object above */
current_object = ob;
/* Required by lambda_ref_replace_program()
* It will be restored below from curobj.
*/
if (!(prog->flags & P_REPLACE_ACTIVE)
|| !lambda_ref_replace_program( l
, CLOSURE_ALIEN_LFUN
, 0, NULL, NULL)
)
{
ob->flags |= O_LAMBDA_REFERENCED;
}
current_object = curobj;
}
/* Clean up the stack and push the result */
sp--;
deref_string(sp->u.string);
sp->type = T_CLOSURE;
sp->x.closure_type = closure_type;
sp->u.lambda = l;
break;
}
/* Symbol can't be created - free the stack and push 0 */
free_object(ob, "symbol_function");
sp--;
free_string(sp->u.string);
put_number(sp, 0);
break;
}
CASE(F_CALL_OUT); /* --- call_out <nargs> --- */
/* EFUN call_out()
*
* void call_out(string fun, int delay, mixed arg, ...)
* void call_out(closure cl, int delay, mixed arg, ...)
*
* Set up a call to function fun or closure cl in the current
* object. The call will take place in delay seconds, with the
* remaining argument list provided. delay can be a minimum time
* of one second.
*/
GET_NUM_ARG
inter_pc = pc;
sp = new_call_out(sp, (short)num_arg);
break;
CASE(F_CALL_OUT_INFO); /* --- call_out_info --- */
/* EFUN call_out_info()
*
* mixed *call_out_info(void)
*
* Get information about all pending call outs. The result is an
* array in which every entry is itself an array describing one
* call_out.
*
* The efun causes the the privilege violation ("call_out_info",
* this_object()). If it is not satisfied, the result will be
* the empty array.
*/
inter_sp = sp;
inter_pc = pc;
if (privilege_violation("call_out_info", &const0))
{
push_referenced_vector(get_all_call_outs());
}
else
{
push_vector(&null_vector);
}
break;
CASE(F_FIND_CALL_OUT); /* --- find_call_out --- */
{
/* EFUN find_call_out()
*
* int find_call_out(string func)
* int find_call_out(closure func)
*
* Find the first call-out due to be executed for function func
* in the current object, and return the time left. If no call-out
* is found return -1.
*/
inter_pc = pc;
find_call_out(current_object, sp, MY_FALSE);
break;
}
CASE(F_REMOVE_CALL_OUT); /* --- remove_call_out --- */
{
/* EFUN remove_all_out()
*
* int remove_call_out(string fun)
* int remove_call_out(closure fun)
*
* Remove next pending call-out for function fun in this object.
* The time left is returned.
*
* -1 is returned if there were no call-outs pending to this
* function.
*/
inter_pc = pc;
find_call_out(current_object, sp, MY_TRUE);
break;
}
CASE(F_SET_HEART_BEAT); /* --- set_heart_beat --- */
{
/* EFUN set_heart_beat()
*
* int set_heart_beat(int flag)
*
* Enable or disable heart beat. The driver will apply
* the lfun heart_beat() to the current object every 2 seconds,
* if it is enabled. If the heart beat is not needed for the
* moment, then do disable it. This will reduce system overhead.
*
* Return true for success, and false for failure.
*
* Disabling an already disabled heart beat (and vice versa
* enabling and enabled heart beat) counts as failure.
*/
int i;
TYPE_TEST1(sp, T_NUMBER)
i = set_heart_beat(current_object, sp->u.number);
sp->u.number = i;
break;
}
/* --- Efuns: Objects --- */
CASE(F_CLONE_OBJECT); /* --- clone_object --- */
{
/* EFUN clone_object()
*
* object clone_object(string name)
* object clone_object(object template)
*
* Clone a new object from definition <name>, or alternatively from
* the object <template>. In both cases, the new object is given an
* unique name and returned.
*/
object_t *ob;
assign_eval_cost();
inter_sp = sp;
inter_pc = pc;
/* Get the argument and clone the object */
if (sp->type == T_STRING)
ob = clone_object(sp->u.string);
else if (sp->type == T_OBJECT)
ob = clone_object(sp->u.ob->load_name);
else
goto bad_arg_1;
free_svalue(sp);
if (ob)
put_ref_object(sp, ob, "F_CLONE_OBJECT");
else
put_number(sp, 0);
break;
}
CASE(F_DESTRUCT); /* --- destruct --- */
/* EFUN destruct()
*
* void destruct(object ob)
*
* Completely destroy and remove object ob (if not already done so).
* After the call to destruct(), no global variables will exist any
* longer, only local ones, and arguments.
*
* If an object self-destructs, it will not immediately terminate
* execution. If the efun this_object() will be called by the
* destructed object, the result will be 0.
*
* The efun accepts destructed objects as argument (which appear
* as the number 0) and the simply acts as a no-op in that case.
*
* Internally, the object is not destructed immediately, but
* instead put into a list and finally destructed after the
* current execution has ended.
*/
if (T_NUMBER != sp->type || sp->u.number)
{
assign_eval_cost();
TYPE_TEST1(sp, T_OBJECT)
inter_sp = sp;
inter_pc = pc;
destruct_object(sp);
}
pop_stack();
break;
CASE(F_EXEC); /* --- exec --- */
{
/* EFUN exec()
*
* object exec(object new, object old)
*
* exec() switches the connection from the interactive object old
* to the object new. If the new object is also interactive, it's
* connection will be transferred to the old object, thus
* exchaning the two connections between the object. If the new
* object is not interactive, the old will not be interactive
* anymore after the exec call succeeded.
* It is used to load different "user objects" or to reconnect
* link dead users.
*
* To provide security mechanisms, the interpreter calls
* master->valid_exec(current_program, new, old), which must
* return anything other than 0 to allow this exec() invocation.
*/
int i;
assign_eval_cost();
TYPE_TEST1(sp-1, T_OBJECT)
TYPE_TEST2(sp, T_OBJECT)
inter_sp = sp;
inter_pc = pc;
i = replace_interactive((sp-1)->u.ob, sp->u.ob, current_prog->name);
/* FinalFrontier suggests 'current_object->prog->name' */
pop_stack();
free_svalue(sp); /* object might have been destructed */
put_number(sp, i);
break;
}
CASE(F_FIND_OBJECT); /* --- find_object --- */
{
/* EXEC find_object()
*
* object find_object(string str)
*
* Find an object with the file_name str. If the object isn't loaded,
* it will not be found.
*/
object_t *ob;
TYPE_TEST1(sp, T_STRING)
ob = find_object(sp->u.string);
free_svalue(sp);
if (ob)
put_ref_object(sp, ob, "find_object");
else
put_number(sp, 0);
break;
}
CASE(F_FUNCTION_EXISTS); /* --- function_exists --- */
{
/* EXEC function_exists()
*
* string function_exists(string str, object ob)
*
* Return the file name of the object that defines the function
* str in object ob. The returned value can be different from
* file_name(ob) if the function is defined in an inherited
* object. In native mode, the returned name always begins with a
* '/' (absolute path). 0 is returned if the function was not
* defined, or was defined as static.
*/
char *str, *res, *p;
TYPE_TEST1(sp-1, T_STRING)
TYPE_TEST2(sp, T_OBJECT)
inter_sp = sp; /* error possible when out of memory */
str = function_exists((sp-1)->u.string, sp->u.ob);
free_svalue(sp);
free_svalue(--sp);
if (str)
{
/* Make a copy of the string so that we can remove
* a the trailing '.c'. In non-compat mode, we also
* have to add the leading '/'.
*/
p = strrchr (str, '.');
if (p)
*p = '\0'; /* temporarily mask out the '.c' */
if (compat_mode)
res = string_copy (str);
else
res = add_slash(str);
if (p)
*p = '.'; /* undo the change above */
if (!res)
{
sp--;
ERROR("Out of memory\n")
}
put_malloced_string(sp, res);
}
else
{
put_number(sp, 0);
}
break;
}
CASE(F_INPUT_TO); /* --- input_to <nargs> --- */
{
/* EFUN input_to()
*
* void input_to(string fun)
* void input_to(string fun, int flag, ...)
*
* Enable next line of user input to be sent to the local
* function fun as an argument. The input line will not be
* parsed, only when it starts with a "!" (like a kind of shell
* escape) (this feature may be disabled).
* The function <fun> may be static, but must not be private (or
* it won't be found).
*
* Note that fun is not called immediately but after pressing the
* RETURN key.
*
* If input_to() is called more than once in the same execution,
* only the first call has any effect.
*
* The optional 3rd and following args will be passed as second and
* subsequent args to the function fun. (This feature is was
* added only recently, to avoid the need for global variables)
*/
GET_NUM_ARG
inter_pc = pc;
inter_sp = sp;
sp = e_input_to(sp, num_arg);
break;
}
CASE(F_INTERACTIVE); /* --- interactive --- */
{
/* EFUN interactive()
*
* int interactive(object ob)
*
* Return non-zero if ob, or when the argument is omitted, this
* object(), is an interactive user. Will return 1 if the
* object is interactive, else 0.
*/
int i;
object_t *ob;
interactive_t *ip;
TYPE_TEST1(sp, T_OBJECT)
ob = sp->u.ob;
(void)O_SET_INTERACTIVE(ip, ob);
i = ip && !ip->do_close;
deref_object(ob, "interactive");
put_number(sp, i);
break;
}
CASE(F_LOAD_NAME); /* --- load_name --- */
{
/* 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.
*
* As a special case, if <ob> is 0, 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.
*
* 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.
*/
char *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, return 0. */
if (sp->type == T_NUMBER && sp->u.number == 0)
{
break;
}
/* 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);
break;
}
if (sp->type != T_STRING)
goto bad_arg_1;
/* Argument is a string: try to find the object for it */
s = sp->u.string;
ob = find_object(s);
if (ob)
{
/* Got it */
s = ob->load_name;
free_string_svalue(sp);
put_ref_string(sp, s);
break;
}
/* There is no object for the string argument: just normalize
* the string. First check if it ends in #<number>.
*/
mem = NULL;
hash = strchr(s, '#');
if (!hash)
{
/* No '#' at all: make the name sane directly */
name = (char *)make_name_sane(s, !compat_mode);
if (!name)
name = 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);
break;
}
/* Good, we can slash off the '#<number>' */
len = (size_t)(hash - s);
p = mem = xalloc(len+1);
if (!p)
ERROR("Out of memory.");
strncpy(p, 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 (s != name)
{
free_string_svalue(sp);
if (name == mem)
{
put_malloced_string(sp, name);
mem = NULL; /* do not deallocate this */
}
else
{
put_volatile_string(sp, name);
}
}
if (mem)
xfree(mem);
break;
}
CASE(F_LOAD_OBJECT); /* --- load_object --- */
{
/* EFUN load_object()
*
* object load_object(string name)
*
* Load the object from the file <name> and return it. If the
* object already exists, just return it.
*
* This efun can be used only to load blueprints - for clones, use
* the efun clone_object().
*/
object_t *ob;
ASSIGN_EVAL_COST
inter_pc = pc;
inter_sp = sp;
TYPE_TEST1(sp, T_STRING);
ob = get_object(sp->u.string);
free_svalue(sp);
if (ob)
put_ref_object(sp, ob, "F_LOAD_OBJECT");
else
put_number(sp, 0);
break;
}
CASE(F_OBJECT_NAME); /* --- object_name --- */
{
/* 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 <ob> is 0, return 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.
*/
char *name,*res;
/* If the argument is 0, return 0. */
if (sp->type == T_NUMBER && sp->u.number == 0)
{
break;
}
TYPE_TEST1(sp, T_OBJECT)
name = sp->u.ob->name;
if (compat_mode)
res = string_copy(name);
else
res = add_slash(name);
if (!res)
ERROR("Out of memory\n")
free_object_svalue(sp);
put_malloced_string(sp, res);
break;
}
CASE(F_REPLACE_PROGRAM); /* --- replace_program <narg> --- */
{
/* 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;
long name_len;
char *name;
program_t *new_prog; /* the replacing program */
program_t *curprog; /* the current program */
int offsets[2]; /* the offsets of the replacing prog */
GET_NUM_ARG
inter_pc = pc;
inter_sp = sp;
if (num_arg > 0)
TYPE_TEST1(sp, T_STRING)
if (!current_object)
ERROR("replace_program called with no current object\n")
if (current_object == simul_efun_object)
ERROR("replace_program on simul_efun object\n")
if (current_object->flags & O_LAMBDA_REFERENCED)
{
/* This was an ERROR(), and should be a proper warning, except
* that the driver doesn't have such a thing.
*/
debug_message("%s Object '%s', program '%s': Cannot schedule "
"replace_program() after binding lambda closures.\n"
, time_stamp(), current_object->name
, current_prog->name
);
for ( ; num_arg != 0; num_arg--)
pop_stack();
break;
}
curprog = current_object->prog;
if (num_arg < 1)
{
size_t replace_index; /* Inherit index of the replacing program */
/* Just take the first normal inherited program */
if (curprog->num_inherited < 1)
ERROR("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)
{
ERROR("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
{
/* Create the full program name with a trailing '.c' and without
* a leading '/' to match the internal name representation.
*/
name_len = (long)svalue_strlen(sp);
name = alloca((size_t)name_len+3);
strcpy(name, sp->u.string);
if (name[name_len-2] != '.' || name[name_len-1] != 'c')
strcat(name,".c");
if (*name == '/')
name++;
new_prog = search_inherited(name, curprog, offsets);
if (!new_prog)
{
/* Given program not inherited, maybe it's the current already.
*/
if (!strcmp(name, curprog->name ))
{
new_prog = current_object->prog;
offsets[0] = offsets[1] = 0;
}
else
{
ERROR("program to replace the current one with has "
"to be inherited\n")
}
}
pop_stack();
}
/* Program found, now check if it contains virtual variables.
* See b-030119 for an explanation.
*/
if (offsets[0] != 0)
{
int i;
Bool has_virtual = MY_FALSE;
for (i = 0; !has_virtual && i < new_prog->num_variables; i++)
{
if (new_prog->variable_names[i].flags & TYPE_MOD_VIRTUAL)
has_virtual = MY_TRUE;
}
if (has_virtual)
{
debug_message("replacement program '%s' has virtual variables "
"and is not the first inherited program\n"
, new_prog->name);
break;
}
}
/* Program found, 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];
break;
}
CASE(F_SET_NEXT_RESET); /* --- set_next_reset() --- */
{
/* 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;
TYPE_TEST1(sp, T_NUMBER)
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;
}
break;
}
CASE(F_SET_THIS_OBJECT); /* --- set_this_object --- */
{
/* EFUN set_this_object()
*
* void set_this_object(object object_to_pretend_to_be);
*
* Set this_object() to <object_to_pretend_to_be>. A privilege
* violation ("set_this_object", this_object(), object_to_be)
* occurs.
*
* It changes the result of this_object() in the using function, and
* the result of previous_object() in functions called in other
* objects by call_other(). Its effect will remain till there is a
* return of an external function call, or another call of
* set_this_object(). While executing code in the master
* object's program or the primary simul_efun object's program,
* set_this_object() is granted even if this_object() is altered by
* set_this_object(). This does not apply to functions inherited from
* other programs.
*
* Use it with extreme care to avoid inconsistencies. After a call of
* set_this_object(), some LPC-constructs might behave in an odd
* manner, or even crash the system. In particular, using global
* variables or calling local functions (except by call_other) is
* illegal.
*
* With the current implementation, global variables can be accessed,
* but this is not guaranteed to work in subsequent versions.
*
* Allowed are call_other, map functions, access of local variables
* (which might hold array pointers to a global array), simple
* arithmetic and the assignment operators.
*/
TYPE_TEST1(sp, T_OBJECT)
if ((master_ob != NULL && current_variables == master_ob->variables)
|| (simul_efun_object != NULL && current_variables == simul_efun_object->variables)
|| privilege_violation("set_this_object", sp))
{
struct control_stack *p;
/* Find the 'extern_call' entry in the call stack which
* determined the current this_object().
*/
for (p = csp; !p->extern_call; p--) NOOP;
p->extern_call |= CS_PRETEND;
p->pretend_to_be = current_object = sp->u.ob;
}
pop_stack();
break;
}
CASE(F_SNOOP); /* --- snoop <nargs> --- */
{
/* EFUN snoop()
*
* object snoop(object snooper)
* object snoop(object snooper, object snoopee)
*
* Starts a snoop from 'snooper' on 'snoopee', or if 'snoopee' is not
* given, terminates any snoop from 'snooper'.
* On success, 'snoopee' is returned, else 0.
*
* The snoop is checked with the master object for validity.
* It will also fail if the 'snoopee' is being snooped already or
* if a snoop would result in a recursive snoop action.
*/
int i;
assign_eval_cost();
GET_NUM_ARG
inter_pc = pc;
inter_sp = sp;
if (num_arg == 1)
{
TYPE_TEST1(sp, T_OBJECT)
i = set_snoop(sp->u.ob, 0);
}
else
{
TYPE_TEST1(sp-1, T_OBJECT)
TYPE_TEST2(sp, T_OBJECT)
i = set_snoop((sp-1)->u.ob, sp->u.ob);
pop_stack();
}
free_svalue(sp);
put_number(sp, i);
break;
}
CASE(F_TELL_OBJECT); /* --- tell_object --- */
/* EFUN tell_object()
*
* void tell_object(object ob, string str)
*
* 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.
*/
ASSIGN_EVAL_COST
TYPE_TEST1(sp-1, T_OBJECT)
TYPE_TEST2(sp, T_STRING)
inter_sp = sp;
inter_pc = pc;
tell_object((sp-1)->u.ob, sp->u.string);
free_string_svalue(sp);
sp--;
if (sp->type == T_OBJECT) /* not self-destructed */
free_object_svalue(sp);
sp--;
break;
CASE(F_THIS_INTERACTIVE); /* --- this_interactive --- */
/* EFUN this_interactive()
*
* object this_interactive(void)
*
* this_interactive() returns the current interactive object, if
* any, i.e. the one who "hit the RETURN key".
*/
if (current_interactive
&& !(current_interactive->flags & O_DESTRUCTED))
push_object(current_interactive);
else
push_number(0);
break;
CASE(F_THIS_OBJECT); /* --- this_object --- */
/* EFUN this_object()
*
* object this_object(void)
*
* Return the object pointer for this object.
*/
if (current_object->flags & O_DESTRUCTED)
{
push_number(0);
break;
}
push_object(current_object);
break;
CASE(F_USERS); /* --- users --- */
/* EFUN users
*
* object *users(void)
*
* Return an array containing all interactive users.
*/
push_referenced_vector(users());
break;
CASE(F_WRITE); /* --- write --- */
/* EFUN write()
*
* void write(mixed msg)
*
* Write out something to the current user. What exactly will
* be printed in the end depends of the type of msg.
*
* If it is a string or a number then just prints it out.
*
* If it is an object then the object will be printed in the
* form: "OBJ("+file_name((object)mix)+")"
*
* If it is an array just "<ARRAY>" will be printed.
*
* If the write() function is invoked by a command of an living
* but not interactive object and the given argument is a string
* then the lfun catch_tell() of the living will be invoked with
* the message as argument.
*
* TODO: this efun could be made the counterpart of MudOS' receive().
* TODO: the accepted types and their interpretations should follow
* TODO:: the to_string() or sprintf() representations.
*/
assign_eval_cost();
inter_pc = pc;
inter_sp = sp;
e_write(sp);
pop_stack();
break;
/* --- Efuns: Files --- */
CASE(F_CAT); /* --- cat <nargs> --- */
{
/* EFUN cat()
*
* int cat(string pathi [, int start [, int num]])
*
* List the file found at path.
* The optional arguments start and num are start line
* number and number of lines. If they are not given the whole
* file is printed from the beginning.
*
* Result is the number of lines printed.
*/
int i;
svalue_t *arg;
int start, len;
assign_eval_cost();
GET_NUM_ARG
inter_pc = pc;
inter_sp = sp;
arg = sp- num_arg + 1;
/* Get and test the arguments */
TYPE_TEST1(arg, T_STRING)
start = 0; len = 0;
if (num_arg > 1)
{
TYPE_TEST2(arg+1, T_NUMBER)
start = arg[1].u.number;
if (num_arg == 3)
{
if (arg[2].type != T_NUMBER)
goto bad_arg_3;
len = arg[2].u.number;
}
}
/* Print the file */
i = e_print_file(arg[0].u.string, start, len);
pop_n_elems(num_arg);
push_number(i);
break;
}
CASE(F_FILE_SIZE); /* --- file_size --- */
{
/* EFUN file_size()
*
* int file_size(string file)
*
* Returns the size of the file in bytes.
*
* Size FSIZE_NOFILE (-1) indicates that the file either does not
* exist, or that it is not readable for the calling object/user.
* Size FSIZE_DIR (-2) indicates that it is a directory.
*/
int i;
assign_eval_cost();
TYPE_TEST1(sp, T_STRING)
inter_sp = sp;
inter_pc = pc;
i = e_file_size(sp->u.string);
free_svalue(sp);
put_number(sp, i);
break;
}
CASE(F_GET_DIR); /* --- get_dir --- */
{
/* EFUN get_dir()
*
* string *get_dir(string str, int mask)
*
* This function takes a path as argument and returns an array of
* file names and attributes in that directory.
*
* The filename part of the path may contain '*' or '?' as
* wildcards: every '*' matches an arbitrary amount of characters
* (or just itself). Thus get_dir ("/path/ *") would return an
* array of all files in directory "/path/", or just ({ "/path/ *"
* }) if this file happens to exist.
*
* The optional second argument mask can be used to get
* information about the specified files.
*
* GETDIR_EMPTY (0x00) return an empty array (not very useful)
* GETDIR_NAMES (0x01) put the file names into the returned array.
* GETDIR_SIZES (0x02) put the file sizes into the returned array.
* GETDIR_DATES (0x04) put the file modification dates into the
* returned array.
* GETDIR_UNSORTED (0x20) if this mask bit is set, the result of
* get_dir() will _not_ be sorted.
* The values of mask can be added together.
*/
vector_t *v;
TYPE_TEST1(sp-1, T_STRING)
TYPE_TEST2(sp, T_NUMBER)
inter_sp = sp;
inter_pc = pc;
v = e_get_dir(sp[-1].u.string, sp->u.number);
sp--; /* think 'free_int_svalue(sp--) */
free_string_svalue(sp);
if (v)
{
put_array(sp, v);
}
else
{
put_number(sp, 0);
}
break;
}
CASE(F_MKDIR); /* --- mkdir --- */
{
/* EFUN mkdir()
*
* int mkdir(string path)
*
* Make a directory named path. Return 1 for success and 0 for
* failure.
*/
int i;
char *path;
assign_eval_cost();
inter_pc = pc;
inter_sp = sp;
TYPE_TEST1(sp, T_STRING)
path = check_valid_path(sp->u.string, current_object, "mkdir", MY_TRUE);
i = !(path == 0 || mkdir(path, 0775) == -1);
free_svalue(sp);
put_number(sp, i);
break;
}
CASE(F_READ_BYTES); /* --- read_bytes <nargs> --- */
{
/* EFUN read_bytes()
*
* string read_bytes (string file, int start, int number)
*
* Reads a given amount of bytes from file.
* If <start> is not given or 0, the file is read from the
* beginning, else from the <start>th byte on. If <start> is
* negative, it is counted from the end of the file.
* <number> is the number of bytes to read. 0 or negative values
* are possible, but not useful.
* If <start> would be outside the actual size of the file, 0 is
* returned instead of a string.
* TODO: Can't read nul-characters.
*/
char *str;
svalue_t *arg;
int start, len;
assign_eval_cost();
GET_NUM_ARG
inter_pc = pc;
inter_sp = sp;
arg = sp- num_arg + 1;
TYPE_TEST1(arg, T_STRING)
/* Get the arguments */
start = 0;
len = 0;
if (num_arg > 1)
{
TYPE_TEST2(arg+1, T_NUMBER)
start = arg[1].u.number;
if (num_arg == 3)
{
if (arg[2].type != T_NUMBER)
goto bad_arg_2;
len = arg[2].u.number;
sp--;
}
sp--;
}
/* Read the file */
str = e_read_bytes(arg[0].u.string, start, len);
pop_stack();
if (str == 0)
push_number(0);
else
{
push_string_malloced(str);
xfree(str);
}
break;
}
CASE(F_READ_FILE); /* --- read_file --- */
{
/* EFUN read_file()
*
* string read_file(string file, int start, int number)
*
* Reads lines from file.
* If <start> is not given or 0, the file is read from the
* beginning, else from the numbered line on.
* If <number> is not given or 0, the whole file is read, else
* just the given amount of lines.
* If <start> would be outside the actual size of the file, 0 is
* returned instead of a string.
*/
char *str;
svalue_t *arg;
int start, len;
assign_eval_cost();
GET_NUM_ARG
inter_pc = pc;
inter_sp = sp;
arg = sp- num_arg + 1;
TYPE_TEST1(arg, T_STRING)
/* Get the arguments */
start = 0;
len = 0;
if (num_arg > 1)
{
TYPE_TEST2(arg+1, T_NUMBER)
start = arg[1].u.number;
if (num_arg == 3)
{
if (arg[2].type != T_NUMBER)
goto bad_arg_3;
len = arg[2].u.number;
sp--;
}
sp--;
}
/* Read the file */
str = e_read_file(arg[0].u.string, start, len);
pop_stack();
if (str == 0)
push_number(0);
else
{
push_malloced_string(str);
}
break;
}
CASE(F_RENAME); /* --- rename --- */
{
/* EFUN rename()
*
* int rename(string from, string to)
*
* The efun rename() will move from to the new name to. If from
* is a file, then to may be either a file or a directory. If
* from is a directory, then to has to be a directory. If to
* exists and is a directory, then from will be placed in that
* directory and keep its original name.
*
* You must have write permission for from to rename the file.
*
* On successfull completion rename() will return 0. If any error
* occurs 1 is returned.
*/
int i;
assign_eval_cost();
inter_pc = pc;
inter_sp = sp;
TYPE_TEST1(sp-1, T_STRING)
TYPE_TEST2(sp, T_STRING)
i = e_rename((sp-1)->u.string, sp->u.string);
pop_n_elems(2);
push_number(i);
break;
}
CASE(F_RM); /* --- rm --- */
{
/* EFUN rm()
*
* int rm(string file)
*
* Remove the file. Returns 0 for failure and 1 for success.
*/
int i;
assign_eval_cost();
TYPE_TEST1(sp, T_STRING)
inter_sp = sp;
inter_pc = pc;
i = e_remove_file(sp->u.string);
free_svalue(sp);
put_number(sp, i);
break;
}
CASE(F_RMDIR); /* --- rmdir --- */
{
/* EFUN rmdir()
*
* int rmdir(string dir)
*
* Remove directory dir. Return 1 on success, 0 on failure.
*/
int i;
char *path;
assign_eval_cost();
inter_pc = pc;
inter_sp = sp;
TYPE_TEST1(sp, T_STRING)
path = check_valid_path(sp->u.string, current_object, "rmdir", MY_TRUE);
i = !(path == 0 || rmdir(path) == -1);
free_svalue(sp);
put_number(sp, i);
break;
}
CASE(F_TAIL); /* --- tail --- */
/* EFUN tail()
*
* void tail(string file)
*
* Print out the tail of a file. There is no specific amount of
* lines given to the output. Only a maximum of 1000 bytes will
* be printed.
*/
assign_eval_cost();
TYPE_TEST1(sp, T_STRING)
inter_sp = sp;
inter_pc = pc;
if (e_tail(sp->u.string))
assign_svalue(sp, &const1);
else
assign_svalue(sp, &const0);
break;
CASE(F_WRITE_BYTES); /* --- write_bytes --- */
{
/* EFUN write_bytes()
*
* int write_bytes(string file, int start, string str)
*
* Write string str to file file by overwriting the old bytes at
* position start. If start is a negative value then it will be
* counted from the end of the file. The file will not be
* appended, instead the function will be aborted. Returns 1 for
* success 0 for failure during execution.
*/
int i;
assign_eval_cost();
TYPE_TEST1(sp-2, T_STRING)
TYPE_TEST2(sp-1, T_NUMBER)
inter_sp = sp;
inter_pc = pc;
if (sp->type != T_STRING)
goto bad_arg_3;
i = e_write_bytes((sp-2)->u.string, (sp-1)->u.number, sp->u.string);
pop_stack();
sp--;
free_svalue(sp);
put_number(sp, i);
break;
}
CASE(F_WRITE_FILE); /* --- write_file --- */
{
/* EFUN write_file()
*
* int write_file(string file, string str)
*
* Append the string str to the file file. Returns 1 for success
* and 0 if any failure occured.
*/
int i;
assign_eval_cost();
TYPE_TEST1(sp-1, T_STRING)
TYPE_TEST2(sp, T_STRING)
inter_sp = sp;
inter_pc = pc;
i = e_write_file((sp-1)->u.string, sp->u.string);
pop_stack();
free_svalue(sp);
put_number(sp, i);
break;
}
/* --- Efuns: Driver and System --- */
CASE(F_QUERY_LOAD_AVERAGE); /* --- query_load_average --- */
/* EFUN query_load_average()
*
* string query_load_average(void)
*
* Returns the load of the mud. Two figures are given, executed
* commands/second and compiled lines/second.
*/
push_string_malloced(query_load_av());
break;
/* --- Efuns: Inventories --- */
CASE(F_ALL_INVENTORY); /* --- all_inventory --- */
{
/* 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;
TYPE_TEST1(sp, T_OBJECT)
inter_sp = sp;
inter_pc = pc;
vec = all_inventory(sp->u.ob);
free_object_svalue(sp);
if (vec == NULL)
{
put_number(sp, 0);
}
else
{
put_array(sp, vec);
}
break;
}
CASE(F_DEEP_INVENTORY); /* --- deep_inventory --- */
{
/* EFUN deep_inventory()
*
* object *deep_inventory(void)
* object *deep_inventory(object ob)
*
* 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.
*/
vector_t *vec;
TYPE_TEST1(sp, T_OBJECT)
inter_sp = sp;
inter_pc = pc;
vec = deep_inventory(sp->u.ob, 0);
free_object_svalue(sp);
put_array(sp, vec);
break;
}
CASE(F_ENVIRONMENT); /* --- environment <nargs> --- */
{
/* 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;
GET_NUM_ARG
if (num_arg)
{
if (sp->type == T_OBJECT)
{
ob = sp->u.ob->super;
free_object_svalue(sp);
}
else if (sp->type == T_STRING)
{
ob = find_object(sp->u.string);
if (!ob || ob->super == NULL || (ob->flags & O_DESTRUCTED))
ob = NULL;
else
ob = ob->super;
free_string_svalue(sp);
}
else
goto bad_arg_1;
}
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);
break;
}
CASE(F_FIRST_INVENTORY); /* --- first_inventory --- */
{
/* 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;
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.string);
if (!ob)
ERRORF(("No object '%s' for first_inventory()\n", sp->u.string));
free_string_svalue(sp);
ob = ob->contains;
}
else
goto bad_arg_1;
if (ob)
put_ref_object(sp, ob, "first_inventory");
else
put_number(sp, 0);
break;
}
CASE(F_MOVE_OBJECT); /* --- move_object --- */
{
/* 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;
ASSIGN_EVAL_COST
inter_pc = pc;
inter_sp = sp;
if ((sp-1)->type == T_OBJECT)
item = (sp-1)->u.ob;
else if ((sp-1)->type == T_STRING)
{
item = get_object((sp-1)->u.string);
if (!item)
error("move_object failed\n");
free_string_svalue(sp-1);
put_ref_object(sp-1, item, "move_object");
}
else
goto bad_arg_1;
if (sp->type == T_OBJECT)
NOOP;
else if (sp->type == T_STRING)
{
dest = get_object(sp->u.string);
if (!dest)
error("move_object failed\n");
free_string_svalue(sp);
put_ref_object(sp, dest, "move_object");
}
else
goto bad_arg_2;
/* move_object() reads its arguments directly from the stack */
move_object();
sp -= 2;
break;
}
CASE(F_NEXT_INVENTORY); /* --- next_inventory --- */
{
/* 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;
TYPE_TEST1(sp, T_OBJECT)
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);
break;
}
CASE(F_PRESENT); /* --- present --- */
{
/* EFUN present()
*
* object present(mixed str)
* object present(mixed str, 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.
*
* "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;
assign_eval_cost();
GET_NUM_ARG
inter_pc = pc;
arg = sp - num_arg + 1;
/* Get the arguments */
if (arg->type != T_STRING && arg->type != T_OBJECT)
goto bad_arg_1;
ob = NULL;
if (num_arg > 1)
{
TYPE_TEST2(arg+1, T_OBJECT)
ob = arg[1].u.ob;
pop_stack();
}
inter_sp = sp;
ob = e_object_present(arg, ob);
free_svalue(arg);
if (ob)
put_ref_object(sp, ob, "present");
else
put_number(sp, 0);
break;
}
CASE(F_SAY); /* --- say <nargs> --- */
{
/* 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.
*/
ASSIGN_EVAL_COST
GET_NUM_ARG
inter_pc = pc;
inter_sp = sp;
#if defined(DEBUG) && defined(MALLOC_smalloc)
static_vector2 = &vtmp.v;
/* TODO: Remove this once VEC_SIZE() is proven to be accurate.
*/
#endif
if (num_arg == 1)
{
/* No objects to exclude */
if (sp->type != T_STRING && sp->type != T_POINTER)
goto bad_arg_1;
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[-1].type != T_STRING && sp[-1].type != T_POINTER)
goto bad_arg_1;
if ( sp->type == T_POINTER )
{
e_say(sp-1, sp->u.vec);
}
else if (sp->type == T_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);
}
else
goto bad_arg_2;
pop_stack();
}
/* 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;
}
pop_stack();
break;
}
CASE(F_TELL_ROOM); /* --- tell_room <nargs> --- */
{
/* 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;
ASSIGN_EVAL_COST
GET_NUM_ARG
inter_pc = pc;
inter_sp = sp;
arg = sp- num_arg + 1;
/* Test 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.string);
if (!ob)
ERROR("Object not found.\n")
}
else
goto bad_arg_1;
if (arg[1].type != T_STRING && arg[1].type != T_POINTER)
goto bad_arg_2;
if (num_arg == 2)
{
avoid = &null_vector;
}
else
{
/* Sort the list of objects to exclude for faster
* operation.
*/
vector_t *vtmpp;
static svalue_t stmp = { T_POINTER };
if (arg[2].type != T_POINTER)
goto bad_arg_3;
stmp.u.vec = arg[2].u.vec;
vtmpp = order_alist(&stmp, 1, MY_TRUE);
avoid = vtmpp->item[0].u.vec;
sp->u.vec = avoid; /* in case of an error, this will be freed. */
sp--;
vtmpp->item[0].u.vec = stmp.u.vec;
free_array(vtmpp);
}
e_tell_room(ob, sp, avoid);
if (num_arg > 2)
free_array(avoid);
pop_stack();
pop_stack();
break;
}
/* --- Efuns: Verbs and Commands --- */
CASE(F_ADD_ACTION); /* --- add_action <nargs> --- */
{
/* EFUN add_action()
*
* void add_action(string fun, string cmd [, int flag])
* void add_action(string fun) // historical
*
* Add an action (verb + function) to the commandgiver.
* TODO: In the long run, get rid of actions.
*/
svalue_t *arg;
svalue_t *verb;
GET_NUM_ARG
inter_pc = pc;
inter_sp = sp;
arg = sp - num_arg + 1;
TYPE_TEST1(arg, T_STRING)
verb = NULL;
if (num_arg >= 2)
{
TYPE_TEST2(arg+1, T_STRING)
if (num_arg > 2)
{
if (arg[2].type != T_NUMBER)
goto bad_arg_3;
}
verb = &arg[1];
}
if (e_add_action( &arg[0], verb
, num_arg > 2 ? arg[2].u.number : 0))
{
/* silent error condition, deallocate strings by hand */
pop_n_elems(num_arg);
}
else
{
/* add_action has reused the strings or freed it */
sp -= num_arg;
}
break;
}
CASE(F_COMMAND); /* --- command <nargs> --- */
{
/* EFUN command()
*
* int command(string str) // native
* int command(string str, object ob) // !native
*
* Execute str as a command given directly by the user. Any
* effects of the command will apply to the current object.
*
* Return value is 0 for failure. Otherwise a numeric value is
* returned which tells the evaluation cost. Bigger number means
* higher cost. The evaluation cost is approximately the number
* of LPC machine code instructions executed.
*
* In native mode, command() can effect only the calling object.
* If native mode is not enabled, command() can get an optional
* second arg, that specifies the object that the command is to
* be applied to.
* If command() is called on another object, it is not possible
* to call static functions in this way, to give some protection
* against illegal forces.
*
* TODO: With add_action(), this should go in the long run.
*/
int i;
svalue_t *arg;
assign_eval_cost();
GET_NUM_ARG
inter_pc = pc;
inter_sp = sp;
arg = sp - num_arg + 1;
if (num_arg == 1)
{
TYPE_TEST1(sp, T_STRING)
i = e_command(arg[0].u.string, NULL);
}
else
{
TYPE_TEST1(sp-1, T_STRING)
TYPE_TEST2(sp, T_OBJECT)
i = e_command(arg[0].u.string, arg[1].u.ob);
pop_stack();
}
free_svalue(sp);
put_number(sp, i);
break;
}
CASE(F_DISABLE_COMMANDS); /* --- disable_commands --- */
/* EFUN disable_commands()
*
* void disable_commands()
*
* Disable this object to use commands normally accessible to
* users.
*/
inter_sp = sp;
enable_commands(MY_FALSE);
break;
CASE(F_ENABLE_COMMANDS); /* --- enable_commands --- */
/* EFUN enable_commands()
*
* void enable_commands()
*
* Enable this object to use commands normally accessible to
* users.
*/
inter_sp = sp;
enable_commands(MY_TRUE);
break;
CASE(F_LIVING); /* --- living --- */
{
/* EFUN living()
*
* int living(object ob)
*
* Return true if ob is a living object (that is,
* enable_commands() has been called from inside the ob).
* ob may be 0, in which case the result is obviously 0, too.
*/
int i;
if (sp->type != T_OBJECT)
{
if (sp->type == T_NUMBER && !sp->u.number)
break;
goto bad_arg_1;
}
i = (sp->u.ob->flags & O_ENABLE_COMMANDS) != 0;
free_object_svalue(sp);
put_number(sp, i);
break;
}
CASE(F_QUERY_ACTIONS); /* --- query_actions --- */
{
/* EFUN query_actions()
*
* mixed *query_actions(object ob, mixed mask_or_verb)
*
* query_actions takes either an object or a filename as first
* argument and a bitmask (int) or string as a second argument.
* If the second argument is a string, query_actions() will return
* an array containing information (see below) on the verb or
* zero if the living object "ob" cannot use the verb. If the
* second argument is a bitmask, query_actions() will return a
* flat array containing information on all verbs added to ob.
* The second argument is optional (default is the bitmask 1).
* 1: the verb
* 2: type
* 4: short_verb
* 8: object
* 16: function
*
* "type" is one of the values defined in <sent.h> (/sys/sent.h)
* (which is provided with the parser source).
*
* SENT_PLAIN added with add_action (fun, cmd);
* SENT_SHORT_VERB added with add_action (fun, cmd, 1);
* SENT_NO_SPACE added with add_action (fun); add_xverb (cmd);
* SENT_NO_VERB just an add_action (fun); without a verb
* SENT_MARKER internal, won't be in the returned array
*/
vector_t *v;
svalue_t *arg;
object_t *ob;
arg = sp - 1;
inter_sp = sp;
inter_pc = pc;
/* Get the arguments */
if (arg[0].type == T_OBJECT)
ob = arg[0].u.ob;
else
{
TYPE_TEST1(arg, T_STRING);
ob = get_object(arg[0].u.string);
if (!ob)
error("query_actions() failed\n");
}
/* Get the actions */
if (arg[1].type == T_STRING)
v = e_get_action(ob, arg[1].u.string);
else if (arg[1].type == T_NUMBER)
v = e_get_all_actions(ob, arg[1].u.number);
else {
TYPE_TEST2(arg+1, T_OBJECT);
v = e_get_object_actions(ob, arg[1].u.ob);
}
/* Clean up the stack and push the result */
pop_stack();
free_svalue(arg);
if (v)
{
put_array(arg, v);
} else
put_number(sp, 0);
break;
}
CASE(F_QUERY_VERB); /* --- query_verb --- */
{
/* EFUN query_verb()
*
* string query_verb(void)
* string query_verb(int flag)
*
* Return the verb of the current command, of 0 if not executing from
* a command. If <flag> is 0 or not given, the verb as given by the user
* is returned; if <flag> is non-0, the verb as specified in the
* add_action() statement is returned.
*
* Give the name of the current command, or 0 if not executing
* from a command. This allows add_action() of several commands
* This efun allows add_action() of several commands
* to the same function. query_verb() returns 0 when invoked by a
* function which was started by a call_out or the heart beat.
* Also when a user logs in query_verb() returns 0.
*/
p_int flag = sp->u.number;
free_svalue(sp);
if (flag == 0)
{
if (!last_verb)
{
put_number(sp, 0);
break;
}
put_ref_string(sp, last_verb);
}
else
{
if (!last_action_verb)
{
put_number(sp, 0);
break;
}
put_ref_string(sp, last_action_verb);
}
break;
}
CASE(F_QUERY_COMMAND); /* --- query_command --- */
{
/* EFUN query_command()
*
* string query_command(void)
*
* Return the full command string, or 0 if not executing from
* a command.
*
* The string returned is the string as seen by the parser:
* after any modify_command handling and after stripping
* trailing spaces.
*/
char * str;
if (!last_command)
{
push_number(0);
break;
}
inter_pc = pc;
inter_sp = sp;
str = string_copy(last_command);
if (!str)
ERROR("Out of memory.\n")
push_malloced_string(str);
break;
}
CASE(F_THIS_PLAYER); /* --- this_player --- */
/* EFUN this_player()
*
* object this_player(void)
*
* Return the current command giver. This can be an interactive
* user or a living object like a npc.
*
* If called from inside the heart_beat() of a not living object
* 0 will be returned.
*/
if (command_giver && !(command_giver->flags & O_DESTRUCTED))
push_object(command_giver);
else
push_number(0);
break;
/* --- Efuns: Uids and Euids --- */
#ifdef F_EXPORT_UID
CASE(F_EXPORT_UID); /* --- export_uid --- */
{
/* 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 driver, why not this one, too?
* TODO:: Actually, this efun is redundant, archaic and should
* TODO:: vanish altogether.
*/
object_t *ob;
TYPE_TEST1(sp, T_OBJECT)
if (!current_object->eff_user)
ERROR("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--;
break;
}
#endif /* F_EXPORT_UID */
#ifdef F_GETEUID
CASE(F_GETEUID); /* --- geteuid --- */
{
/* 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;
TYPE_TEST1(sp, T_OBJECT)
ob = sp->u.ob;
if (ob->eff_user)
{
char *tmp;
tmp = ob->eff_user->name;
pop_stack();
push_volatile_string(tmp);
}
else
{
free_svalue(sp);
put_number(sp, 0);
}
break;
}
#endif /* F_GETEUID */
#ifdef F_SETEUID
CASE(F_SETEUID); /* --- seteuid --- */
{
/* 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)
goto bad_arg_1;
current_object->eff_user = 0;
free_svalue(argp);
put_number(argp, 1);
break;
}
if (argp->type != T_STRING)
goto bad_arg_1;
/* Call the master to clear this use of seteuid() */
assign_eval_cost();
inter_sp = _push_volatile_string(argp->u.string,
_push_valid_ob(current_object, sp) );
inter_pc = pc;
ret = apply_master(STR_VALID_SETEUID, 2);
if (!ret || ret->type != T_NUMBER || ret->u.number != 1)
{
free_svalue(argp);
put_number(argp, 0);
break;
}
current_object->eff_user = add_name(argp->u.string);
free_svalue(argp);
put_number(argp, 1);
break;
}
#endif /* F_SETEUID */
#if defined(F_GETUID) || defined(F_CREATOR)
#ifdef F_GETUID
CASE(F_GETUID); /* --- getuid --- */
#else
CASE(F_CREATOR); /* --- creator --- */
#endif
{
/* 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;
char *name;
TYPE_TEST1(sp, T_OBJECT)
ob = sp->u.ob;
deref_object(ob, "getuid");
if ( NULL != (name = ob->user->name) )
put_ref_string(sp, name);
else
put_number(sp, 0);
break;
}
#endif
/* --- Optional Efuns: Technical --- */
#ifdef F_BREAK_POINT
CASE(F_BREAK_POINT); /* --- break_point --- */
/* EFUN break_point()
*
* void break_point()
*
* This function is for system internal use and should never be called
* by user objects. It is supposed to check the stack integrity and
* aborts the driver when it detects corruption.
*
*/
if (sp - fp - csp->num_local_variables + 1 != 0)
fatal("Bad stack pointer.\n");
break;
#endif
#ifdef F_RUSAGE
CASE(F_RUSAGE); /* --- rusage --- */
{
/* EFUN rusage()
*
* int *rusage(void)
*
* Return an array with current system resource usage statistics,
* as returned by the getrusage(2) of Unix.
* namely: utime, stime, maxrss, rus.ru_ixrss, rus.ru_idrss,
* rus.ru_isrss, rus.ru_minflt, rus.ru_majflt, rus.ru_nswap,
* rus.ru_inblock, rus.ru_oublock, rus.ru_msgsnd,
* rus.ru_msgrcv, rus.ru_nsignals, rus.ru_nvcsw,
* rus.ru_nivcsw
* TODO: The indices should be in an include file.
*/
struct rusage rus;
vector_t *res;
svalue_t *v;
#ifndef GETRUSAGE_RESTRICTED
int maxrss;
#endif
if (getrusage(RUSAGE_SELF, &rus) < 0) {
push_number(0);
break;
}
res = allocate_array(16);
v = res->item;
v[ 0].u.number = RUSAGE_TIME(rus.ru_utime);
v[ 1].u.number = RUSAGE_TIME(rus.ru_stime);
#ifndef GETRUSAGE_RESTRICTED
maxrss = rus.ru_maxrss;
#ifdef sun
maxrss *= getpagesize() / 1024;
#endif
v[ 2].u.number = maxrss;
v[ 3].u.number = rus.ru_ixrss;
v[ 4].u.number = rus.ru_idrss;
v[ 5].u.number = rus.ru_isrss;
v[ 6].u.number = rus.ru_minflt;
v[ 7].u.number = rus.ru_majflt;
v[ 8].u.number = rus.ru_nswap;
v[ 9].u.number = rus.ru_inblock;
v[10].u.number = rus.ru_oublock;
v[11].u.number = rus.ru_msgsnd;
v[12].u.number = rus.ru_msgrcv;
v[13].u.number = rus.ru_nsignals;
v[14].u.number = rus.ru_nvcsw;
v[15].u.number = rus.ru_nivcsw;
#endif /* GETRUSAGE_RESTRICTED */
push_referenced_vector(res);
break;
}
#endif
/* --- Optional Efuns: Alists --- */
#ifdef F_ASSOC
CASE(F_ASSOC); /* --- assoc <nargs> --- */
{
/* EFUN assoc()
*
* int assoc (mixed key, mixed *keys)
* mixed assoc (mixed key, mixed *alist [, mixed fail] )
* mixed assoc (mixed key, mixed *keys, mixed *data [, mixed fail])
*
* Search for <key> in the <alist> resp. in the <keys>.
*
* When the key list of an alist contains destructed objects
* it is better not to free them till the next reordering by
* order_alist to retain the alist property.
*
* TODO: Make the alist-efuns xefuns.
*/
svalue_t *args;
vector_t *keys,*data;
svalue_t *fail_val;
int ix;
GET_NUM_ARG
args = sp -num_arg +1;
TYPE_TEST2(args+1, T_POINTER)
/* Analyse the arguments */
if ( !VEC_SIZE(args[1].u.vec)
|| args[1].u.vec->item[0].type != T_POINTER )
{
keys = args[1].u.vec;
if (num_arg == 2)
{
data = NULL;
}
else
{
if (args[2].type != T_POINTER
|| VEC_SIZE(args[2].u.vec) != VEC_SIZE(keys))
{
goto bad_arg_3;
}
data = args[2].u.vec;
}
if (num_arg == 4)
{
fail_val = &args[3];
}
else
{
fail_val = &const0;
}
}
else
{
keys = args[1].u.vec->item[0].u.vec;
if (VEC_SIZE(args[1].u.vec) > 1)
{
if (args[1].u.vec->item[1].type != T_POINTER
|| VEC_SIZE(args[1].u.vec->item[1].u.vec) != VEC_SIZE(keys))
{
goto bad_arg_2;
}
data = args[1].u.vec->item[1].u.vec;
}
else
{
data = NULL;
}
if (num_arg == 3) fail_val = &args[2];
else if (num_arg == 2) fail_val = &const0;
else
{
ERROR ("too many args to efun assoc\n")
return MY_FALSE;
}
}
/* Call assoc() and push the result */
ix = assoc(&args[0],keys);
if (data == NULL)
{
pop_n_elems(num_arg);
push_number(ix);
}
else
{
assign_svalue(args
, ix == -1
? fail_val
: (destructed_object_ref(&data->item[ix])
? &const0
: &data->item[ix])
);
pop_n_elems(num_arg-1);
}
break;
}
#endif /* F_ASSOC */
#ifdef F_INSERT_ALIST
CASE(F_INSERT_ALIST) /* --- insert_alist <nargs> --- */
{
/* EFUN insert_alist()
*
* mixed* insert_alist (mixed key, mixed data..., mixed * alist)
* int insert_alist (mixed key, mixed * keys)
*
* 1. Form: Alist Insertion
*
* The <key> and all following <data> values are inserted
* into the <alist>. If an entry for <key> already exists
* in the list, just the data values are replaced. The number
* of <data> values must match the number of data arrays
* in the alist, naturally.
*
* Result is the updated <alist>.
*
* 2. Form: Key Insertion
*
* Insert the <key> into the (ordered) array of <keys>, so that
* subsequent assoc()s can perform quick lookups. Result is the
* index at which <key> was inserted (or already found).
*
* CAVEAT: when working with string keys, the index might no longer
* be valid after the next call to insert_alist().
*/
/* When the key list of an alist contains destructed objects
it is better not to free them till the next reordering by
order_alist to retain the alist property.
*/
int i;
vector_t *list;
long listsize;
size_t keynum;
svalue_t *key,*key_data,*ret;
static LOCAL_VEC1(insert_alist_vec, T_NUMBER);
/* Mock-alist for the insert_alist() key-insertion form.
*/
GET_NUM_ARG
#if defined(DEBUG) && defined(MALLOC_smalloc)
static_vector1 = &insert_alist_vec.v;
/* TODO: Remove this once VEC_SIZE() is proven to be accurate.
*/
#endif
if (sp->type != T_POINTER)
bad_arg_pc(num_arg,F_INSERT_ALIST, sp, pc);
/* Make up an alist if only a key-insertion is required */
if ( !(listsize = (long)VEC_SIZE(sp->u.vec))
|| sp->u.vec->item[0].type != T_POINTER )
{
list = &insert_alist_vec.v;
*list->item = *sp;
listsize = 1;
}
else
list = sp->u.vec;
/* Check the validity of the alist */
keynum = VEC_SIZE(list->item[0].u.vec);
for (i = 1; i < listsize; i++)
{
if (list->item[i].type != T_POINTER
|| VEC_SIZE(list->item[i].u.vec) != keynum)
{
bad_arg_pc(num_arg,F_INSERT_ALIST, sp, pc);
}
}
/* Get and test the data to insert */
if (num_arg == 2)
{
if (sp[-1].type != T_POINTER)
{
key_data = NULL;
key = sp-1;
}
else
{
if (VEC_SIZE(sp[-1].u.vec) != (size_t)listsize)
goto bad_arg_1;
key_data = key = sp[-1].u.vec->item;
}
}
else
{
if (num_arg - 1 != listsize)
goto bad_arg_1;
key_data = key = sp-num_arg+1;
}
/* Do the insertion */
inter_sp = sp; /* array might get too big */
ret = insert_alist(key,key_data,list);
pop_n_elems(num_arg);
sp++;
*sp = *ret;
break;
}
#endif /* F_INSERT_ALIST */
#ifdef F_INTERSECT_ALIST
CASE(F_INTERSECT_ALIST); /* --- intersect_alist --- */
{
/* EFUN intersect_alist()
*
* mixed * intersect_alist (mixed * list1, mixed * list2)
*
* Does a fast set intersection on alist key vectors (NOT on full
* alists!). The operator '&' does set intersection on arrays in
* general.
*/
vector_t *tmp;
TYPE_TEST1(sp-1, T_POINTER)
TYPE_TEST2(sp, T_POINTER)
tmp = intersect_alist( (sp-1)->u.vec, sp->u.vec );
pop_stack();
free_array(sp->u.vec);
sp->u.vec = tmp;
break;
}
#endif /* F_INTERSECT_ALIST */
#ifdef F_ORDER_ALIST
CASE(F_ORDER_ALIST); /* --- order_alist <nargs> --- */
{
/* EFUN order_alist()
*
* mixed *order_alist(mixed *keys, mixed *|void data, ...)
*
* Creates an alist.
*
* Either takes an array containing keys, and others containing
* the associated data, where all arrays are to be of the same
* length, or takes a single array that contains as first member
* the array of keys and has an arbitrary number of other members
* containing data, each of wich has to be of the same length as
* the key array. Returns an array holding the sorted key array
* and the data arrays; the same permutation that is applied to
* the key array is applied to all data arrays.
*/
int i;
svalue_t *args;
vector_t *list;
long listsize;
Bool reuse;
size_t keynum;
GET_NUM_ARG
args = sp-num_arg+1;
/* Get the key array to order */
TYPE_TEST1(args, T_POINTER)
if (num_arg == 1
&& ((list = args->u.vec), (listsize = (long)VEC_SIZE(list)))
&& list->item[0].type == T_POINTER)
{
args = list->item;
reuse = (list->ref == 1);
}
else
{
listsize = num_arg;
reuse = MY_TRUE;
}
keynum = VEC_SIZE(args[0].u.vec);
/* Get the data arrays to order */
for (i = 0; i < listsize; i++)
{
if (args[i].type != T_POINTER
|| VEC_SIZE(args[i].u.vec) != keynum)
{
ERRORF(("bad data array %d in call to order_alist\n",i))
}
}
/* Create the alist */
list = order_alist(args, listsize, reuse);
pop_n_elems(num_arg);
sp++;
put_array(sp, list);
break;
}
#endif /* F_ORDER_ALIST */
/* --- Optional Efuns: Miscellaneous --- */
#ifdef USE_SET_LIGHT
CASE(F_SET_LIGHT); /* --- set_light --- */
{
/* 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;
TYPE_TEST1(sp, T_NUMBER)
add_light(current_object, sp->u.number);
o1 = current_object;
while (o1->super)
o1 = o1->super;
sp->u.number = o1->total_light;
break;
}
#endif /* USE_SET_LIGHT */
/* --- XEfun and XCodes --- */
CASE(F_ESCAPE); /* --- escape <instr> ... --- */
{
/* A prefixed instruction. */
#define XCASE(x) CASE((x)-0x100)
#undef GET_NUM_ARG
#define GET_NUM_ARG num_arg = EXTRACT_UCHAR(pc); pc++;
#undef TYPE_TEST1
#define TYPE_TEST1(arg, t) if ( (arg)->type != t ) goto xbad_arg_1;
int code; /* the actual instruction code */
code = LOAD_CODE(pc);
#ifdef TRACE_CODE
previous_instruction[last] = code + 0x100;
#endif
#ifdef OPCPROF
opcount[code+0x100]++;
#endif
switch(code)
{
default:
fatal("Unknown stackmachine escape code: %d (%d)\n"
, code, code+0x100);
xbad_arg_1: instruction = code + 0x100; goto bad_arg_1;
xbad_arg_2: instruction = code + 0x100; goto bad_arg_2;
xbad_arg_3: instruction = code + 0x100; goto bad_arg_3;
/* --- Machine Instructions --- */
XCASE(F_END_CATCH); /* --- esc end_catch --- */
/* For a catch(...guarded code...) statement, the compiler
* generates a F_END_CATCH as last instruction of the
* guarded code.
*
* Executed when no error occured, it returns into
* catch_instruction() to clean up the
* error recovery information pushed by the F_CATCH
* and leave a 0 on the stack.
*
* dump_trace() checks for this bytecode, but accepts a normal
* instruction as well as an escaped instruction.
*/
return MY_TRUE;
break;
/* --- esc breakn_continue <num> <offset> ---*/
XCASE(F_BREAKN_CONTINUE);
/* Implement the 'continue;' statement from within
* a nested surrounding structure.
*
* Pop <num>+1 (uint8) break-levels from the break stack
* and jump by (16-Bit) short <offset> bytes, counted from the
* first by of <offset>
*/
break_sp +=
LOAD_UINT8(pc) * (sizeof(svalue_t)/sizeof(*break_sp));
/* FALLTHROUGH */
/* --- esc break_continue <offset> ---*/
XCASE(F_BREAK_CONTINUE);
{
/* Implement the 'continue;' statement for the immediate
* surrounding structure.
*
* Pop one break-level from the break stack and jump
* by (16-Bit) unsigned short <offset> bytes, counted from the
* first by of <offset>
*
* Pitfall: the offset is added to the current pc in 16-Bit
* unsigned arithmetic, allowing to jump backwards using big
* enough values.
*
* TODO: Make that a proper signed short.
*/
/* TODO: uint16 */ unsigned short offset;
break_sp += sizeof(svalue_t)/sizeof(*break_sp);
GET_SHORT(offset, pc);
offset += pc - current_prog->program;
pc = current_prog->program + offset;
break;
}
/* --- esc push_protected_indexed_lvalue --- */
XCASE(F_PUSH_PROTECTED_INDEXED_LVALUE);
/* Op. (vector v=sp[-1], int i=sp[0])
* Op. (mapping v=sp[-1], mixed i=sp[0])
*
* Compute the lvalue &(v[i]), store it in a struct
* protected_lvalue, and push the protector as PROTECTED_LVALUE
* into the stack.
*/
sp = push_protected_indexed_lvalue(sp, pc);
break;
/* --- esc push_protected_rindexed_lvalue --- */
XCASE(F_PUSH_PROTECTED_RINDEXED_LVALUE);
/* Op. (vector v=sp[-1], int i=sp[0])
*
* Compute the lvalue &(v[<i]), store it in a struct
* protected_lvalue, and push the protector as PROTECTED_LVALUE
* into the stack.
*/
sp = push_protected_rindexed_lvalue(sp, pc);
break;
/* --- esc push_protected_indexed_map_lvalue --- */
XCASE(F_PUSH_PROTECTED_INDEXED_MAP_LVALUE);
/* Op. (mapping m=sp[-2], mixed i=sp[-1], int j=sp[0])
*
* Compute the lvalue &(m[i:j]), store it in a struct
* protected_lvalue, and push the protector as PROTECTED_LVALUE
* into the stack.
*/
push_protected_indexed_map_lvalue(sp, pc);
break;
/* --- esc protected_index_lvalue --- */
XCASE(F_PROTECTED_INDEX_LVALUE);
/* Operator (string|vector &v=sp[0], int i=sp[-1])
* (mapping &v=sp[0], mixed i=sp[-1])
*
* Compute the index &(*v[i]) of lvalue <v>, wrap it into a
* protector, and push the reference to the protector as
* PROTECTED_LVALUE onto the stack.
*
* If <v> is a protected non-string-lvalue, the protected_lvalue
* referenced by <v>.u.lvalue will be deallocated, and the
* protector itself will be stored in <last_indexing_protector>
* for the time being.
*
* If <v> is a string-lvalue, it is made a malloced string if
* necessary.
*/
sp = protected_index_lvalue(sp, pc);
break;
/* --- esc protected_rindex_lvalue --- */
XCASE(F_PROTECTED_RINDEX_LVALUE);
/* Operator (string|vector &v=sp[0], int i=sp[-1])
*
* Compute the index &(*v[<i]) of lvalue <v>, wrap it into a
* protector, and push the reference to the protector as
* PROTECTED_LVALUE onto the stack.
*
* If <v> is a protected non-string-lvalue, the protected_lvalue
* referenced by <v>.u.lvalue will be deallocated, and the
* protector itself will be stored in <last_indexing_protector>
* for the time being.
*
* If <v> is a string-lvalue, it is made a malloced string if
* necessary.
*/
sp = protected_rindex_lvalue(sp, pc);
break;
/* --- esc protected_range_lvalue --- */
XCASE(F_PROTECTED_RANGE_LVALUE);
/* X-Op (string|vector &v=sp[0], int i2=sp[-1], i1=sp[-2])
*
* Compute the range &(v[i1..i2]) of lvalue <v>, wrap it into a
* protector, and push the reference to the protector onto the
* stack.
*
* If <v> is a protected lvalue itself, its protecting svalue will
* be used in the result protector.
*
* If <v> is a string-lvalue, it is made a malloced string if
* necessary.
*/
inter_pc = pc;
sp = protected_range_lvalue(0x000, sp);
break;
/* --- esc protected_nr_range_lvalue --- */
XCASE(F_PROTECTED_NR_RANGE_LVALUE);
/* X-Op (string|vector &v=sp[0], int i2=sp[-1], i1=sp[-2])
*
* Compute the range &(v[i1..<i2]) of lvalue <v>, wrap it into a
* protector, and push the reference to the protector onto the
* stack.
*
* If <v> is a protected lvalue itself, its protecting svalue will
* be used in the result protector.
*
* If <v> is a string-lvalue, it is made a malloced string if
* necessary.
*/
inter_pc = pc;
sp = protected_range_lvalue(0x001, sp);
break;
/* --- esc protected_rn_range_lvalue --- */
XCASE(F_PROTECTED_RN_RANGE_LVALUE);
/* X-Op (string|vector &v=sp[0], int i2=sp[-1], i1=sp[-2])
*
* Compute the range &(v[<i1..i2]) of lvalue <v>, wrap it into a
* protector, and push the reference to the protector onto the
* stack.
*
* If <v> is a protected lvalue itself, its protecting svalue will
* be used in the result protector.
*
* If <v> is a string-lvalue, it is made a malloced string if
* necessary.
*/
inter_pc = pc;
sp = protected_range_lvalue(0x100, sp);
break;
/* --- esc protected_rr_range_lvalue --- */
XCASE(F_PROTECTED_RR_RANGE_LVALUE);
/* X-Op (string|vector &v=sp[0], int i2=sp[-1], i1=sp[-2])
*
* Compute the range &(v[<i1..<i2]) of lvalue <v>, wrap it into a
* protector, and push the reference to the protector onto the
* stack.
*
* If <v> is a protected lvalue itself, its protecting svalue will
* be used in the result protector.
*
* If <v> is a string-lvalue, it is made a malloced string if
* necessary.
*/
inter_pc = pc;
sp = protected_range_lvalue(0x101, sp);
break;
/* --- esc protected_nx_range_lvalue --- */
XCASE(F_PROTECTED_NX_RANGE_LVALUE);
/* X-Op (string|vector &v=sp[0], i1=sp[-1])
*
* Compute the range &(v[i1..]) of lvalue <v>, wrap it into a
* protector, and push the reference to the protector onto the
* stack.
*
* If <v> is a protected lvalue itself, its protecting svalue will
* be used in the result protector.
*
* If <v> is a string-lvalue, it is made a malloced string if
* necessary.
*
* We implement it by pushing '1' onto the stack and then
* calling protected_nr_range_lvalue, effectively computing
* &(v[i1..<1]).
*/
inter_pc = pc;
sp++;
sp[0] = sp[-1]; /* Pull up the 'v' */
put_number(sp-1, 1); /* 'Push' the 1 for the upper bound */
sp = protected_range_lvalue(0x001, sp);
break;
/* --- esc protected_rx_range_lvalue --- */
XCASE(F_PROTECTED_RX_RANGE_LVALUE);
/* X-Op (string|vector &v=sp[0], int i1=sp[-1])
*
* Compute the range &(v[<i1..]) of lvalue <v>, wrap it into a
* protector, and push the reference to the protector onto the
* stack.
*
* If <v> is a protected lvalue itself, its protecting svalue will
* be used in the result protector.
*
* If <v> is a string-lvalue, it is made a malloced string if
* necessary.
*
* We implement it by pushing '1' onto the stack and then
* calling protected_nr_range_lvalue, effectively computing
* &(v[i1..<1]).
*/
inter_pc = pc;
sp++;
sp[0] = sp[-1]; /* Pull up the 'v' */
put_number(sp-1, 1); /* 'Push' the 1 for the upper bound */
sp = protected_range_lvalue(0x101, sp);
break;
XCASE(F_UNDEF); /* --- esc undef --- */
{
/* Catch-all instructions for declared but not implemented
* (defined) functions. Usually used by the compiler to
* handle prototypes (in that case it is the first and only
* instruction of the generated stub), it is also inserted
* into closures when the object the closure is bound to
* is destructed.
* Note: this instruction MUST be the first in the function.
*/
char *name;
/* pc has already been incremented */
if (pc > current_prog->program && pc <= PROGRAM_END(*current_prog))
{
/* Copy the function name pointer into name.
*/
memcpy(&name, FUNCTION_NAMEP(FUNCTION_FROM_CODE(pc-2)), sizeof name);
}
else
{
/* It is a vanished closure */
name = "Object the closure was bound to has been destructed";
/* TODO: WHich object? This can also happen as result of
* TODO:: a replace_program */
}
ERRORF(("Undefined function: %s\n", name))
}
/* --- XEfuns: Miscellaneous --- */
XCASE(F_ABS); /* --- esc abs --- */
{
/* XEFUN abs()
*
* int abs (int arg)
* float abs (float arg)
*
* Returns the absolute value of the argument <arg>.
*/
switch(sp->type)
{
default:
goto xbad_arg_1;
case T_NUMBER:
if (sp->u.number == PINT_MIN)
{
ERRORF(("Numeric overflow: abs(%ld)\n"
, (long)sp->u.number));
/* NOTREACHED */
break;
}
if (sp->u.number < 0)
sp->u.number = - sp->u.number;
break;
case T_FLOAT:
{
STORE_DOUBLE_USED
double x;
x = READ_DOUBLE(sp);
if (x < 0.0)
STORE_DOUBLE(sp, -(x));
break;
}
}
break;
}
XCASE(F_SIN); /* --- esc sin --- */
{
/* XEFUN sin()
*
* float sin(int|float)
*
* Returns the sinus of the argument.
*/
STORE_DOUBLE_USED
double d;
if (sp->type != T_FLOAT)
{
if (sp->type != T_NUMBER) goto xbad_arg_1;
d = sin((double)(sp->u.number));
}
else
d = sin(READ_DOUBLE(sp));
sp->type = T_FLOAT;
STORE_DOUBLE(sp, d);
break;
}
XCASE(F_ASIN); /* --- esc asin --- */
{
/* XEFUN asin()
*
* float asin(float)
*
* Returns the inverse sinus of the argument.
*/
STORE_DOUBLE_USED
double d;
if (sp->type != T_FLOAT || (d = READ_DOUBLE(sp)) < -1. || d > 1. )
goto xbad_arg_1;
d = asin(d);
STORE_DOUBLE(sp, d);
break;
}
XCASE(F_COS); /* --- esc cos --- */
{
/* XEFUN cos()
*
* float cos(int|float)
*
* Returns the cosinus of the argument.
*/
STORE_DOUBLE_USED
double d;
if (sp->type != T_FLOAT)
{
if (sp->type != T_NUMBER) goto xbad_arg_1;
d = cos((double)(sp->u.number));
}
else
d = cos(READ_DOUBLE(sp));
sp->type = T_FLOAT;
STORE_DOUBLE(sp, d);
break;
}
XCASE(F_ACOS); /* --- esc acos --- */
{
/* XEFUN acos()
*
* float acos(float)
*
* Returns the inverse cosinus of the argument.
*/
STORE_DOUBLE_USED
double d;
if (sp->type != T_FLOAT || (d = READ_DOUBLE(sp)) < -1. || d > 1. )
goto xbad_arg_1;
d = acos(d);
STORE_DOUBLE(sp, d);
break;
}
XCASE(F_TAN); /* --- esc tan --- */
{
/* XEFUN tan()
*
* float tan(int|float)
*
* Returns the tangens of the argument.
*/
STORE_DOUBLE_USED
double d;
if (sp->type != T_FLOAT)
{
if (sp->type != T_NUMBER) goto xbad_arg_1;
d = tan((double)(sp->u.number));
}
else
d = tan(READ_DOUBLE(sp));
sp->type = T_FLOAT;
STORE_DOUBLE(sp, d);
break;
}
XCASE(F_ATAN); /* --- esc atan --- */
{
/* XEFUN atan()
*
* float atan(int|float)
*
* Returns the inverse tangens of the argument.
*/
STORE_DOUBLE_USED
double d;
if (sp->type != T_FLOAT)
{
if (sp->type != T_NUMBER) goto xbad_arg_1;
d = atan((double)(sp->u.number));
}
else
d = atan(READ_DOUBLE(sp));
if (d < (-DBL_MAX) || d > DBL_MAX)
ERRORF(("Numeric overflow: atan(%g)\n", READ_DOUBLE(sp)));
sp->type = T_FLOAT;
STORE_DOUBLE(sp, d);
break;
}
XCASE(F_ATAN2); /* --- esc atan2 --- */
{
/* XEFUN atan2()
*
* float atan2(int|float y, int|float x)
*
* Returns the inverse tangens of the argument.
*/
STORE_DOUBLE_USED
double x, y, d;
if (sp->type != T_FLOAT)
{
if (sp->type != T_NUMBER) goto xbad_arg_2;
x = (double)(sp->u.number);
}
else
x = READ_DOUBLE(sp);
if (sp[-1].type != T_FLOAT)
{
if (sp[-1].type != T_NUMBER) goto xbad_arg_1;
y = (double)sp[-1].u.number;
}
else
y = READ_DOUBLE(sp-1);
d = atan2(y, x);
if (d < (-DBL_MAX) || d > DBL_MAX)
ERRORF(("Numeric overflow: atan2(%g, %g)\n", y, x));
pop_stack();
sp->type = T_FLOAT;
STORE_DOUBLE(sp, d);
break;
}
XCASE(F_LOG); /* --- esc log --- */
{
/* XEFUN log()
*
* float log(int|float)
*
* Returns the natural logarithmus of the argument.
*/
STORE_DOUBLE_USED
double d, e;
if (sp->type != T_FLOAT)
{
if (sp->type != T_NUMBER) goto xbad_arg_1;
d = (double)sp->u.number;
}
else
d = READ_DOUBLE(sp);
if (d <= 0.)
goto xbad_arg_1;
e = log(d);
if (e < (-DBL_MAX) || e > DBL_MAX)
ERRORF(("Numeric overflow: log(%g)\n", d));
sp->type = T_FLOAT;
STORE_DOUBLE(sp, e);
break;
}
XCASE(F_EXP); /* --- esc exp --- */
{
/* XEFUN exp()
*
* float exp(int|float)
*
* Returns the e to the power of the argument.
*/
STORE_DOUBLE_USED
double d;
if (sp->type != T_FLOAT)
{
if (sp->type != T_NUMBER) goto xbad_arg_1;
d = exp((double)sp->u.number);
if (d < (-DBL_MAX) || d > DBL_MAX)
ERRORF(("Numeric overflow: exp(%ld)\n", sp->u.number));
}
else
{
d = exp(READ_DOUBLE(sp));
if (d < (-DBL_MAX) || d > DBL_MAX)
ERRORF(("Numeric overflow: exp(%g)\n", READ_DOUBLE(sp)));
}
sp->type = T_FLOAT;
STORE_DOUBLE(sp, d);
break;
}
XCASE(F_SQRT); /* --- esc sqrt --- */
{
/* XEFUN sqrt()
*
* float sqrt(int|float)
*
* Returns the square root of the argument.
*/
STORE_DOUBLE_USED
double d, e;
if (sp->type != T_FLOAT)
{
if (sp->type != T_NUMBER) goto xbad_arg_1;
d = (double)sp->u.number;
}
else
d = READ_DOUBLE(sp);
if (d < 0.)
goto xbad_arg_1;
e = sqrt(d);
if (e < (-DBL_MAX) || e > DBL_MAX)
ERRORF(("Numeric overflow: sqrt(%g)\n", d));
sp->type = T_FLOAT;
STORE_DOUBLE(sp, e);
break;
}
XCASE(F_CEIL); /* --- esc ceil --- */
{
/* XEFUN ceil()
*
* float ceil(int|float)
*
* Returns the smallest whole number which is still bigger
* than the argument. For integer arguments, the result will
* be the argument value itself, albeit converted to a float.
*/
STORE_DOUBLE_USED
double d;
if (sp->type == T_FLOAT)
{
d = ceil(READ_DOUBLE(sp));
if (d < (-DBL_MAX) || d > DBL_MAX)
ERRORF(("Numeric overflow: ceil(%g)\n", READ_DOUBLE(sp)));
}
else if (sp->type == T_NUMBER)
{
d = sp->u.number;
sp->type = T_FLOAT;
}
else
goto xbad_arg_1;
STORE_DOUBLE(sp, d);
break;
}
XCASE(F_FLOOR); /* --- esc floor --- */
{
/* XEFUN floor()
*
* float floor(int|float)
*
* Returns the biggest whole number which is not larger
* than the argument. For integer arguments, the result will
* be the argument value itself, albeit converted to a float.
*/
STORE_DOUBLE_USED
double d;
if (sp->type == T_FLOAT)
{
d = floor(READ_DOUBLE(sp));
if (d < (-DBL_MAX) || d > DBL_MAX)
ERRORF(("Numeric overflow: floor(%g)\n", READ_DOUBLE(sp)));
}
else if (sp->type == T_NUMBER)
{
d = sp->u.number;
sp->type = T_FLOAT;
}
else
goto xbad_arg_1;
STORE_DOUBLE(sp, d);
break;
}
XCASE(F_POW); /* --- esc pow --- */
{
/* XEFUN pow()
*
* float pow(int|float x, int|float y)
*
* Returns x to the power of y.
*/
STORE_DOUBLE_USED
double x, y, d;
if (sp->type != T_FLOAT)
{
if (sp->type != T_NUMBER) goto xbad_arg_2;
y = (double)(sp->u.number);
}
else
y = READ_DOUBLE(sp);
if (sp[-1].type != T_FLOAT)
{
if (sp[-1].type != T_NUMBER) goto xbad_arg_1;
x = (double)sp[-1].u.number;
}
else
x = READ_DOUBLE(sp-1);
if (x == 0. && y < 0.)
ERROR("Can't raise 0 to negative powers.\n")
if (x < 0. && y != (double)((long)y))
ERROR("Can't raise negative number to fractional powers.\n")
d = pow(x, y);
if (d < (-DBL_MAX) || d > DBL_MAX)
ERRORF(("Numeric overflow: pow(%g, %g)\n", x, y));
pop_stack();
sp->type = T_FLOAT;
STORE_DOUBLE(sp, d);
break;
}
XCASE(F_GET_TYPE_INFO); /* --- esc get_type_info --- */
{
/* XEFUN get_type_info()
*
* mixed get_type_info(mixed arg, int flag)
*
* Returns info about the type of arg, as controlled by the flag.
*
* If the optional argument flag is not a number, an array is
* returned, whose first element is an integer denoting the data
* type, as defined in <lpctypes.h>. The second entry can contain
* additional information about arg.
* If flag is the number 0, only the first element of that array
* (i.e. the data type) is returned (as int).
* If flag is 1, the second element is returned.
* If <arg> is a closure, the <flag> setting 2 lets the efun
* return the object the closure is bound to.
* For every other <flag> setting, -1 is returned.
*
* The secondary information is:
* - for mappings the width, ie the number of data items per key.
* - for symbols and quoted arrays the number of quotes.
* - for closures the closure type
* - for strings 0 for shared strings, and non-0 for others.
* - -1 for all other datatypes.
*
* TODO: The flags should be defined in an include file.
* TODO: The array returned for closures should contain all
* TODO:: three items.
*/
mp_int i, j;
i = sp[-1].type;
/* Determine the second return value */
switch(i)
{
default:
j = -1;
break;
case T_STRING:
j = (sp[-1].x.string_type == STRING_SHARED) ? 0 : 1;
break;
case T_MAPPING:
j = sp[-1].u.map->num_values;
break;
case T_CLOSURE:
if ( sp->type == T_NUMBER && sp->u.number == 2)
{
object_t *ob;
ob = NULL;
sp--;
switch(sp->x.closure_type)
{
default:
/* efun, simul-efun, operator closure */
ob = sp->u.ob;
break;
case CLOSURE_LFUN:
case CLOSURE_IDENTIFIER:
case CLOSURE_BOUND_LAMBDA:
case CLOSURE_LAMBDA:
ob = sp->u.lambda->ob;
break;
case CLOSURE_ALIEN_LFUN:
ob = sp->u.lambda->function.alien.ob;
break;
case CLOSURE_UNBOUND_LAMBDA:
ob = NULL;
break;
}
free_closure(sp);
if (!ob || ob->flags & O_DESTRUCTED)
put_number(sp, 0);
else
put_ref_object(sp, ob, "get_type_info");
goto again;
/* NOTREACHED */
}
case T_SYMBOL:
case T_QUOTED_ARRAY:
j = sp[-1].x.generic;
break;
}
/* Depending on flag, return the proper value */
if (sp->type == T_NUMBER)
{
free_svalue(--sp);
if (sp[1].u.number != 1)
{
if (sp[1].u.number)
j = -1;
else
j = i;
}
put_number(sp, j);
}
else
{
vector_t *v;
inter_sp = sp;
inter_pc = pc;
v = allocate_array(2);
v->item[0].u.number = i;
v->item[1].u.number = j;
free_svalue(sp);
free_svalue(--sp);
put_array(sp,v);
}
break;
}
XCASE(F_RAISE_ERROR); /* --- esc raise_error --- */
{
/* XEFUN raise_error()
*
* void raise_error(string arg)
*
* Abort execution. If the current program execution was initiated
* by catch(), that catch expression will return arg as error
* code, else the arg will printed as error message. This
* is very similar to throw(), but while throw() is intended to be
* called inside catch(), raise_error() can be called
* anywhere.
*/
if (sp->type != T_STRING)
goto xbad_arg_1;
ERRORF(("%s", sp->u.string));
}
XCASE(F_REFERENCEP); /* --- esc referencep --- */
{
/* XEFUN referencep()
*
* int referencep(mixed arg)
*
* Returns true if arg was passed by reference to the current
* function, instead of the usual call-by-value.
*/
int i;
if (sp->type != T_LVALUE)
goto xbad_arg_1;
i = sp->u.lvalue->type == T_LVALUE;
free_svalue(sp);
put_number(sp, i);
break;
}
XCASE(F_TYPEOF); /* --- esc typeof --- */
{
/* XEFUN typeof()
*
* int typeof(mixed)
*
* Returns a code for the type of the argument, as defined in
* <sys/lpctypes.h>
*/
mp_int i = sp->type;
free_svalue(sp);
put_number(sp, i);
break;
}
XCASE(F_TO_INT); /* --- esc to_int --- */
{
/* EFUN to_int()
*
* int to_int(string)
* int to_int(float)
* int to_int(int)
* int to_int(closure)
*
* Floats are truncated to integer values, strings with leadings
* digits are converted to integers up to the first non-digit.
* variable- and lfun-closures are converted into their variable
* resp.function index.
* Integers are just returned.
*/
int n;
switch(sp->type)
{
default:
goto xbad_arg_1;
case T_FLOAT:
{
double d;
d = READ_DOUBLE(sp);
if (d < (-DBL_MAX) || d > DBL_MAX)
ERRORF(("Numeric overflow: to_int(%g)\n", d));
n = (long)d;
break;
}
case T_STRING:
n = atol(sp->u.string);
/* TODO: make this strtol() */
free_string_svalue(sp);
break;
case T_CLOSURE:
if (sp->x.closure_type == CLOSURE_IDENTIFIER
|| sp->x.closure_type == CLOSURE_LFUN)
n = sp->u.lambda->function.index;
else
goto xbad_arg_1;
free_closure(sp);
break;
case T_NUMBER:
n = sp->u.number;
break;
}
put_number(sp, n);
break;
}
XCASE(F_TO_FLOAT); /* --- esc to_float --- */
{
/* EFUN to_float()
*
* float to_float(int)
* float to_float(string)
* float to_float(float)
*
* Ints are expanded to floats, strings are converted up to the
* first character that doesnt belong into a float.
* Floats are just returned.
*/
STORE_DOUBLE_USED
double d;
d = 0.0;
switch(sp->type)
{
default:
goto xbad_arg_1;
case T_NUMBER:
d = (double)sp->u.number;
break;
case T_FLOAT:
NOOP;
break;
case T_STRING:
d = atof(sp->u.string);
/* TODO: make this strtof() or so */
free_string_svalue(sp);
break;
}
if (sp->type != T_FLOAT)
{
sp->type = T_FLOAT;
STORE_DOUBLE(sp, d);
}
break;
}
XCASE(F_TO_STRING); /* --- esc to_string --- */
{
/* XEFUN to_string()
*
* string to_string(mixed)
*
* The argument is converted to a string. Works with int, float,
* object, arrays (to convert an array of int back into a string),
* symbols, strings, and closures.
*
* Converts variable/lfun closure to the appropriate names.
*/
char buf[1024], *s;
s = NULL;
buf[sizeof(buf)-1] = '\0';
switch(sp->type)
{
default:
goto xbad_arg_1;
case T_NUMBER:
sprintf(buf,"%ld", sp->u.number);
if (buf[sizeof(buf)-1] != '\0')
FATAL("Buffer overflow in F_TO_STRING: "
"int number too big.\n")
s = string_copy(buf);
break;
case T_FLOAT:
sprintf(buf,"%g", READ_DOUBLE(sp));
if (buf[sizeof(buf)-1] != '\0')
FATAL("Buffer overflow in F_TO_STRING: "
"int number too big.\n")
s = string_copy(buf);
break;
case T_OBJECT:
if (!compat_mode)
s = add_slash(sp->u.ob->name);
else
s = string_copy(sp->u.ob->name);
if (!s)
ERROR("Out of memory\n")
free_object_svalue(sp);
break;
case T_POINTER:
{
/* Arrays of ints are considered exploded strings and
* converted back accordingly, ie. up to the first 0
* or the first non-int.
*/
long size;
svalue_t *svp;
char c, *d;
size = (long)VEC_SIZE(sp->u.vec) + 1;
svp = sp->u.vec->item;
d = s = xalloc((size_t)size);
for (;;)
{
if (!--size)
{
*d++ = '\0';
break;
}
if (svp->type != T_NUMBER || !(c = (char)svp->u.number) )
{
*d++ = '\0';
d = string_copy(s);
xfree(s);
s = d;
break;
}
*d++ = c;
svp++;
}
free_array(sp->u.vec);
break;
}
case T_CLOSURE:
{
/* Convert the various types of closures into a string */
lambda_t *l = sp->u.lambda;
object_t *ob;
int ix;
switch(sp->x.closure_type)
{
case CLOSURE_IDENTIFIER: /* Variable Closure */
{
/* We need the program resident */
if (O_PROG_SWAPPED(l->ob))
{
l->ob->time_of_ref = current_time;
if (load_ob_from_swap(l->ob) < 0)
ERROR("Out of memory.\n");
}
if (l->function.index != VANISHED_VARCLOSURE_INDEX)
{
/* Get the variable name */
put_ref_string(sp
, l->ob->prog->variable_names[l->function.index].name
);
}
else
{
/* Variable vanished in a replace_program() */
put_volatile_string(sp, "dangling var closure");
}
break;
}
case CLOSURE_LFUN: /* Lfun closure */
case CLOSURE_ALIEN_LFUN:
{
program_t *prog;
fun_hdr_p fun;
funflag_t flags;
char *function_name;
inherit_t *inheritp;
if (sp->x.closure_type == CLOSURE_LFUN)
{
ob = l->ob;
ix = l->function.index;
}
else
{
ob = l->function.alien.ob;
ix = l->function.alien.index;
/* TODO: ix: After a replace_program, can this index
* TODO:: be negative?
*/
}
/* Get the program resident */
if (O_PROG_SWAPPED(ob)) {
ob->time_of_ref = current_time;
if (load_ob_from_swap(ob) < 0)
ERROR("Out of memory\n");
}
/* Find the true definition of the function */
prog = ob->prog;
flags = prog->functions[ix];
while (flags & NAME_INHERITED)
{
inheritp = &prog->inherit[flags & INHERIT_MASK];
ix -= inheritp->function_index_offset;
prog = inheritp->prog;
flags = prog->functions[ix];
}
/* Copy the function name pointer (a shared string) */
fun = prog->program + (flags & FUNSTART_MASK);
memcpy(&function_name, FUNCTION_NAMEP(fun)
, sizeof function_name
);
put_ref_string(sp, function_name);
break;
}
case CLOSURE_UNBOUND_LAMBDA: /* Unbound-Lambda Closure */
case CLOSURE_PRELIMINARY: /* Preliminary Lambda Closure */
{
char *rc;
if (sp->x.closure_type == CLOSURE_PRELIMINARY)
sprintf(buf, "<prelim lambda 0x%p>", l);
else
sprintf(buf, "<free lambda 0x%p>", l);
rc = string_copy(buf);
put_malloced_string(sp, rc);
break;
}
case CLOSURE_LAMBDA: /* Lambda Closure */
case CLOSURE_BOUND_LAMBDA: /* Bound-Lambda Closure */
{
char *rc;
if (sp->x.closure_type == CLOSURE_BOUND_LAMBDA)
sprintf(buf, "<bound lambda 0x%p:", l);
else
sprintf(buf, "<lambda 0x%p:", l);
ob = l->ob;
if (!ob)
{
strcat(buf, "{null}>");
rc = string_copy(buf);
}
else
{
if (ob->flags & O_DESTRUCTED)
strcat(buf, "{dest}");
xallocate(rc, strlen(buf)+strlen(ob->name)+3
, "string-repr of lambda closure");
strcat(buf, "/");
strcpy(rc, buf);
strcat(rc, ob->name);
strcat(rc, ">");
}
put_malloced_string(sp, rc);
break;
}
default:
{
int type = sp->x.closure_type;
if (type < 0)
{
switch(type & -0x0800)
{
case CLOSURE_OPERATOR:
{
s = NULL;
switch(type - CLOSURE_OPERATOR)
{
case F_POP_VALUE:
s = ",";
break;
case F_BBRANCH_WHEN_NON_ZERO:
s = "do";
break;
case F_BBRANCH_WHEN_ZERO:
s = "while";
break;
case F_BRANCH:
s = "continue";
break;
case F_CSTRING0:
s = "default";
break;
case F_BRANCH_WHEN_ZERO:
s = "?";
break;
case F_BRANCH_WHEN_NON_ZERO:
s = "?!";
break;
case F_RANGE:
s = "[..]";
break;
case F_NR_RANGE:
s = "[..<]";
break;
case F_RR_RANGE:
s = "[<..<]";
break;
case F_RN_RANGE:
s = "[<..]";
break;
case F_MAP_INDEX:
s = "[,]";
break;
case F_NX_RANGE:
s = "[..";
break;
case F_RX_RANGE:
s = "[<..";
break;
}
if (s)
{
put_volatile_string(sp, s);
break;
}
type += CLOSURE_EFUN - CLOSURE_OPERATOR;
}
/* default action for operators: FALLTHROUGH */
case CLOSURE_EFUN:
{
char *rc;
sprintf(buf, "#'%s"
, instrs[type - CLOSURE_EFUN].name);
rc = string_copy(buf);
put_malloced_string(sp, rc);
break;
}
case CLOSURE_SIMUL_EFUN:
{
char *rc;
sprintf(buf, "#'<sefun>%s"
, simul_efunp[type - CLOSURE_SIMUL_EFUN].name);
rc = string_copy(buf);
put_malloced_string(sp, rc);
break;
}
}
break;
}
else /* type >= 0 */
{
goto xbad_arg_1;
} /* if (type) */
} /* case default */
} /* switch(closure type) */
break;
}
case T_SYMBOL:
{
/* Easy: the symbol value is a string */
sp->type = T_STRING;
sp->x.string_type = STRING_SHARED;
break;
}
case T_STRING:
break;
}
if (sp->type != T_STRING)
put_malloced_string(sp, s);
break;
}
XCASE(F_TO_ARRAY); /* --- esc to_array --- */
{
/* XEFUN to_array()
*
* mixed *to_array(string)
* mixed *to_array(symbol)
* mixed *to_array(quotedarray)
* mixed *to_array(mixed *)
*
* Strings and symbols are converted to an int array that
* consists of the args characters, with 0 == '\0' as last
* character stored.
* Quoted arrays are ``dequoted'', and arrays are left as they
* are.
*/
vector_t *v;
char *s, ch;
svalue_t *svp;
if (sp->type == T_STRING || sp->type == T_SYMBOL)
{
/* Split the string into an array of ints */
inter_sp = sp;
inter_pc = pc;
v = allocate_uninit_array((mp_int)svalue_strlen(sp) + 1);
s = sp->u.string;
svp = v->item;
do {
ch = *s++;
put_number(svp, ch);
svp++;
} while (ch);
free_string_svalue(sp);
put_array(sp, v);
break;
}
else if (sp->type == T_QUOTED_ARRAY)
{
/* Unquote it fully */
sp->type = T_POINTER;
break;
}
else if (sp->type == T_POINTER)
{
/* Good as it is */
break;
}
else
goto xbad_arg_1;
break;
}
/* --- XEfuns: Strings --- */
XCASE(F_STRSTR); /* --- esc strstr --- */
{
/* XEFUN strstr()
*
* int strstr (string str, string str2, int pos)
*
* Returns the index of str2 in str searching from position pos.
* If str2 is not found in str, -1 is returned. The returned
* index is relativ to the beginning of the string.
*
* If pos is negativ, it counts from the end of the string.
*/
char *p1, *p2;
int offs;
if (sp[-2].type != T_STRING) goto xbad_arg_1;
if (sp[-1].type != T_STRING) goto xbad_arg_2;
if (sp[ 0].type != T_NUMBER) goto xbad_arg_3;
p1 = sp[-2].u.string;
if ( 0 != (offs = sp->u.number) )
{
/* Set p1 to the offset */
if (offs < 0)
{
offs += svalue_strlen(sp-2);
if (offs < 0)
offs = 0;
}
/* The loop is necessary because the allocated
* length might not be the real length.
* TODO: Lars sighs deeply.
*/
if (offs)
{
do {
if (!*p1++)
{
p1--;
break;
}
} while (--offs);
}
}
/* Now do the search starting at p1 */
p2 = strstr(p1, sp[-1].u.string);
p1 = sp[-2].u.string;
sp--;
pop_stack();
free_string_svalue(sp);
put_number(sp, p2 ? (p2 - p1) : -1);
break;
}
/* --- XEfuns: Arrays and Mappings --- */
XCASE(F_M_ALLOCATE); /* --- esc m_allocate --- */
{
/* XEFUN m_allocate()
*
* mapping m_allocate(int size, int width)
*
* Reserve memory for a mapping.
*
* size is the number of entries (i.e. keys) to reserve, width is
* the number of data items per entry. If the optional width is
* omitted, 1 is used as default.
*/
if ( sp[-1].type != T_NUMBER || sp[-1].u.number < 0)
goto xbad_arg_1;
if ( sp->type != T_NUMBER || sp->u.number < 0)
goto xbad_arg_2;
sp--;
if (max_mapping_size && sp->u.number > max_mapping_size)
ERRORF(("Illegal mapping size: %ld\n", sp->u.number));
if (!(sp->u.map = allocate_mapping(sp->u.number, sp[1].u.number)))
{
sp++;
/* sp points to a number-typed svalue, so freeing won't
* be a problem.
*/
ERROR("Out of memory\n")
}
sp->type = T_MAPPING;
break;
}
XCASE(F_M_CONTAINS); /* --- esc m_contains <nargs> --- */
{
/* XEFUN m_contains()
*
* int m_contains(mixed &data1, ..., &dataN, map, key)
*
* If the mapping contains the key map, the corresponding values
* are assigned to the data arguments, which massed be passed by
* reference, and 1 is returned. If key is not in map, 0 is
* returned and the data args are left unchanged.
* It is possible to use this function for a 0-value mapping, in
* which case it has the same effect as member(E).
*/
svalue_t *item;
int i;
GET_NUM_ARG
/* Test the arguments */
for (i = -num_arg; ++i < -1; )
if (sp[i].type != T_LVALUE)
bad_arg_pc(num_arg + i, code + 0x100, sp, pc);
if (sp[-1].type != T_MAPPING ||
sp[-1].u.map->num_values != num_arg -2)
bad_arg_pc(num_arg + i, code + 0x100, sp, pc);
item = get_map_value(sp[-1].u.map, sp);
if (item == &const0)
{
/* Not found */
pop_n_elems(num_arg-1);
free_svalue(sp);
put_number(sp, 0);
break;
}
free_svalue(sp--); /* free key */
/* Copy the elements */
for (i = -num_arg + 1; ++i < 0; )
{
/* get_map_lvalue() may return destructed objects. */
/* TODO: May this cause problems elsewhere, too? */
if (destructed_object_ref(item))
{
assign_svalue(sp[i].u.lvalue, &const0);
item++;
}
else
/* mapping must not have been freed yet */
assign_svalue(sp[i].u.lvalue, item++);
free_svalue(&sp[i]);
}
free_svalue(sp--); /* free mapping */
sp += 3 - num_arg;
put_number(sp, 1);
break;
}
/* --- XEfuns: Functions and Closures --- */
XCASE(F_CALLER_STACK); /* --- esc caller_stack --- */
{
/* XEFUN caller_stack()
*
* object *caller_stack()
* object *caller_stack(int add_interactive)
*
* Returns an array of the previous_object()s who caused the
* call_other() to this_object(). previous_object(i) equals
* caller_stack()[i].
* If you pass the optional argument <add_interactive> (as true
* value), this_interactive() (or 0 if not existing) is appended
* to the array.
*/
int depth, i;
Bool done;
struct control_stack *p;
vector_t *v;
svalue_t *svp;
TYPE_TEST1(sp, T_NUMBER)
/* Determine the depth of the call stack */
p = csp;
for (depth = 0, done = MY_FALSE; ; depth++)
{
do {
if (p == CONTROL_STACK)
{
done = MY_TRUE;
break;
}
} while ( !(--p)[1].extern_call );
if (done)
break;
}
/* Allocate and fill in the result array */
v = allocate_uninit_array(depth + (sp->u.number ? 1 : 0));
p = csp;
for (i = 0, svp = v->item, done = MY_FALSE; i < depth; i++, svp++)
{
object_t *prev;
do {
if (p == CONTROL_STACK)
{
done = MY_TRUE;
break;
}
} while ( !(--p)[1].extern_call);
/* Break if end of stack */
if (done)
break;
/* Get 'the' calling object */
if (p[1].extern_call & CS_PRETEND)
prev = p[1].pretend_to_be;
else
prev = p[1].ob;
/* Enter it into the array */
if (prev == NULL || prev->flags & O_DESTRUCTED)
put_number(svp, 0);
else
put_ref_object(svp, prev, "caller_stack");
}
#ifdef DEBUG
if (i < depth)
{
error("Computed stack depth to %d, but found only %d objects\n"
, depth, i);
/* NOTREACHED */
break;
}
#endif
/* If so desired, add the interactive object */
if (sp->u.number)
{
if ( current_interactive
&& !(current_interactive->flags & O_DESTRUCTED))
{
put_ref_object(svp, current_interactive, "caller_stack");
}
else
put_number(svp, 0);
}
/* Assign the result and return */
put_array(sp, v);
break;
}
XCASE(F_CALLER_STACK_DEPTH); /* esc caller_stack_depth --- */
{
/* XEFUN caller_stack_depth()
*
* int caller_stack_depth(void)
*
* Returns the number of previous objects on the stack. This
* can be used for security checks.
*/
int depth;
Bool done;
struct control_stack *p;
/* Determine the depth of the call stack */
p = csp;
for (depth = 0, done = MY_FALSE; ; depth++)
{
do {
if (p == CONTROL_STACK)
{
done = MY_TRUE;
break;
}
} while ( !(--p)[1].extern_call );
if (done)
break;
}
push_number(depth);
break;
}
XCASE(F_CALL_RESOLVED); /* --- esc call_resolved <nargs> --- */
{
/* XEFUN call_resolved()
*
* int call_resolved(mixed & result, object ob, string func, ...)
*
* Similar to call_other(). If ob->func() is defined and publicly
* accessible, any of the optional extra arguments are passed to
* ob->func(...). The result of that function call is stored in
* result, which must be passed by reference.
*
* If the current object is already destructed, or the ob does not
* exist, or ob does not define a public accessible function named
* func, call_resolved() returns 0 as failure code, else 1 for
* success.
*
* ob can also be a file_name. If a string is passed for ob, and
* no object with that name does exist, an error occurs.
*/
svalue_t *arg;
object_t *ob;
ASSIGN_EVAL_COST
GET_NUM_ARG
inter_pc = pc;
inter_sp = sp;
arg = sp - num_arg + 1;
/* Test the arguments */
if (arg[0].type != T_LVALUE)
goto xbad_arg_1;
if (arg[1].type == T_NUMBER && arg[1].u.number == 0)
ob = NULL;
else if (arg[1].type == T_OBJECT)
ob = arg[1].u.ob;
else if (arg[1].type == T_STRING)
{
ob = get_object(arg[1].u.string);
if (!ob)
ERROR("call_resolved() failed: can't get object.\n")
}
else
goto xbad_arg_2;
if (arg[2].type != T_STRING)
goto xbad_arg_3;
/* No external calls may be done when this object is
* destructed, or if the called object is destructed.
*/
if (current_object->flags & O_DESTRUCTED
|| ob == NULL)
{
pop_n_elems(num_arg);
push_number(0);
break;
}
/* Handle traceing. */
if (TRACEP(TRACE_CALL_OTHER) && TRACE_IS_INTERACTIVE())
{
if (!++traceing_recursion)
{
inter_sp = sp;
do_trace("Call other ", arg[2].u.string, "\n");
}
traceing_recursion--;
}
/* Send the remaining arguments to the function.
*/
if (!apply_low(arg[2].u.string, ob, num_arg-3, MY_FALSE))
{
/* Function not found */
pop_n_elems(num_arg-1);
free_svalue(sp);
put_number(sp, 0);
break;
}
/* The result of the function call is on the stack. But, so
* is the function name and object that was called.
* These have to be removed.
*/
sp = inter_sp;
transfer_svalue(arg, sp--); /* Copy the function call result */
pop_n_elems(2); /* Remove old arguments to call_solved */
free_svalue(sp); /* Free the lvalue */
put_number(sp, 1);
break;
}
XCASE(F_EXTERN_CALL); /* --- esc extern_call --- */
{
/* XEFUN extern_call()
*
* int extern_call();
*
* Returns zero, if the function that is currently being executed
* was called by a local call, non-zero for call_other(), driver
* applies, closure calls, etc. Currently the only return value
* for them is 1, but later the various methods may be
* distinguished by means of the return value.
*/
struct control_stack * pt = csp;
while (pt->catch_call) pt--;
push_number((pt->extern_call & ~CS_PRETEND) ? 1 : 0);
break;
}
XCASE(F_GET_EVAL_COST); /* --- esc get_eval_cost --- */
{
/* XEFUN get_eval_cost()
*
* int get_eval_cost()
*
* Returns the remaining evaluation cost the current
* execution (the current command) may use up.
*
* It starts at a driver given high value (__MAX_EVAL_COST__) and
* is reduced with each executed statement.
*/
push_number((max_eval_cost ? max_eval_cost : LONG_MAX) - eval_cost);
break;
}
XCASE(F_PREVIOUS_OBJECT); /* --- esc previous_object --- */
{
/* XEFUN previous_object()
*
* object previous_object(int i)
*
* Follow back the last <i> call_other()s and return the calling
* object (i.e. previous_object(2) returns the caller of the
* caller). It must hold 0 <= i < caller_stack_depth().
*
* There is an important special case: in functions called by the
* gamedriver in reaction to some external event (e.g. commands
* added by add_action), previous_object() will return
* this_object(), but previous_object(0) will return 0.
*/
int i;
struct control_stack *p;
object_t *prev_ob;
/* Test the arguments */
if (sp->type != T_NUMBER)
goto xbad_arg_1;
i = sp->u.number;
if (i > MAX_TRACE) {
sp->u.number = 0;
break;
}
/* Set p back to the <i>th extern call */
p = csp;
do {
do {
if (p == CONTROL_STACK) {
sp->u.number = 0;
goto again;
}
} while ( !(--p)[1].extern_call );
} while (--i >= 0);
/* Determine the object and push it */
if (p[1].extern_call & CS_PRETEND)
prev_ob = p[1].pretend_to_be;
else
prev_ob = p[1].ob;
if (!prev_ob || prev_ob->flags & O_DESTRUCTED)
sp->u.number = 0;
else
put_ref_object(sp, prev_ob, "previous_object");
break;
}
/* --- XEfuns: Objects --- */
XCASE(F_OBJECT_TIME); /* --- esc object_time --- */
{
/* XEFUN 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;
if (sp->type != T_OBJECT)
goto xbad_arg_1;
load_time = sp->u.ob->load_time;
free_object_svalue(sp);
put_number(sp, load_time);
break;
}
XCASE(F_PROGRAM_TIME); /* --- esc program_time --- */
{
/* XEFUN 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;
if (sp->type != T_OBJECT)
goto xbad_arg_1;
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--;
ERROR("Out of memory\n")
}
}
load_time = sp->u.ob->prog->load_time;
free_object_svalue(sp);
put_number(sp, load_time);
break;
}
XCASE(F_PROGRAM_NAME); /* --- esc program_name --- */
{
/* XEFUN 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.
*/
char *name, *res;
object_t *ob;
/* If the argument is 0, return 0. */
if (sp->type == T_NUMBER && sp->u.number == 0)
{
break;
}
TYPE_TEST1(sp, T_OBJECT)
ob = sp->u.ob;
if (O_PROG_SWAPPED(ob))
{
ob->time_of_ref = current_time;
if (load_ob_from_swap(ob) < 0)
{
ERROR("Out of memory\n");
}
}
name = ob->prog->name;
if (compat_mode)
res = string_copy(name);
else
res = add_slash(name);
if (!res)
ERROR("Out of memory\n")
free_object_svalue(sp);
put_malloced_string(sp, res);
break;
}
/* --- esc query_once_interactive --- */
XCASE(F_QUERY_ONCE_INTERACTIVE);
{
/* XEFUN query_once_interactive()
*
* int query_once_interactive(object ob)
*
* True if the object is or once was interactive.
*/
object_t *obj;
if (sp->type != T_OBJECT) goto xbad_arg_1;
obj = sp->u.ob;
put_number(sp, obj->flags & O_ONCE_INTERACTIVE ? 1 : 0);
deref_object(obj, "query_once_interactive");
break;
}
/* --- XEfuns: Network IO --- */
XCASE(F_QUERY_UDP_PORT); /* --- esc query_udp_port --- */
{
/* XEFUN query_udp_port()
*
* int query_udp_port(void)
*
* Returns the port number that is used for the inter mud
* protocol.
*/
push_number(udp_port);
break;
}
XCASE(F_QUERY_INPUT_PENDING); /* --- esc query_input_pending --- */
{
/* XEFUN query_input_pending()
*
* object query_input_pending(object ob)
*
* If ob is interactive and currently has an input_to() pending,
* the object that has called the input_to() is returned,
* else 0.
*/
object_t *ob, *cb;
interactive_t *ip;
TYPE_TEST1(sp, T_OBJECT)
ob = sp->u.ob;
if (O_SET_INTERACTIVE(ip, ob) && ip->input_to)
{
cb = callback_object(&(ip->input_to->fun));
if (cb)
sp->u.ob = ref_object(cb, "query_input_pending");
else
put_number(sp, 0);
}
else
{
put_number(sp, 0);
}
deref_object(ob, "query_input_pending");
break;
}
XCASE(F_QUERY_IP_NAME); /* --- esc query_ip_name --- */
{
/* XEFUN query_ip_name()
*
* string query_ip_name(object ob)
*
* Give the ip-name for user the current user or for the optional
* argument ob. An asynchronous process 'hname' is used to find
* out these names in parallel. If there are any failures to find
* the ip-name, then the ip-number is returned instead.
*/
inter_pc = pc;
sp = query_ip_name(sp, MY_TRUE);
break;
}
XCASE(F_QUERY_IP_NUMBER); /* --- esc query_ip_number --- */
{
/* XEFUN query_ip_number()
*
* string query_ip_number(object ob)
* string query_ip_number(mixed & ob)
*
* Give the ip-number for the current user or the optional
* argument ob.
*
* If ob is given as reference (and it must be a valid object
* then), it will upon return be set to the struct sockaddr_in of
* the queried object, represented by an array of integers, one
* integer per address byte:
* ob[0.. 1]: sin_family
* ob[2.. 3]: sin_port
* ob[4.. 7]: sin_addr
* ob[8..15]: undefined.
*/
inter_pc = pc;
sp = query_ip_name(sp, MY_FALSE);
break;
}
XCASE(F_QUERY_MUD_PORT); /* --- esc query_mud_port --- */
{
/* XEFUN query_mud_port()
*
* int query_mud_port(void)
* int query_mud_port(object user)
* int query_mud_port(int num)
*
* Returns the port number the parser uses for user connections.
*
* If no argument is given, the port for this_player() is
* returned. If this_player() is not existing or not interactive,
* the first port number open for connections is returned.
*
* If an user object is given, the port used for its connection is
* returned.
* If a positive number is given, the <num>th port number the
* parser uses for connections is returned (given that there are
* that many ports).
* If -1 is given, the number of ports open for connections is
* returned.
*/
inter_pc = pc;
sp = query_ip_port(sp);
break;
}
/* --- XEfuns: Driver and System --- */
XCASE(F_GARBAGE_COLLECTION); /* --- esc garbage_collection --- */
{
/* XEFUN garbage_collection()
*
* void garbage_collection(void)
* void garbage_collection(string filename)
*
* Tell the parser to initiate a garbage collection after the
* current execution ended.
*/
GET_NUM_ARG
if (num_arg)
{
if (sp->type != T_STRING)
goto xbad_arg_1;
#if defined(GC_SUPPORT)
{
int fd;
char * path;
restore_default_gc_log();
path = check_valid_path( sp->u.string, current_object
, "garbage_collection", MY_TRUE);
if (path == NULL)
{
ERRORF(("Illegal arg 1 to garbage_collection(): "
"No privilege to write file '%s'.\n"
, sp->u.string
));
/* NOTREACHED */
break;
}
fd = ixopen3(path, O_CREAT|O_APPEND|O_WRONLY, 0640);
if (fd < 0)
{
ERRORF(("Can't open GC log file '%s': errno %d\n"
, path, errno));
/* NOTREACHED */
break;
}
gcollect_outfd = fd;
}
#endif
free_svalue(sp); sp--;
}
extra_jobs_to_do = MY_TRUE;
gc_request = gcEfun;
break;
}
/* --- XEfuns: Inventories */
XCASE(F_ALL_ENVIRONMENT); /* --- esc all_environment <nargs> --- */
{
/* XEFUN 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.
*/
GET_NUM_ARG
if (num_arg
&& sp->type != T_OBJECT
&& (sp->type != T_NUMBER || sp->u.number != 0)
) goto xbad_arg_1;
if (!num_arg || sp->type != T_NUMBER)
{
/* Not a destructed object given: do the call */
inter_sp = sp;
sp = x_all_environment(sp, num_arg);
} /* else: the 0 from the destructed object is also the result */
break;
}
/* --- Optional XEfuns --- */
#ifdef F_COPY_MAPPING
XCASE(F_COPY_MAPPING); /* --- esc copy_mapping --- */
{
/* XEFUN copy_mapping()
*
* mapping copy_mapping(mapping)
*
* This efun is needed to create copies of mappings instead of
* just passing a reference, like adding/subtraction from a
* mapping do.
* TODO: This efun is outdated by the copy() efun.
*/
mapping_t *m, *m2;
TYPE_TEST1(sp, T_MAPPING)
m = sp->u.map;
check_map_for_destr(m);
m2 = copy_mapping(m);
free_mapping(m);
sp->u.map = m2;
break;
}
#endif
#ifdef F_EXTRACT
/* TODO: Get rid of efun extract() altogether */
XCASE(F_EXTRACT2); /* --- esc extract2 --- */
{
/* Compute the range sp[0]..end from string/array sp[-1]
* and leave it on the stack. If sp[0] is negative, it is
* counted from the end of the string/array.
*
* The compiler generates this if the efun extract() is called
* with just two arguments.
* TODO: Get rid of efun extract() .
*/
long len, from;
svalue_t *arg;
arg = sp - 1;
if (arg->type == T_STRING)
{
/* Slice an array */
char *res;
len = (long)_svalue_strlen(&arg[0]);
if ((arg+1)->type != T_NUMBER)
{
ERRORF(("Index value must be a number.\n"));
/* NOTREACHED */
return MY_FALSE; /* Flow control hint */
}
from = arg[1].u.number;
sp--;
if (from < 0) {
from = len + from;
if (from < 0)
from = 0;
}
if (from >= len) {
pop_stack();
push_volatile_string("");
break;
}
res = string_copy(arg->u.string + from);
free_string_svalue(sp);
put_malloced_string(sp, res);
break;
}
if (arg->type != T_POINTER)
{
ERRORF(("Indexed value is neither string nor array.\n"));
/* NOTREACHED */
return MY_FALSE; /* Flow control hint */
}
/* Slice an array */
{
vector_t *v, *res;
if ((arg+1)->type != T_NUMBER)
{
ERRORF(("Index value must be a number.\n"));
/* NOTREACHED */
return MY_FALSE; /* Flow control hint */
}
v = arg->u.vec;
len = (long)VEC_SIZE(v);
from = arg[1].u.number;
sp--;
if (from < 0) {
from = len + from;
}
res = slice_array(v, from, len-1);
free_array(v);
put_array(sp,res);
break;
}
}
XCASE(F_EXTRACT1); /* --- esc extract1 --- */
{
/* XEFUN extract1()
*
* string extract(string arg)
*
* Generated by the compiler when it finds efun extract()
* used with just the string, this efun returns the string.
*/
if (sp->type != T_STRING)
goto xbad_arg_1;
break;
}
XCASE(F_EXTRACT); /* --- esc extract --- */
{
/* XEFUN extract()
*
* string extract(string str, int from, int to)
* string extract(string str, int from)
* mixed * extract(mixed * arr, int from, int to)
* mixed * extract(mixed * arr, int from)
*
* Extract a substring from a string, resp. a subarray
* from an array.
*
* This is the old notation for str[from..to] and supported
* only for hysterical raisins. The distinctive point is that
* negative values for from and to implement the from-the-end
* indexing.
*/
if (sp[-1].type != T_NUMBER) goto xbad_arg_2;
if (sp[0].type != T_NUMBER) goto xbad_arg_3;
if (sp[-2].type == T_POINTER)
{
/* Extract from an array */
vector_t *v;
mp_int end, size;
v = sp[-2].u.vec;
v =
slice_array(
v,
sp[-1].u.number,
(end = sp[0].u.number) >= (size = (mp_int)VEC_SIZE(v)) ?
size - 1 :
end
);
pop_n_elems(3);
if (v)
{
push_referenced_vector(v);
}
else
{
push_number(0);
}
}
else if (sp[-2].type == T_STRING)
{
/* Extract from a string */
long len, from, to;
char *res;
len = (long)_svalue_strlen(&sp[-2]);
from = sp[-1].u.number;
if (from < 0) {
from = len + from;
if (from < 0)
from = 0;
}
to = sp[0].u.number;
if (to < 0)
to = len + to;
if (to >= len)
to = len-1;
if (to < from) {
pop_n_elems(3);
push_volatile_string("");
break;
}
if (to == len-1) {
res = string_copy(sp[-2].u.string + from);
pop_n_elems(3);
push_malloced_string(res);
break;
}
res = xalloc((size_t)(to - from + 2));
strncpy(res, sp[-2].u.string + from, (size_t)(to - from + 1));
res[to - from + 1] = '\0';
pop_n_elems(3);
push_malloced_string(res);
}
else
{
goto xbad_arg_1;
}
break;
}
#endif
#ifdef F_SWAP
XCASE(F_SWAP); /* --- esc swap --- */
{
/* XEFUN swap()
*
* void swap(object obj)
*
* Swap out an object. This efun is only used for system internal
* debugging and can cause a crash.
*/
object_t *ob;
if (sp->type != T_OBJECT) goto xbad_arg_1;
ob = sp->u.ob;
if (ob != current_object) /* should also check csp */
{
if (!O_PROG_SWAPPED(ob))
(void)swap_program(ob);
if (!O_VAR_SWAPPED(ob))
(void)swap_variables(ob);
}
pop_stack();
break;
}
#endif
} /* switch(code) */
break;
} /* end of F_ESCAPE */
} /* end of the monumental switch */
/* Instruction executed */
/* Even intermediate results could exceed the stack size.
* We better check for that.
*/
if (sp - VALUE_STACK == SIZEOF_STACK - 1)
{
/* sp ist just at then end of the stack area */
stack_overflow(sp, fp, pc);
}
else if ((long)(sp - VALUE_STACK) > (long)(SIZEOF_STACK - 1))
{
/* When we come here, we already overwrote the bounds
* of the stack :-(
*/
fatal("Fatal stack overflow: %ld too high\n"
, (long)(sp - VALUE_STACK - (SIZEOF_STACK - 1))
);
}
#ifdef DEBUG
if (expected_stack && expected_stack != sp)
{
fatal( "Bad stack after evaluation.\n"
"sp: %lx expected: %lx\n"
"Instruction %d(%s), num arg %d\n"
, (long)sp, (long)expected_stack
, instruction, get_f_name(instruction), num_arg);
}
if (sp < fp + csp->num_local_variables - 1)
{
fatal( "Bad stack after evaluation.\n"
"sp: %lx minimum expected: %lx\n"
"Instruction %d(%s), num arg %d\n"
, (long)sp, (long)(fp + csp->num_local_variables - 1)
, instruction, get_f_name(instruction), num_arg);
}
#endif /* DEBUG */
/* Execute the next instruction */
goto again;
/* Get rid of the handy but highly local macros */
# undef GET_NUM_ARG
# undef TYPE_TEST1
# undef TYPE_TEST2
# undef TYPE_TEST3
# undef TYPE_TEST4
# undef CASE
} /* eval_instruction() */
/*-------------------------------------------------------------------------*/
/* These macros are no longer needed:
*/
#undef push_malloced_string
#undef push_number
/*-------------------------------------------------------------------------*/
static Bool
apply_low (char *fun, object_t *ob, int num_arg, Bool b_ign_prot)
/* The low-level implementation of function calls.
*
* Call function <fun> in <ob>ject with <num_arg> arguments pushed
* onto the stack (<inter_sp> points to the last one). static and protected
* functions can't be called from the outside unless <b_ign_prot> is true.
* apply_low() takes care of calling shadows where necessary.
*
* When apply_low() returns true, the call was successful, the arguments
* one the stack have been popped and replaced with the result. But note
* that <ob> might have been destructed during the call.
*
* If apply_low() returns false, the function was not found and the arguments
* must be removed by the caller. One reason for failure can be an attempt
* to call an inherited function '::foo' with this function.
*
* To speed up the calls, apply_low() maintains a cache of earlier calls, both
* hits and misses.
*
* The function call will swap in the object and also unset its reset status.
*/
{
program_t *progp;
struct control_stack *save_csp;
p_int ix;
/* This object will now be used, and is thus a target for
* reset later on (when time due).
*/
ob->flags &= ~O_RESET_STATE;
#ifdef DEBUG
if (num_error > 2) {
fatal("apply_low with too many errors.\n");
goto failure;
}
#endif
/* If there is a chain of objects shadowing, start with the first
* of these.
*/
if (ob->flags & O_SHADOW)
{
object_t *shadow;
while (NULL != (shadow = O_GET_SHADOW(ob)->shadowed_by)
&& shadow != current_object)
{
ob = shadow;
}
}
retry_for_shadow:
ob->time_of_ref = current_time;
/* Load the object from swap */
if (ob->flags & O_SWAPPED)
{
if (load_ob_from_swap(ob) < 0)
error("Out of memory\n");
}
progp = ob->prog;
#ifdef DEBUG
if (ob->flags & O_DESTRUCTED)
fatal("apply() on destructed object '%s' function '%s'\n"
, ob->name != NULL ? ob->name : "<null>"
, fun != NULL ? fun : "<null>"
);
#endif
/* Lookup the shared string for <fun>. Function names are always
* shared, so if there is no shared string twin for <fun>, the
* called function does not exist anywhere. As a side effect,
* we get the shared string for <fun> which makes the cache-lookup
* much faster.
*/
fun = findstring(fun);
if (!fun)
goto failure2;
/* *fun is now guaranteed to be a shared string */
/* Get the hashed index into the cache */
ix =
( progp->id_number ^ (p_int)fun ^ ( (p_int)fun >> APPLY_CACHE_BITS ) )
& (CACHE_SIZE-1);
/* Check if we have an entry for this function call */
if (cache[ix].id == progp->id_number
&& (cache[ix].name == fun || !strcmp(cache[ix].name, fun))
)
{
/* We have found a matching entry in the cache. The contents have
* to match, not only the pointers, because cache entries for
* functions not existant in _this_ object <ob> are stored as
* separately allocated copy, not as another ref to the shared
* string. Yet they shall be found here.
*/
#ifdef APPLY_CACHE_STAT
apply_cache_hit++;
#endif
if (cache[ix].progp
/* Static functions may not be called from outside. */
&& ( !cache[ix].flags
|| b_ign_prot
|| ( !(cache[ix].flags & TYPE_MOD_PROTECTED)
&& current_object == ob))
)
{
/* the cache will tell us in wich program the function is, and
* where.
*/
fun_hdr_p funstart;
push_control_stack(inter_sp, inter_pc, inter_fp);
csp->ob = current_object;
csp->prev_ob = previous_ob;
csp->num_local_variables = num_arg;
csp->funstart = funstart = cache[ix].funstart;
current_prog = cache[ix].progp;
current_strings = current_prog->strings;
function_index_offset = cache[ix].function_index_offset;
#ifdef DEBUG
if (!ob->variables && cache[ix].variable_index_offset)
fatal("%s Fatal: apply (cached) for object %p '%s' "
"w/o variables, but offset %d\n"
, time_stamp(), ob, ob->name
, cache[ix].variable_index_offset);
#endif
current_variables = ob->variables;
if (current_variables)
current_variables += cache[ix].variable_index_offset;
inter_sp = setup_new_frame2(funstart, inter_sp, MY_FALSE, MY_FALSE);
previous_ob = current_object;
current_object = ob;
save_csp = csp;
eval_instruction(FUNCTION_CODE(funstart), inter_sp);
#ifdef DEBUG
if (save_csp-1 != csp)
fatal("Bad csp after execution in apply_low\n");
#endif
/* Arguments and local variables are now removed. One
* resulting value is always returned on the stack.
*/
return MY_TRUE;
}
/* when we come here, the cache has told us that the function isn't
* defined in the object
*/
}
else
{
/* we have to search the function */
char *shared_name;
#ifdef APPLY_CACHE_STAT
apply_cache_miss++;
#endif
shared_name = fun;
if ( NULL != shared_name)
{
int fx;
/* Yup, fun is a function _somewhere_ */
eval_cost++;
fx = find_function(shared_name, progp);
if (fx >= 0)
{
/* Found the function - setup the control stack and
* create a new cache entry.
*/
funflag_t flags;
fun_hdr_p funstart;
push_control_stack(inter_sp, inter_pc, inter_fp);
/* if an error occurs here, it won't leave the cache in an
* inconsistent state.
*/
csp->ob = current_object;
csp->prev_ob = previous_ob;
if (!cache[ix].progp)
{
/* The old cache entry was for an undefined function,
* so the name had to be malloced.
*/
xfree(cache[ix].name);
}
cache[ix].id = progp->id_number;
cache[ix].name = shared_name;
csp->num_local_variables = num_arg;
current_prog = progp;
flags = setup_new_frame1(fx, 0, 0);
current_strings = current_prog->strings;
cache[ix].progp = current_prog;
cache[ix].function_index_offset = function_index_offset;
cache[ix].variable_index_offset = variable_index_offset;
#ifdef DEBUG
if (!ob->variables && variable_index_offset)
fatal("%s Fatal: apply for object %p '%s' w/o variables, "
"but offset %d\n"
, time_stamp(), ob, ob->name, variable_index_offset);
#endif
current_variables = ob->variables;
if (current_variables)
current_variables += variable_index_offset;
funstart = current_prog->program + (flags & FUNSTART_MASK);
cache[ix].funstart = funstart;
cache[ix].flags = progp->functions[fx]
& (TYPE_MOD_STATIC|TYPE_MOD_PROTECTED);
/* Static functions may not be called from outside,
* Protected functions not even from the inside.
*/
if (0 != cache[ix].flags
&& ( (cache[ix].flags & TYPE_MOD_PROTECTED)
|| current_object != ob)
&& !b_ign_prot
)
{
/* Not found */
previous_ob = csp->prev_ob;
current_object = csp->ob;
pop_control_stack();
if (ob->flags & O_SHADOW && O_GET_SHADOW(ob)->shadowing)
{
/* This is an object shadowing another. The function
* was not found, but can maybe be found in the object
* we are shadowing.
*/
ob = O_GET_SHADOW(ob)->shadowing;
goto retry_for_shadow;
}
else
goto failure;
}
csp->funstart = funstart;
inter_sp = setup_new_frame2(funstart, inter_sp, MY_FALSE, MY_FALSE);
previous_ob = current_object;
current_object = ob;
save_csp = csp;
eval_instruction(FUNCTION_CODE(funstart), inter_sp);
#ifdef DEBUG
if (save_csp-1 != csp)
fatal("Bad csp after execution in apply_low\n");
#endif
/* Arguments and local variables are now removed. One
* resulting value is always returned on the stack.
*/
return MY_TRUE;
} /* end if (fx >= 0) */
} /* end if(shared_name) */
/* We have to mark this function as non-existant in this object. */
if (!cache[ix].progp)
{
/* The old cache entry was for an undefined function, so the
name had to be malloced */
xfree(cache[ix].name);
}
cache[ix].id = progp->id_number;
cache[ix].name = string_copy(fun);
cache[ix].progp = NULL;
}
/* At this point, the function was not found in the object. But
* maybe this object is a shadow and we find the function in the
* shadowed object.
*/
if (ob->flags & O_SHADOW && O_GET_SHADOW(ob)->shadowing)
{
ob = O_GET_SHADOW(ob)->shadowing;
goto retry_for_shadow;
}
failure:
if (fun[0] == ':')
error("Illegal function call\n");
failure2:
/* Failure. Deallocate stack. */
return MY_FALSE;
} /* apply_low() */
/*-------------------------------------------------------------------------*/
void
push_apply_value (void)
/* Push the current <apply_return_value> onto the stack, <apply_return_value>
* itself free afterwards.
*/
{
*++inter_sp = apply_return_value;
apply_return_value.type = T_NUMBER;
}
/*-------------------------------------------------------------------------*/
void
pop_apply_value (void)
/* Pop the current value on the stack into <apply_return_value>, after
* freeing the latter of course.
*/
{
free_svalue(&apply_return_value);
apply_return_value = *inter_sp--;
}
/*-------------------------------------------------------------------------*/
svalue_t *
sapply_int (char *fun, object_t *ob, int num_arg, Bool b_find_static)
/* Call function <fun> in <ob>ject with <num_arg> arguments pushed
* onto the stack (<inter_sp> points to the last one). static and protected
* functions can't be called from the outside unless <b_find_static> is true.
* sapply() takes care of calling shadows where necessary.
*
* sapply() returns a pointer to the function result when the call was
* successfull, or NULL on failure. The arguments are popped in any case.
* The result pointer, if returned, points to a static area which will be
* overwritten with the next sapply().
*
* The function call will swap in the object and also unset its reset status.
*
* interpret.h defines the macro sapply(fun,ob,num_arg) for the most
* common call with b_find_static passed as false.
*/
{
#ifdef DEBUG
svalue_t *expected_sp;
#endif
/* Handle tracing */
if (TRACEP(TRACE_APPLY) && TRACE_IS_INTERACTIVE())
{
if (!++traceing_recursion)
{
do_trace("Apply", "", "\n");
}
traceing_recursion--;
}
#ifdef DEBUG
expected_sp = inter_sp - num_arg;
#endif
/* Do the call */
if (!apply_low(fun, ob, num_arg, b_find_static))
{
inter_sp = _pop_n_elems(num_arg, inter_sp);
return NULL;
}
transfer_svalue(&apply_return_value, inter_sp);
inter_sp--;
#ifdef DEBUG
if (expected_sp != inter_sp)
fatal("Corrupt stack pointer.\n");
#endif
return &apply_return_value;
} /* sapply_int() */
/*-------------------------------------------------------------------------*/
svalue_t *
apply (char *fun, object_t *ob, int num_arg)
/* Call function <fun> in <ob>ject with <num_arg> arguments pushed
* onto the stack (<inter_sp> points to the last one). static and protected
* functions can't be called from the outside.
* apply() takes care of calling shadows where necessary.
*
* apply() returns a pointer to the function result when the call was
* successfull, or NULL on failure. The arguments are popped in any case.
* The result pointer, if returned, points to a static area which will be
* overwritten with the next apply().
*
* The function call will swap in the object and also unset its reset status.
*
* The big difference between apply() and sapply() is that apply() sets
* the tracedepth to 0 before calling the function.
*/
{
tracedepth = 0;
return sapply_int(fun, ob, num_arg, MY_FALSE);
}
/*-------------------------------------------------------------------------*/
static void
secure_apply_error ( svalue_t *save_sp, struct control_stack *save_csp
, Bool clear_costs)
/* Recover from an error during a secure apply. <save_sp> and <save_csp>
* are the saved evaluator stack and control stack pointers, saving the
* state from when secure_apply() was entered.
*
* The function pops all the arguments for the call from the stack, and
* then calls runtime_error() in the master object with the necessary
* information, unless it is a triple fault - in that case only a
* debug_message() is generated.
*
* If <clear_costs> is TRUE, the eval costs and limits will be reset
* before runtime_error() is called. This is used for top-level master
* applies which should behave like normal function calls in the error
* handling.
*/
{
if (csp != save_csp)
{
/* Could be error before push.
* We have to unroll the control stack in case it references
* lambda closures.
*/
while (csp > save_csp+1)
pop_control_stack();
previous_ob = csp->prev_ob;
current_object = csp->ob;
pop_control_stack();
}
inter_sp = _pop_n_elems (inter_sp - save_sp, inter_sp);
if (num_error == 3)
{
if (!out_of_memory)
{
debug_message("%s Master failure: %s", time_stamp(), current_error);
xfree(current_error);
xfree(current_error_file);
xfree(current_error_object_name);
if (current_error_trace)
{
free_array(current_error_trace);
current_error_trace = NULL;
}
if (uncaught_error_trace)
{
free_array(uncaught_error_trace);
uncaught_error_trace = NULL;
}
}
}
else if (!out_of_memory)
{
int a;
object_t *save_cmd;
push_malloced_string(current_error);
a = 1;
if (current_error_file)
{
push_malloced_string(current_error_file);
push_malloced_string(current_error_object_name);
push_number(current_error_line_number);
a += 3;
}
if (current_heart_beat)
{
/* Heartbeat error (unlikely though): turn off the heartbeat in
* the object and also pass it to RUNTIME_ERROR.
*/
object_t *culprit;
culprit = current_heart_beat;
current_heart_beat = NULL;
set_heart_beat(culprit, 0);
debug_message("%s Heart beat in %s turned off.\n"
, time_stamp(), culprit->name);
inter_sp = _push_valid_ob(culprit, inter_sp);
a++;
}
else
{
if (!current_error_file)
{
/* Pass dummy values */
push_number(0);
push_number(0);
push_number(0);
a += 3;
}
/* Normal error: push -1 instead of a culprit. */
push_number(-1);
a++;
}
if (clear_costs)
{
CLEAR_EVAL_COST;
RESET_LIMITS;
}
save_cmd = command_giver;
apply_master(STR_RUNTIME, a);
command_giver = save_cmd;
}
num_error--;
} /* secure_apply_error() */
/*-------------------------------------------------------------------------*/
svalue_t *
secure_apply (char *fun, object_t *ob, int num_arg)
/* Call function <fun> in <ob>ject with <num_arg> arguments pushed
* onto the stack (<inter_sp> points to the last one). static and protected
* functions can't be called from the outside.
* secure_apply() takes care of calling shadows where necessary.
*
* secure_apply() returns a pointer to the function result when the call was
* successfull, or NULL on failure. The arguments are popped in any case.
* The result pointer, if returned, points to a static area which will be
* overwritten with the next secure_apply().
*
* The function call will swap in the object and also unset its reset status.
*
* Errors during the execution are caught (this is the big difference
* to sapply()/apply()) and cause secure_apply() to return NULL.
*/
{
struct error_recovery_info error_recovery_info;
svalue_t *save_sp;
struct control_stack *save_csp;
svalue_t *result;
if (ob->flags & O_DESTRUCTED)
return NULL;
error_recovery_info.rt.last = rt_context;
error_recovery_info.rt.type = ERROR_RECOVERY_APPLY;
rt_context = (rt_context_t *)&error_recovery_info;
save_sp = inter_sp;
save_csp = csp;
if (setjmp(error_recovery_info.con.text))
{
secure_apply_error(save_sp - num_arg, save_csp, MY_FALSE);
result = NULL;
}
else
{
result = sapply(fun, ob, num_arg);
}
rt_context = error_recovery_info.rt.last;
return result;
} /* secure_apply() */
/*-------------------------------------------------------------------------*/
svalue_t *
apply_master_ob (char *fun, int num_arg, Bool external)
/* Aliases:
* apply_master(fun, num_arg) == apply_master_ob(fun, num_arg, FALSE)
* callback_master(fun, num_arg) == apply_master_ob(fun, num_arg, TRUE)
*
* Call function <fun> in the master object with <num_arg> arguments pushed
* onto the stack (<inter_sp> points to the last one). static and protected
* functions can be called from the outside. The function takes care
* of calling shadows where necessary.
*
* If <external> is TRUE, it means that this call is due to some external
* event (like an ERQ message) instead of being caused by a running program.
* The effect of this flag is that the error handling is like for a normal
* function call (clearing the eval costs before calling runtime_error()).
*
* apply_master_object() returns a pointer to the function result when the
* call was successfull, or NULL on failure. The arguments are popped in
* any case.
* The result pointer, if returned, points to a static area which will be
* overwritten with the next apply_master_object().
*
* The function makes sure that there is a master object to be called. If
* necessary, a new one is compiled or, failing that, an old one is
* reactivated.
*
* Errors during the execution are caught and case the function to
* return NULL.
*
* The function operates on an execution tick reserve of MASTER_RESERVED_COST
* which is used then the normal evaluation cost is already too high.
*/
{
static int eval_cost_reserve = MASTER_RESERVED_COST;
/* Available eval_cost reserver. If needed, the reserve is halved
* for the duration of the apply to establish a protection against
* an endless recursion of master calls.
*/
volatile Bool reserve_used = MY_FALSE;
struct error_recovery_info error_recovery_info;
svalue_t *save_sp;
struct control_stack *save_csp;
svalue_t *result;
/* Get the master object. */
assert_master_ob_loaded();
/* Tap into the eval_cost reserve if the end is near */
if ( (max_eval_cost && eval_cost > max_eval_cost - MASTER_RESERVED_COST)
&& eval_cost_reserve > 1)
{
eval_cost -= eval_cost_reserve;
assigned_eval_cost -= eval_cost_reserve;
eval_cost_reserve >>= 1;
reserve_used = MY_TRUE;
}
/* Setup the the error recovery and call the function */
error_recovery_info.rt.last = rt_context;
error_recovery_info.rt.type = ERROR_RECOVERY_APPLY;
rt_context = (rt_context_t *)&error_recovery_info;
save_sp = inter_sp;
save_csp = csp;
if (setjmp(error_recovery_info.con.text))
{
secure_apply_error(save_sp - num_arg, save_csp, external);
printf("%s Error in master_ob->%s()\n", time_stamp(), fun);
debug_message("%s Error in master_ob->%s()\n", time_stamp(), fun);
result = NULL;
}
else
{
result = sapply_int(fun, master_ob, num_arg, MY_TRUE);
}
/* Free the reserve if we used it */
if (reserve_used)
{
eval_cost_reserve <<= 1;
assigned_eval_cost = eval_cost += eval_cost_reserve;
}
rt_context = error_recovery_info.rt.last;
return result;
} /* apply_master_ob() */
/*-------------------------------------------------------------------------*/
void
assert_master_ob_loaded (void)
/* Make sure that there is a master object <master_ob>.
* If necessary, a new master is compiled, or, failing that, an old
* destructed one is reactivated. If everything fails, the driver exits.
*
* Note that the function may be called recursively:
* - While calling a master function from yyparse() (e.g. log_error()),
* the master self-destructs and then causes an error.
* - Another possibility is that some driver hook invokes some
* function that uses apply_master_ob().
* - The master object might have been reloaded without noticing that
* it is the master. This could happen when there already was a call to
* assert_master_ob_loaded(), clearing master_ob, and the master
* inherits itself. Partial working self-inheritance is possible if
* the H_INCLUDE_DIRS hook does something strange.
*/
{
static Bool inside = MY_FALSE;
/* Flag to notice recursive calls */
static object_t *destructed_master_ob = NULL;
/* Old, destructed master object */
int i;
if (!master_ob || master_ob->flags & O_DESTRUCTED)
{
/* The master object has been destructed. Free our reference,
* and load a new one.
*/
if (inside || !master_ob)
{
object_t *ob;
object_t *prev;
Bool newly_removed = MY_FALSE;
/* TRUE if the old master was on the list of newly
* destructed objects. That is important to know
* because then it still has all its variables.
*/
/* A recursive call while loading the master, or there
* was no master to begin with.
* If there is a destructed master, reactivate that
* one, else stop the driver.
*/
if (!destructed_master_ob)
{
fprintf(stderr, "%s Failed to load master object '%s'.\n"
, time_stamp(), master_name);
add_message("Failed to load master object '%s'!\n"
, master_name);
exit(1);
}
/* If we come here, we had a destructed master and failed
* to load a new one. Now try to reactivate the
* old one again.
*
* We don't have to reactivate any destructed inherits, though:
* as long as the master references their programs, that's all
* we need.
*/
/* First, make sure that there is no half-done object
* using the masters name.
*/
if ( NULL != (ob = find_object(master_name)) )
{
destruct(ob);
}
/* Get the destructed master */
ob = destructed_master_ob;
destructed_master_ob = NULL;
/* Remove the destructed master from the list
* of newly destructed objects or destructed objects.
*/
if (newly_destructed_objs != NULL)
{
if (ob == newly_destructed_objs)
{
newly_destructed_objs = ob->next_all;
newly_removed = MY_TRUE;
num_newly_destructed--;
}
else
{
for ( prev = newly_destructed_objs
; prev && prev->next_all != ob
; prev = prev->next_all
) NOOP;
if (prev)
{
prev->next_all = ob->next_all;
newly_removed = MY_TRUE;
num_newly_destructed--;
}
}
}
if (!newly_removed && destructed_objs != NULL)
{
if (ob == destructed_objs)
{
destructed_objs = ob->next_all;
if (destructed_objs)
destructed_objs->prev_all = NULL;
num_destructed--;
}
else
{
for ( prev = destructed_objs
; prev && prev->next_all != ob
; prev = prev->next_all
) NOOP;
if (prev)
{
prev->next_all = ob->next_all;
if (prev->next_all)
prev->next_all->prev_all = prev;
num_destructed--;
}
}
}
ob->flags &= ~O_DESTRUCTED;
/* Restore the old masters variable space.
* Remember: as long as the objects are in the 'newly destructed'
* list, they still have all variables.
*/
if (!newly_removed && ob->prog->num_variables)
{
int save_privilege = malloc_privilege;
int j;
svalue_t *v;
malloc_privilege = MALLOC_SYSTEM;
ob->variables = v = (svalue_t *)
xalloc(sizeof *v * ob->prog->num_variables);
malloc_privilege = save_privilege;
for (j = ob->prog->num_variables; --j >= 0; )
*v++ = const0;
}
/* Reenter the object into the various lists */
enter_object_hash(ob);
ob->next_all = obj_list;
ob->prev_all = NULL;
if (obj_list)
obj_list->prev_all = ob;
obj_list = ob;
if (!obj_list_end)
obj_list_end = ob;
num_listed_objs++;
ob->super = NULL;
ob->contains = NULL;
ob->next_inv = NULL;
/* Reactivate the old master */
master_ob = ref_object(ob, "assert_master_ob_loaded");
if (current_object == &dummy_current_object_for_loads)
current_object = master_ob;
push_number(newly_removed);
sapply_int(STR_REACTIVATE, ob, 1, MY_TRUE);
push_number(2 - (newly_removed ? 1 : 0));
sapply_int(STR_INAUGURATE, ob, 1, MY_TRUE);
fprintf(stderr, "%s Old master reactivated.\n", time_stamp());
inside = MY_FALSE;
return;
} /* if (inside || !master_obj) */
/* A normal call to assert_master_ob_loaded: just load a new one */
fprintf(stderr, "%s assert_master_ob_loaded: Reloading master '%s'\n"
, time_stamp(), master_name);
destructed_master_ob = master_ob;
/* Clear the pointer, in case the load failed.
*/
master_ob = NULL;
inside = MY_TRUE;
if (!current_object)
{
current_object = &dummy_current_object_for_loads;
}
#ifdef USE_FREE_CLOSURE_HOOK
/* don't free the closure hooks now since they might be
* still in use - the backend will take care of them.
*/
free_closure_hooks(driver_hook, NUM_DRIVER_HOOKS);
for (i = NUM_DRIVER_HOOKS; i--;)
driver_hook[i] = const0;
#else
/* Free the closure hooks.
*/
for (i = NUM_DRIVER_HOOKS; i--;)
{
/* The object reference in bound closures is not counted! */
if (driver_hook[i].type == T_CLOSURE &&
driver_hook[i].x.closure_type == CLOSURE_LAMBDA)
{
driver_hook[i].x.closure_type = CLOSURE_UNBOUND_LAMBDA;
}
assign_svalue(driver_hook+i, &const0);
}
#endif
init_telopts();
master_ob = get_object(master_name);
if (current_object == &dummy_current_object_for_loads)
{
/* This might be due to the above assignment, or to setting
* it in the backend.
*/
current_object = master_ob;
}
initialize_master_uid();
push_number(3);
apply_master(STR_INAUGURATE, 1);
assert_master_ob_loaded();
/* ...in case inaugurate_master() destructed this object again */
inside = MY_FALSE;
ref_object(master_ob, "assert_master_ob_loaded");
if (destructed_master_ob)
free_object(destructed_master_ob, "assert_master_ob_loaded");
fprintf(stderr, "%s Reloading done.\n", time_stamp());
}
/* Master exists. Nothing to see here, move along... */
} /* assert_master_ob_loaded() */
/*-------------------------------------------------------------------------*/
void
call_lambda (svalue_t *lsvp, int num_arg)
/* Call the closure <lsvp> with <num_arg> arguments on the stack. On
* success, the arguments are replaced with the result, else an error()
* is generated.
*/
{
# define CLEAN_CSP \
previous_ob = csp->prev_ob; \
current_object = csp->ob; \
pop_control_stack();
/* Macro to undo all the call preparations in case the closure
* can't be called after all.
*/
svalue_t *sp;
lambda_t *l = lsvp->u.lambda;
sp = inter_sp;
/* Basic setup for the new control frame.
* If the closure can't be called, all this has to be undone
* using the macro CLEAN_CSP.
*/
push_control_stack(sp, inter_pc, inter_fp);
csp->ob = current_object;
csp->prev_ob = previous_ob;
csp->num_local_variables = num_arg;
previous_ob = current_object;
switch(lsvp->x.closure_type)
{
case CLOSURE_LFUN: /* --- lfun closure --- */
{
funflag_t flags;
fun_hdr_p funstart;
/* Can't call from a destructed object */
if (l->ob->flags & O_DESTRUCTED)
{
/* inter_sp == sp */
CLEAN_CSP
error("Object '%s' the closure was bound to has been "
"destructed\n", l->ob->name);
/* NOTREACHED */
return;
}
/* Reference the object the lfun is bound to */
l->ob->time_of_ref = current_time;
l->ob->flags &= ~O_RESET_STATE;
current_object = l->ob;
/* Make the object resident */
if (current_object->flags & O_SWAPPED
&& load_ob_from_swap(current_object) < 0)
{
/* inter_sp == sp */
CLEAN_CSP
error("Out of memory\n");
/* NOTREACHED */
return;
}
#ifdef DEBUG
if (l->function.index >= current_object->prog->num_functions)
fatal("Calling non-existing lfun closure #%hu in program '%s' "
"with %hu functions.\n"
, l->function.index
, current_object->prog->name
, current_object->prog->num_functions
);
#endif
/* Ok, object and program are there */
current_prog = current_object->prog;
/* inter_sp == sp */
flags = setup_new_frame(l->function.index);
funstart = current_prog->program + (flags & FUNSTART_MASK);
csp->funstart = funstart;
eval_instruction(FUNCTION_CODE(funstart), inter_sp);
/* The result is on the stack (inter_sp) */
return;
}
case CLOSURE_ALIEN_LFUN: /* --- alien lfun closure --- */
{
funflag_t flags;
fun_hdr_p funstart;
/* Can't call from a destructed object */
if (l->ob->flags & O_DESTRUCTED)
{
/* inter_sp == sp */
CLEAN_CSP
error("Object '%s' the closure was bound to has been "
"destructed\n", l->ob->name);
/* NOTREACHED */
return;
}
/* Reference the bound and the originating object */
l->ob->time_of_ref = current_time;
l->function.alien.ob->time_of_ref = current_time;
l->function.alien.ob->flags &= ~O_RESET_STATE;
current_object = l->ob;
/* Can't call a function in a destructed object */
if (l->function.alien.ob->flags & O_DESTRUCTED)
{
/* inter_sp == sp */
CLEAN_CSP
error("Object '%s' holding the closure has been "
"destructed\n", l->function.alien.ob->name);
/* NOTREACHED */
return;
}
/* Make the objects resident */
if ( ( current_object->flags & O_SWAPPED
&& load_ob_from_swap(current_object) < 0)
|| ( l->function.alien.ob->flags & O_SWAPPED
&& load_ob_from_swap(l->function.alien.ob) < 0)
)
{
/* inter_sp == sp */
CLEAN_CSP
error("Out of memory\n");
/* NOTREACHED */
return;
}
#ifdef DEBUG
if (l->function.alien.index >= l->function.alien.ob->prog->num_functions)
fatal("Calling non-existing lfun closure #%hu in program '%s' "
"with %hu functions.\n"
, l->function.alien.index
, l->function.alien.ob->prog->name
, l->function.alien.ob->prog->num_functions
);
#endif
/* If the object creating the closure wasn't the one in which
* it will be executed, we need to record the fact in a second
* 'dummy' control frame. If we didn't, major security holes
* open up.
*/
if (l->ob != l->function.alien.ob)
{
csp->extern_call = MY_TRUE;
csp->funstart = NULL;
push_control_stack(sp, 0, inter_fp);
csp->ob = current_object;
csp->prev_ob = previous_ob;
csp->num_local_variables = num_arg;
previous_ob = current_object;
}
/* Finish the setup of the control frame.
* This is a real inter-object call.
*/
csp->extern_call = MY_TRUE;
current_object = l->function.alien.ob;
current_prog = current_object->prog;
/* inter_sp == sp */
flags = setup_new_frame(l->function.alien.index);
funstart = current_prog->program + (flags & FUNSTART_MASK);
csp->funstart = funstart;
eval_instruction(FUNCTION_CODE(funstart), inter_sp);
/* If necessary, remove the second control frame */
if (l->ob != l->function.alien.ob)
{
current_object = csp->ob;
previous_ob = csp->prev_ob;
pop_control_stack();
}
/* The result is on the stack (inter_sp) */
return;
}
case CLOSURE_IDENTIFIER: /* --- variable closure --- */
{
short i; /* the signed variant of lambda_t->function.index */
CLEAN_CSP /* no call will be done */
if (num_arg)
error("Arguments passed to variable closure.\n");
/* Don't use variables in a destructed object */
if (l->ob->flags & O_DESTRUCTED)
{
error("Object '%s' the closure was bound to has been destructed\n"
, l->ob->name);
/* NOTREACHED */
return;
}
/* Make the object resident */
if ( (l->ob->flags & O_SWAPPED)
&& load_ob_from_swap(l->ob) < 0
)
{
error("Out of memory.\n");
/* NOTREACHED */
return;
}
/* Do we have the variable? */
if ( (i = (short)l->function.index) < 0)
{
error("Variable not inherited\n");
/* NOTREACHED */
return;
}
l->ob->time_of_ref = current_time;
#ifdef DEBUG
if (!l->ob->variables)
fatal("%s Fatal: call_lambda on variable for object %p '%s' "
"w/o variables, index %d\n"
, time_stamp(), l->ob, l->ob->name, i);
#endif
assign_svalue_no_free(++sp, &l->ob->variables[i]);
inter_sp = sp;
return;
}
case CLOSURE_BOUND_LAMBDA: /* --- bound lambda closure --- */
{
lambda_t *l2;
/* Deref the closure and then treat the resulting unbound
* lambda like a normal lambda
*/
l2 = l->function.lambda;
l2->ob = l->ob;
l = l2;
}
/* FALLTHROUGH */
case CLOSURE_LAMBDA:
{
fun_hdr_p funstart;
/* Can't call from a destructed object */
if (l->ob->flags & O_DESTRUCTED)
{
/* inter_sp == sp */
CLEAN_CSP
error("Object '%s' the closure was bound to has been "
"destructed\n", l->ob->name);
/* NOTREACHED */
return;
}
current_object = l->ob;
/* Make the object resident */
if (current_object->flags & O_SWAPPED
&& load_ob_from_swap(current_object) < 0)
{
/* inter_sp == sp */
CLEAN_CSP
error("Out of memory\n");
/* NOTREACHED */
return;
}
/* Reference the object */
current_object->time_of_ref = current_time;
current_object->flags &= ~O_RESET_STATE;
/* Finish the setup */
current_prog = current_object->prog;
current_lambda = *lsvp; addref_closure(lsvp, "call_lambda");
variable_index_offset = 0;
function_index_offset = 0;
funstart = l->function.code + 1;
csp->funstart = funstart;
sp = setup_new_frame2(funstart, sp, MY_FALSE, MY_TRUE);
current_variables = current_object->variables;
current_strings = current_prog->strings;
eval_instruction(FUNCTION_CODE(funstart), sp);
/* The result is on the stack (inter_sp) */
return;
}
case CLOSURE_UNBOUND_LAMBDA:
case CLOSURE_PRELIMINARY:
/* no valid current_object ==> pop the control stack */
/* inter_sp == sp */
CLEAN_CSP
break;
default: /* --- efun-, simul efun-, operator closure */
{
int i; /* the closure type */
current_object = lsvp->u.ob;
/* Can't call from a destructed object */
if (current_object->flags & O_DESTRUCTED)
{
/* inter_sp == sp */
CLEAN_CSP
error("Object '%s' the closure was bound to has been "
"destructed\n", current_object->name);
/* NOTREACHED */
return;
}
/* Make the object resident */
if (current_object->flags & O_SWAPPED
&& load_ob_from_swap(current_object) < 0)
{
/* inter_sp == sp */
CLEAN_CSP
error("Out of memory\n");
/* NOTREACHED */
return;
}
/* Reference the object */
current_object->time_of_ref = current_time;
i = lsvp->x.closure_type;
if (i < CLOSURE_SIMUL_EFUN)
{
/* It's an operator or efun */
if (i == CLOSURE_EFUN + F_UNDEF)
{
/* The closure was discovered to be bound to a destructed
* object and thus disabled.
*/
CLEAN_CSP
error("Object the closure was bound to has been destructed\n");
/* NOTREACHED */
return;
}
i -= CLOSURE_EFUN;
/* Efuns have now a positive value, operators a negative one.
*/
if (i >= 0
|| instrs[i -= CLOSURE_OPERATOR-CLOSURE_EFUN].min_arg)
{
/* To call an operator or efun, we have to construct
* a small piece of program with this instruction.
*/
bytecode_t code[5]; /* the code fragment */
bytecode_p p; /* the code pointer */
int min, max, def;
min = instrs[i].min_arg;
max = instrs[i].max_arg;
p = code;
/* Fix up the number of arguments passed */
if (num_arg < min)
{
/* Add some arguments */
int f;
if (num_arg == min-1
&& 0 != (def = instrs[i].Default) && def != -1)
{
/* We lack one argument for which a default
* is provided.
*/
*p++ = (bytecode_t)(def);
max--;
min--;
}
else
{
/* Maybe there is a fitting replacement efun */
f = proxy_efun(i, num_arg);
if (f >= 0)
/* Yup, use that one */
i = f;
else
{
/* Nope. */
csp->extern_call = MY_TRUE;
inter_pc = csp->funstart = EFUN_FUNSTART;
csp->instruction = i;
error("Too few arguments to %s\n", instrs[i].name);
}
}
}
else if (num_arg > 0xff || (num_arg > max && max != -1))
{
csp->extern_call = MY_TRUE;
inter_pc = csp->funstart = EFUN_FUNSTART;
csp->instruction = i;
error("Too many arguments to %s\n", instrs[i].name);
}
/* Store the instruction code */
if (i > 0xff)
*p++ = (bytecode_t)(i >> F_ESCAPE_BITS);
*p++ = (bytecode_t)i;
/* Store the <nargs> code, if necessary */
if (min != max)
*p++ = (bytecode_t)num_arg;
/* And finally the return instruction */
if ( instrs[i].ret_type == TYPE_VOID )
*p++ = F_RETURN0;
else
*p++ = F_RETURN;
csp->instruction = i;
csp->funstart = EFUN_FUNSTART;
csp->num_local_variables = 0;
inter_fp = sp - num_arg + 1;
eval_instruction(code, sp);
/* The result is on the stack (inter_sp) */
return;
}
else
{
/* It is an operator or syntactic marker: fall through
* to uncallable closure type.
*/
break;
}
}
else
{
/* simul_efun */
object_t *ob;
/* Mark the call as sefun closure */
inter_pc = csp->funstart = SIMUL_EFUN_FUNSTART;
/* Get the simul_efun object */
if ( !(ob = simul_efun_object) )
{
/* inter_sp == sp */
if ( !(ob = get_simul_efun_object()) ) {
csp->extern_call = MY_TRUE;
error("Couldn't load simul_efun object\n");
/* NOTREACHED */
return;
}
}
call_simul_efun(i - CLOSURE_SIMUL_EFUN, ob, num_arg);
CLEAN_CSP
}
/* The result is on the stack (inter_sp) */
return;
}
}
error("Uncallable closure\n");
/* NOTREACHED */
return;
# undef CLEAN_CSP
} /* call_lambda() */
/*-------------------------------------------------------------------------*/
svalue_t *
secure_call_lambda (svalue_t *closure, int num_arg)
/* Call the closure <closure> with <num_arg> arguments on the stack.
* On success, the functions returns a pointer to the result in the
* global apply_return_value, on failure it returns NULL. The arguments are
* removed in either case.
*
* This error recovery is the difference to call_lambda().
*/
{
struct error_recovery_info error_recovery_info;
svalue_t *save_sp;
struct control_stack *save_csp;
svalue_t *result;
error_recovery_info.rt.last = rt_context;
error_recovery_info.rt.type = ERROR_RECOVERY_APPLY;
rt_context = (rt_context_t *)&error_recovery_info;
save_sp = inter_sp;
save_csp = csp;
if (setjmp(error_recovery_info.con.text))
{
secure_apply_error(save_sp - num_arg, save_csp, MY_FALSE);
result = NULL;
}
else
{
call_lambda(closure, num_arg);
transfer_svalue((result = &apply_return_value), inter_sp);
inter_sp--;
}
rt_context = error_recovery_info.rt.last;
return result;
} /* secure_call_lambda() */
/*-------------------------------------------------------------------------*/
static void
call_simul_efun (int code, object_t *ob, int num_arg)
/* Call the simul_efun <code> in the sefun object <ob> with <num_arg>
* arguments on the stack. If it can't be found in the <ob>ject, the
* function queries the auxiliary sefun objects in <simul_efun_vector>.
*
* The function is looked up in the objects by name because its original
* entry in the simul_efun_table[] has been marked as "discarded".
*
* Leave the result on the stack on return.
*/
{
char *function_name;
function_name = simul_efunp[code].name;
/* First, try calling the function in the given object */
if (!apply_low(function_name, ob, num_arg, MY_FALSE))
{
/* Function not found: try the alternative sefun objects */
if (simul_efun_vector)
{
long i;
svalue_t *v;
i = (long)VEC_SIZE(simul_efun_vector);
for (v = simul_efun_vector->item+1; MY_TRUE; v++)
{
if (--i <= 0 || v->type != T_STRING)
{
error("Calling a vanished simul_efun\n");
return;
}
if ( !(ob = get_object(v->u.string)) )
continue;
if (apply_low(function_name, ob, num_arg, MY_FALSE))
return;
}
return;
}
error("Calling a vanished simul_efun\n");
return;
}
/*
* The result of the function call is on the stack.
*/
} /* call_simul_efun() */
/*-------------------------------------------------------------------------*/
char *
function_exists (char *fun, object_t *ob)
/* Search for the function <fun> in the object <ob>. If existing, return
* the name of the program, if not return NULL.
*
* Visibility rules apply: static and protected functions can't be
* found from the outside.
*/
{
char *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
/* Make the program resident */
if (O_PROG_SWAPPED(ob))
{
ob->time_of_ref = current_time;
if (load_ob_from_swap(ob) < 0)
error("Out of memory\n");
}
shared_name = findstring(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];
if (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);
/* And after all this, the function may be undefined */
if (FUNCTION_CODE(funstart)[0] == F_ESCAPE
&& FUNCTION_CODE(funstart)[1] == F_UNDEF - 0x100)
{
return NULL;
}
/* We got it. */
return progp->name;
} /* function_exists() */
/*-------------------------------------------------------------------------*/
void
call_function (program_t *progp, int fx)
/* Call the function <fx> in program <progp> for the current_object.
* This is done with no frame set up. No arguments are passed,
* returned values are removed.
*
* Right now this function is used just for heartbeats, and the
* way of calling prevents shadows from being called.
*/
{
funflag_t flags;
fun_hdr_p funstart;
push_control_stack(inter_sp, inter_pc, inter_fp);
csp->ob = current_object;
csp->prev_ob = previous_ob;
#ifdef DEBUG
if (csp != CONTROL_STACK)
fatal("call_function with bad csp\n");
#endif
csp->num_local_variables = 0;
current_prog = progp;
flags = setup_new_frame(fx);
funstart = current_prog->program + (flags & FUNSTART_MASK);
csp->funstart = funstart;
previous_ob = current_object;
tracedepth = 0;
eval_instruction(FUNCTION_CODE(funstart), inter_sp);
free_svalue(inter_sp--); /* Throw away the returned result */
} /* call_function() */
/*-------------------------------------------------------------------------*/
int
get_line_number (bytecode_p p, program_t *progp, char **namep)
/* Look up the line number for address <p> within the program <progp>.
* Result is the line number, and *<namep> is set to the name of the
* source resp. include file.
*
* If the code was generated from an included file, and if the name lengths
* allow it, the returned name is "<program name> (<include filename>)".
* In this case, the returned *<namep> points to a static buffer.
*
* TODO: (an old comment which might no longer be true): This can be done
* TODO:: much more efficiently, but that change has low priority.)
*/
{
/* Datastructure to keep track of included files */
struct incinfo
{
char *name; /* Name of parent file */
struct incinfo *super; /* Pointer to parent entry */
int super_line; /* Line number within parent file */
};
int offset; /* (Remaining) program offset to resolve */
int i; /* Current line number */
include_t *includes; /* Pointer to the next include info */
struct incinfo *inctop = NULL; /* The include information stack. */
int relocated_from = 0;
int relocated_to = -1;
Bool used_system_mem;
/* TRUE if the line numbers needed SYSTEM privilege to be swapped in,
* because this means that afterwards they need to be deallocated
* again.
*/
if (!progp || !p)
{
*namep = "UNDEFINED";
return 0;
}
used_system_mem = MY_FALSE;
/* Get the line numbers */
if (!progp->line_numbers)
{
if (!load_line_numbers_from_swap(progp))
{
/* Uhhmm, out of memory - try to pull some rank */
int save_privilege;
Bool rc;
used_system_mem = MY_TRUE;
save_privilege = malloc_privilege;
malloc_privilege = MALLOC_SYSTEM;
rc = load_line_numbers_from_swap(progp);
malloc_privilege = save_privilege;
if (!rc)
{
*namep = "UNDEFINED";
return 0;
}
}
}
/* Get the offset within the program */
offset = (int)(p - progp->program);
if (p < progp->program || p > PROGRAM_END(*progp))
{
printf("%s get_line_number(): Illegal offset %d in object %s\n"
, time_stamp(), offset, progp->name);
debug_message("%s get_line_number(): Illegal offset %d in object %s\n"
, time_stamp(), offset, progp->name);
*namep = "UNDEFINED";
return 0;
}
includes = progp->includes;
/* Decode the line number information until the line number
* for offset is found. We do this by reading the line byte codes,
* counting up the line number <i> while decrementing the <offset>.
* If the offset becomes <= 0, we found the line.
*/
for (i = 0, p = progp->line_numbers->line_numbers; ; )
{
int o;
o = GET_CODE(p);
if (o <= 63) /* 0x00..0x3F */
{
if (o >= LI_MAXOFFSET) /* 0x3b..0x3f */
{
if (o != LI_MAXOFFSET)
{
switch (o)
{
case LI_BACK:
{
unsigned int off;
p++;
off = GET_CODE(p);
i -= off+1;
break;
}
case LI_INCLUDE:
{
/* Included file: push the information */
struct incinfo *inc_new;
/* Find the next include which generated code.
* We know that there is one.
*/
while (includes->depth < 0) includes++;
i++;
inc_new = xalloc(sizeof *inc_new);
/* TODO: What if this fails? */
inc_new->name = includes->filename;
includes++;
inc_new->super = inctop;
inc_new->super_line = i;
inctop = inc_new;
i = 0;
break;
}
case LI_INCLUDE_END:
{
/* End of include: retrieve old position */
struct incinfo *inc_old;
inc_old = inctop;
i = inc_old->super_line;
inctop = inc_old->super;
xfree(inc_old );
break;
}
case LI_L_RELOCATED:
{
int h, l;
p++;
h = GET_CODE(p);
p++;
l = GET_CODE(p);
i -= 2;
relocated_to = i;
relocated_from = relocated_to - ((h << 8) + l);
p++; /* skip trailing LI_L_RELOCATED */
break;
}
}
}
else /* 0x3c */
{
offset -= o;
}
}
else /* 0x00..0x3b */
{
offset -= o;
i++;
if (offset <= 0)
break;
}
}
else if (o <= 127) /* 0x40..0x7f */
{
/* Simple entry: count offset and lines */
offset -= (o&7) + 1;
i += (o>>3) - 6;
if (offset <= 0)
break;
}
else if (o >= 256-LI_MAXEMPTY) /* 0xE0 .. 0xFF */
{
i += 256-o;
}
else /* 0x80 .. 0xDF */
{
i -= 2;
relocated_from = (relocated_to = i) - (o - LI_RELOCATED);
}
/* Get the next line number bytecode */
p++;
} /* line number search */
if (i == relocated_to + 1)
i = relocated_from + 1;
/* Perform the announced relocation */
/* Here, i is the line number, and if inctop is not NULL, the
* code originates from the included file pointed to by inctop.
* In either case, set *<namep> to the pointer to the name
* of the file.
*/
if (inctop)
{
/* The code was included */
static char namebuf[80];
*namep = inctop->name;
if (strlen(*namep) + strlen(progp->name) < sizeof(namebuf) - 3)
{
sprintf(namebuf, "%s (%s)", progp->name, *namep);
*namep = namebuf;
}
else
*namep = "<program names too long>";
/* Free the include stack structures */
do {
struct incinfo *inc_old;
inc_old = inctop;
inctop = inc_old->super;
xfree(inc_old);
} while (inctop);
}
else
{
/* Normal code */
*namep = progp->name;
}
if (used_system_mem)
{
/* We used SYSTEM priviledged memory - now we have to return it.
*/
total_prog_block_size -= progp->line_numbers->size;
total_bytes_unswapped -= progp->line_numbers->size;
xfree(progp->line_numbers);
progp->line_numbers = NULL;
reallocate_reserved_areas();
}
/* Return the line number */
return i;
} /* get_line_number() */
/*-------------------------------------------------------------------------*/
int
get_line_number_if_any (char **name)
/* Look up the line number for the current execution address.
* Result is the line number, and *<namep> is set to the name of the
* source resp. include file.
*
* The function recognizes sefun and lambda closures, the latter return
* the approximate position offset of the offending instruction within
* the closure.
*
* *<namep> may point to a static buffer.
*/
{
if (csp >= &CONTROL_STACK[0] && csp->funstart == SIMUL_EFUN_FUNSTART)
{
*name = "<simul_efun closure>";
return 0;
}
if (csp >= &CONTROL_STACK[0] && csp->funstart == EFUN_FUNSTART)
{
static char buf[256];
char *iname;
iname = instrs[csp->instruction].name;
if (iname)
{
buf[sizeof buf - 1] = '\0';
buf[0] = '#';
buf[1] = '\'';
strcpy(buf+2, iname);
if (buf[sizeof buf - 1] != '\0')
fatal("interpret:get_line_number_if_any(): "
"buffer overflow.\n");
iname = buf;
}
else
iname = "<efun closure>";
*name = iname;
return 0;
}
if (current_prog)
{
if (csp->funstart < current_prog->program
|| csp->funstart > PROGRAM_END(*current_prog))
{
static char name_buffer[24];
sprintf(name_buffer, "<lambda 0x%6lx>", (long)csp->funstart);
*name = name_buffer;
return inter_pc - csp->funstart - 2;
}
return get_line_number(inter_pc, current_prog, name);
}
*name = "";
return 0;
}
/*-------------------------------------------------------------------------*/
char *
collect_trace (strbuf_t * sbuf, vector_t ** rvec )
/* Collect the traceback for the current (resp. last) function call, starting
* from the first frame.
*
* If <sbuf> is not NULL, traceback is written in readable form into the
* stringbuffer <sbuf>.
*
* If <rvec> is not NULL, the traceback is returned in a newly created array
* which pointer is put into *<rvec>. For the format of the array, see
* efun debug_info().
*
* If a heart_beat() is involved, return a pointer to the name of the object
* that had it, otherwise return NULL.
*/
{
struct control_stack *p; /* Control frame under inspection */
char *ret = NULL;
bytecode_p pc = inter_pc;
int line = 0;
char *name;
char *file;
object_t *ob = NULL;
bytecode_p last_catch = NULL; /* Last found catch */
/* Temporary structure to hold the tracedata before it is condensed
* into the result array.
*/
struct traceentry {
vector_t * vec;
struct traceentry * next;
} *first_entry, *last_entry;
size_t num_entries;
#define NEW_ENTRY(var, type, progname) \
struct traceentry * var; \
var = alloca(sizeof(*var)); \
if (!var) \
error("Stack overflow in collect_trace()"); \
var->vec = allocate_array(TRACE_MAX); \
var->next = NULL; \
if (!first_entry) \
first_entry = last_entry = var; \
else { \
last_entry->next = var; \
last_entry = var; \
} \
num_entries++; \
put_number(var->vec->item+TRACE_TYPE, type); \
put_malloced_string(var->vec->item+TRACE_PROGRAM, string_copy(progname)); \
put_malloced_string(entry->vec->item+TRACE_OBJECT, string_copy(ob->name));
#define PUT_LOC(entry, val) \
put_number(entry->vec->item+TRACE_LOC, (p_int)(val))
first_entry = last_entry = NULL;
num_entries = 0;
if (!current_prog)
{
if (sbuf)
strbuf_addf(sbuf, "%s\n", STR_NO_PROG_TRACE);
if (rvec)
{
vector_t * vec;
vec = allocate_array(1);
put_ref_string(vec->item, STR_NO_PROG_TRACE);
*rvec = vec;
}
return NULL;
}
if (csp < &CONTROL_STACK[0])
{
if (sbuf)
strbuf_addf(sbuf, "%s\n", STR_NO_TRACE);
if (rvec)
{
vector_t * vec;
vec = allocate_array(1);
put_ref_string(vec->item, STR_NO_TRACE);
*rvec = vec;
}
return NULL;
}
/* Loop through the call stack.
* The organisation of the control stack results in the information
* for this frame (p[0]) being stored in the next (p[1]).
* Confused now? Good.
*/
p = &CONTROL_STACK[0];
do {
bytecode_p dump_pc; /* the frame's pc */
program_t *prog; /* the frame's program */
if (p->extern_call)
{
/* Find the next extern_call and set <ob> to the
* then-current object for all the coming frames.
*/
struct control_stack *q = p;
for (;;) {
if (++q > csp)
{
ob = current_object;
break;
}
if (q->extern_call)
{
ob = q->ob;
break;
}
}
last_catch = NULL;
}
/* Retrieve pc and program from the stack */
if (p == csp)
{
dump_pc = pc;
prog = current_prog;
}
else
{
dump_pc = p[1].pc;
prog = p[1].prog;
}
/* Use some heuristics first to see if it could possibly be a CATCH.
* The pc should point at a F_END_CATCH instruction, or at a LBRANCH
* to that instruction.
*/
if (p > &CONTROL_STACK[0] && p->funstart == p[-1].funstart)
{
bytecode_p pc2 = p->pc;
if (!pc2)
goto not_catch; /* shouldn't happen... */
if (GET_CODE(pc2) == F_LBRANCH)
{
short offset;
pc2++;
GET_SHORT(offset, pc2);
if (offset <= 0)
goto not_catch;
pc2 += offset;
}
#if F_END_CATCH >= 0x100
if (pc2 - FUNCTION_CODE(p->funstart) < 2)
goto not_catch;
if (GET_CODE(pc2-2) != F_ESCAPE
|| GET_CODE(pc2-1) != F_END_CATCH-0x100)
{
goto not_catch;
}
#else
if (pc2 - FUNCTION_CODE(p->funstart) < 1)
goto not_catch;
if (GET_CODE(pc2-1) != F_END_CATCH)
{
goto not_catch;
}
#endif
if (last_catch == pc2)
goto not_catch;
last_catch = pc2;
name = "CATCH";
file = NULL;
line = 0;
goto name_computed;
}
not_catch: /* The frame does not point at a catch here */
/* Efun symbol? */
if (!prog || !dump_pc)
{
/* TODO: See comments in call_lambda(): this code
* TODO:: should never be reached.
*/
if (sbuf)
strbuf_addf(sbuf, "<function symbol> in '%20s' ('%20s')\n"
, ob->prog->name, ob->name);
if (rvec)
{
NEW_ENTRY(entry, TRACE_TYPE_SYMBOL, ob->prog->name);
}
continue;
}
/* simul_efun closure? */
if (p[0].funstart == SIMUL_EFUN_FUNSTART)
{
if (sbuf)
strbuf_addf( sbuf
, "<simul_efun closure> bound to '%20s' ('%20s')\n"
, ob->prog->name, ob->name);
if (rvec)
{
NEW_ENTRY(entry, TRACE_TYPE_SEFUN, ob->prog->name);
}
continue;
}
/* efun closure? */
if (p[0].funstart == EFUN_FUNSTART)
{
char * iname;
iname = instrs[p[0].instruction].name;
if (iname)
{
if (sbuf)
strbuf_addf(sbuf, "#\'%-14s for '%20s' ('%20s')\n"
, iname, ob->prog->name, ob->name);
if (rvec)
{
NEW_ENTRY(entry, TRACE_TYPE_EFUN, ob->prog->name);
put_volatile_string(entry->vec->item+TRACE_NAME, iname);
}
}
else
{
if (sbuf)
strbuf_addf( sbuf, "<efun closure %d> for '%20s' ('%20s')\n"
, p[0].instruction, ob->prog->name, ob->name);
if (rvec)
{
NEW_ENTRY(entry, TRACE_TYPE_EFUN, ob->prog->name);
put_number(entry->vec->item+TRACE_NAME, p[0].instruction);
}
}
continue;
}
/* Lambda closure? */
if (p[0].funstart < prog->program
|| p[0].funstart > PROGRAM_END(*prog))
{
if (sbuf)
strbuf_addf( sbuf
, "<lambda 0x%6lx> in '%20s' ('%20s') offset %ld\n"
, (long)p[0].funstart, ob->prog->name, ob->name
, (long)(FUNCTION_FROM_CODE(dump_pc) - p[0].funstart)
);
if (rvec)
{
NEW_ENTRY(entry, TRACE_TYPE_LAMBDA, ob->prog->name);
put_number(entry->vec->item+TRACE_NAME, (p_int)p[0].funstart);
PUT_LOC(entry, (FUNCTION_FROM_CODE(dump_pc) - p[0].funstart));
}
continue;
}
/* Nothing of the above: a normal program */
line = get_line_number(dump_pc, prog, &file);
memcpy(&name, FUNCTION_NAMEP(p[0].funstart), sizeof name);
name_computed: /* Jump target from the catch detection */
/* Print the name and line */
if (strcmp(name, "heart_beat") == 0 && p != csp)
ret = p->extern_call ? (p->ob ? p->ob->name : NULL) : ob->name;
if (sbuf)
{
if (file != NULL)
strbuf_addf(sbuf, "'%15s' in '%20s' ('%20s') line %d\n"
, name, file, ob->name, line);
else
strbuf_addf(sbuf, "'%15s' in %22s ('%20s')\n"
, name, "", ob->name);
}
if (rvec)
{
NEW_ENTRY(entry, TRACE_TYPE_LFUN, file != NULL ? file : "");
put_malloced_string(entry->vec->item+TRACE_NAME, string_copy(name));
PUT_LOC(entry, line);
}
} while (++p <= csp);
/* Condense the singular entries into the result array */
if (rvec)
{
vector_t * vec;
size_t ix;
vec = allocate_array(num_entries+1);
if (ret)
put_malloced_string(vec->item, string_copy(ret));
for (ix = 1; first_entry != NULL; ix++, first_entry = first_entry->next)
{
put_array(vec->item+ix, first_entry->vec);
}
*rvec = vec;
}
/* Done */
return ret;
#undef NEW_ENTRY
#undef PUT_LOC
} /* collect_trace() */
/*-------------------------------------------------------------------------*/
char *
dump_trace (Bool how, vector_t ** rvec)
/* Write out a traceback, starting from the first frame. If a heart_beat()
* is involved, return the name of the object that had it.
*
* If <how> is FALSE (the normal case), the trace is written with
* debug_message() only. If <how> is TRUE (used for internal errors), the
* trace is also written to stdout.
*
* If TRACE_CODE is defined and <how> is true, the last executed
* instructions are printed, too.
*
* If <rvec> is not NULL, the traceback is returned in a newly created array
* which pointer is put into *<rvec>. For the format of the array, see
* efun debug_info().
*/
{
strbuf_t sbuf;
char *hb_obj_name;
strbuf_zero(&sbuf);
hb_obj_name = collect_trace(&sbuf, rvec);
/* Print the last instructions if required */
#ifdef TRACE_CODE
if (how) {
/* TODO: This number of instructions should be a runtime arg */
#ifdef DEBUG
(void)last_instructions(200, MY_TRUE, NULL);
if (inter_pc)
printf("%6lx: %3d %3d %3d %3d %3d %3d %3d %3d\n"
, (long)inter_pc
, inter_pc[0], inter_pc[1], inter_pc[2], inter_pc[3]
, inter_pc[4], inter_pc[5], inter_pc[6], inter_pc[7] );
else
printf("No program counter.\n");
#else /* DEBUG */
last_instructions(20, MY_TRUE, NULL);
#endif /* DEBUG */
}
#endif /* TRACE_CODE */
/* Print the trace */
if (how)
fputs(sbuf.buf, stdout);
debug_message("%s", sbuf.buf);
/* Cleanup and return */
strbuf_free(&sbuf);
return hb_obj_name;
} /* dump_trace() */
/*-------------------------------------------------------------------------*/
void
invalidate_apply_low_cache (void)
/* Called in the (unlikely) case that all programs had to be renumbered,
* this invalidates the call cache.
*/
{
int i;
for (i = 0; i < CACHE_SIZE; i++)
{
cache[i].id = 0;
if (!cache[i].progp && cache[i].name)
xfree(cache[i].name);
}
}
/*-------------------------------------------------------------------------*/
size_t
interpreter_overhead (void)
/* Return the amount of memory allocated for the interpreter.
* Right now this is the apply cache overhead.
*/
{
size_t sum, ix;
sum = 0;
for (ix = 0; ix < CACHE_SIZE; ix++)
{
if (!cache[ix].progp && cache[ix].name)
{
sum += strlen(cache[ix].name)+1;
}
}
return sum;
} /* interpreter_overhead() */
#ifdef GC_SUPPORT
/*-------------------------------------------------------------------------*/
void
clear_interpreter_refs (void)
/* GC Support: Clear the interpreter references.
*/
{
#ifdef TRACE_CODE
{
int i;
for (i = TOTAL_TRACE_LENGTH; --i >= 0; )
{
object_t *ob;
if (NULL != (ob = previous_objects[i])
&& ob->flags & O_DESTRUCTED
&& ob->ref
)
{
ob->ref = 0;
ob->prog->ref = 0;
clear_inherit_ref(ob->prog);
}
}
}
#endif
} /* clear_interpreter_refs() */
/*-------------------------------------------------------------------------*/
void
count_interpreter_refs (void)
/* GC Support: Count/mark all interpreter held structures.
*/
{
int i;
for (i = CACHE_SIZE; --i>= 0; ) {
if (!cache[i].progp)
note_malloced_block_ref(cache[i].name);
}
#ifdef TRACE_CODE
for (i = TOTAL_TRACE_LENGTH; --i >= 0; )
{
object_t *ob;
if ( NULL != (ob = previous_objects[i]) )
{
if (ob->flags & O_DESTRUCTED)
{
previous_objects[i] = NULL;
previous_instruction[i] = 0;
reference_destructed_object(ob);
}
else
{
ob->ref++;
}
}
}
#endif
}
/*-------------------------------------------------------------------------*/
#endif /* GC_SUPPORT */
/*=========================================================================*/
/* D E B U G G I N G */
/*-------------------------------------------------------------------------*/
#ifdef OPCPROF
Bool
opcdump (char * fname)
/* Print the usage statistics for the opcodes into the file <fname>.
* Return TRUE on success, FALSE if <fname> can't be written.
*/
{
int i;
FILE *f;
fname = check_valid_path(fname, current_object, "opcdump", MY_TRUE);
if (!fname)
return MY_FALSE;
f = fopen(fname, "w");
if (!f)
return MY_FALSE;
FCOUNT_WRITE(fname);
for(i = 0; i < MAXOPC; i++)
{
if (opcount[i])
#ifdef VERBOSE_OPCPROF
fprintf(f,"%d: \"%-16s\" %6d\n",i, get_f_name(i), opcount[i]);
#else
fprintf(f,"%d: %d\n", i, opcount[i]);
#endif
}
fclose(f);
return MY_TRUE;
}
#endif
#ifdef TRACE_CODE
/*-------------------------------------------------------------------------*/
static char *
get_arg (int a)
/* Return the argument for the instruction at previous_pc[<a>] as a string.
* If there is no argument, return "".
*
* Helper function for last_instructions().
*/
{
static char buff[10];
bytecode_p from, to;
int b;
b = (a+1) % TOTAL_TRACE_LENGTH;
from = previous_pc[a];
to = previous_pc[b];
if (to - from < 2)
return "";
if (to - from == 2)
{
sprintf(buff, "%d", GET_CODE(from+1));
return buff;
}
if (to - from == 3)
{
short arg;
GET_SHORT(arg, from+1);
sprintf(buff, "%d", arg);
return buff;
}
if (to - from == 5)
{
int32 arg;
GET_INT32(arg, from+1);
sprintf(buff, "%ld", (long)arg);
return buff;
}
return "";
}
/*-------------------------------------------------------------------------*/
static void
last_instr_output (char *str, svalue_t **svpp)
/* <svpp> == NULL: print string <str>
* <svpp> != NULL: store a copy of <str> as string-svalue to *<svpp>, then
* increment *<svpp>
*
* Helper function to last_instructions() to either print strings for
* a tracedump, or to push them onto the evaluator stack for the efun
* last_instructions().
*/
{
if (svpp)
{
if ( !(str = string_copy(str)) )
error("Out of memory\n");
put_malloced_string((*svpp), str);
(*svpp)++;
}
else
{
printf("%s\n", str);
}
}
/*-------------------------------------------------------------------------*/
static Bool
program_referenced (program_t *prog, program_t *prog2)
/* Return TRUE if <prog2> inherits <prog>.
*
* Auxiliary function to last_instructions().
*/
{
inherit_t *inh;
int i;
if (prog == prog2)
return MY_TRUE;
/* If a prog2 is swapped out, it can't have prog inherited
* and swapped in.
*/
if (P_PROG_SWAPPED(prog2))
return MY_FALSE;
/* Recursively test the inherits */
for (i = prog2->num_inherited, inh = prog2->inherit; --i >= 0; inh++)
{
if (program_referenced(prog, inh->prog))
return MY_TRUE;
}
return MY_FALSE;
}
/*-------------------------------------------------------------------------*/
static Bool
program_exists (program_t *prog, object_t *guess)
/* Test if <prog> exists - either by itself or as inherited program.
* Start testing with the program of <guess>, if it is not there,
* test all objects in the list.
*
* Auxiliary function to last_instructions().
*/
{
if (program_referenced(prog, guess->prog))
return MY_TRUE;
for (guess = obj_list; guess; guess = guess->next_all)
{
#ifdef DEBUG
if (guess->flags & O_DESTRUCTED) /* TODO: Can't happen */
continue;
#endif
if (program_referenced(prog, guess->prog))
return MY_TRUE;
}
return MY_FALSE;
}
/*-------------------------------------------------------------------------*/
int
last_instructions (int length, Bool verbose, svalue_t **svpp)
/* 'Print' a dump of the <length> last instructions. If <svpp> is NULL,
* all the data is printed, else *<svpp> points to the evaluator stack
* and all the 'printed' lines are pushed onto the stack using *<svpp>
* as pointer.
*
* If <verbose> is true, more information is printed.
*
* Return the index for the last executed instruction.
*
* This function is called from dump_trace() and f_last_instructions().
*/
{
int i;
object_t *old_obj;
char old_file[160], buf[400];
int old_line, line = 0;
old_obj = NULL;
old_file[0] = old_file[sizeof old_file - 1] = '\0';
old_line = 0;
i = (last - length + TOTAL_TRACE_LENGTH) % TOTAL_TRACE_LENGTH;
/* Walk through the instructions.
* Instructions with value 0 are not used yet, or have been
* removed while cleaning up destructed objects.
*/
do {
i = (i + 1) % TOTAL_TRACE_LENGTH;
if (previous_instruction[i] != 0)
{
if (verbose)
{
char *file;
program_t *ppr;
bytecode_p ppc;
ppr = previous_programs[i];
ppc = previous_pc[i]+1;
if (!program_exists(ppr, previous_objects[i]))
{
file = "program deallocated";
line = 0;
}
else if (ppc < ppr->program || ppc > PROGRAM_END(*ppr))
{
file = "<lambda ?>";
line = 0;
}
else
{
line = get_line_number(ppc, ppr, &file);
}
if (previous_objects[i] != old_obj || strcmp(file, old_file))
{
sprintf(buf, "%.170s %.160s line %d",
previous_objects[i]->name, file, line
);
last_instr_output(buf, svpp);
old_obj = previous_objects[i];
xstrncpy(old_file, file, sizeof old_file - 1);
}
}
sprintf(buf, "%6lx: %3d %8s %-26s (%ld:%3ld)"
, (long)previous_pc[i]
, previous_instruction[i] /* instrs.h has these numbers */
, get_arg(i)
, get_f_name(previous_instruction[i])
, (long) (stack_size[i] + 1)
, (long) (abs_stack_size[i])
);
if (verbose && line != old_line)
sprintf(buf + strlen(buf), "\tline %d", old_line = line);
last_instr_output(buf, svpp);
}
} while (i != last);
return last;
} /* last_instructions() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_last_instructions (svalue_t *sp)
/* TEFUN last_instructions()
*
* string *last_instructions (int length, int verbose)
*
* Return an array showing the 'length' last executed
* instructions in disassembled form. If 'verbose' is non-zero
* (the default), line number information are also included.
* Each string is built as this:
*
* Opcode-Address: Opcode Operand Mnemonic (Stackdepth) Linenumber
*
* The Stackdepth information consists of two numbers <rel>:<abs>:
* <rel> is the relative stack usage in this function, <abs> is the
* absolute stack usage.
*
* The linenumber information is appended if requested and a new
* source line is reached. Also, calls between objects produce a
*
* Objectname Programname Linenumber
*
* entry in the resulting array (in verbose mode only).
*
* There is a preconfigured upper limit for the backtrace.
*/
{
vector_t *v, *v2;
mp_int num_instr, size;
svalue_t *svp;
/* Test the arguments */
if (sp[-1].type != T_NUMBER || (num_instr = sp[-1].u.number) <= 0)
bad_xefun_arg(1, sp);
if (sp->type != T_NUMBER)
bad_xefun_arg(2, sp);
sp--;
inter_sp = sp; /* Out of memory possible */
if (num_instr > TOTAL_TRACE_LENGTH)
num_instr = TOTAL_TRACE_LENGTH;
/* Allocate the result vector */
size = sp[1].u.number ? num_instr << 1 : num_instr;
v = allocate_array(size);
/* Enter the vector into the stack for now, so that it will be
* freed when an out of memory error occurs.
*/
put_array(sp, v);
svp = v->item;
last_instructions(num_instr, sp[1].u.number != 0, &svp);
/* If we allocated the vector to big, get a shorter one and copy
* the data.
*/
if (svp - v->item < size)
{
size = svp - v->item;
v2 = allocate_array(size);
memcpy(v2->item, v->item, size * sizeof *svp);
sp->u.vec = v2;
free_empty_vector(v);
}
return sp;
} /* f_last_instructions() */
/*-------------------------------------------------------------------------*/
#endif /* TRACE_CODE */
#ifdef DEBUG
/*-------------------------------------------------------------------------*/
void
count_inherits (program_t *progp)
/* Check Refcounts: Increment the extra_ref of all programs inherited
* by <progp>. If one of those programs has not been visited yet,
* its extra_ref is set to 1 and this function is called recursively.
*
* If check_..._search_prog is set and equal to one of the inherited
* programs, a notice is printed.
*/
{
int i;
program_t *progp2;
/* Clones will not add to the ref count of inherited progs */
for (i = 0; i < progp->num_inherited; i++)
{
progp2 = progp->inherit[i].prog;
progp2->extra_ref++;
if (progp2 == check_a_lot_ref_counts_search_prog)
printf("%s Found prog, inherited by %s, new total ref %ld\n"
, time_stamp(), progp->name, progp2->ref);
if (NULL == register_pointer(ptable, progp2))
continue;
progp2->extra_ref = 1;
if (progp2->blueprint)
count_extra_ref_in_object(progp2->blueprint);
count_inherits(progp2);
}
} /* count_inherits() */
/*-------------------------------------------------------------------------*/
static void
count_extra_ref_in_mapping_filter ( svalue_t *key, svalue_t *data
, void * extra)
/* Count the extra refs for <key> and the associated <data>. <extra>
* is a mp_int giving the number of data values.
*/
{
count_extra_ref_in_vector(key, 1);
count_extra_ref_in_vector(data, (size_t)extra);
}
/*-------------------------------------------------------------------------*/
static void
check_extra_ref_in_mapping_filter (svalue_t *key, svalue_t *data
, void * extra)
/* Check the extra refs for <key> and the associated <data>. <extra>
* is a mp_int giving the number of data values.
*/
{
check_extra_ref_in_vector(key, 1);
check_extra_ref_in_vector(data, (size_t)extra);
}
/*-------------------------------------------------------------------------*/
void
count_extra_ref_in_object (object_t *ob)
/* Count the extra refs for object <ob>. If the object has been visited
* before, extra_ref is just incremented. Otherwise, extra_ref is
* set to 1 and all depending refs are counted.
*
* If check_..._search_prog is set and matches the objects program,
* a notice is printed.
*/
{
Bool was_swapped = MY_FALSE;
ob->extra_ref++;
if ( NULL == register_pointer(ptable, ob) )
return;
ob->extra_ref = 1;
if ( !O_PROG_SWAPPED(ob) )
{
ob->prog->extra_ref++;
if (ob->prog == check_a_lot_ref_counts_search_prog)
printf("%s Found program for object %s\n", time_stamp(), ob->name);
}
/* Clones will not add to the ref count of inherited progs */
if (O_PROG_SWAPPED(ob))
{
if (load_ob_from_swap(ob) < 0)
debug_message( "%s check-refcounts: Program for '%s' can't be "
"swapped in - extra refcounts may be wrong.\n"
, time_stamp(), ob->name);
else
was_swapped = MY_TRUE;
}
if (!O_PROG_SWAPPED(ob))
{
if (NULL != register_pointer(ptable, ob->prog))
{
ob->prog->extra_ref = 1;
if (ob->prog->blueprint)
count_extra_ref_in_object(ob->prog->blueprint);
count_inherits(ob->prog);
}
}
if (was_swapped)
swap_program(ob);
if (ob->flags & O_SHADOW)
{
ed_buffer_t *buf;
if ( NULL != (buf = O_GET_SHADOW(ob)->ed_buffer) )
count_ed_buffer_extra_refs(buf);
}
} /* count_extra_ref_in_closure() */
/*-------------------------------------------------------------------------*/
static void
count_extra_ref_in_closure (lambda_t *l, ph_int type)
/* Count the extra refs in the closure <l> of type <type>.
*/
{
if (CLOSURE_HAS_CODE(type))
{
/* We need to count the extra_refs in the constant values. */
mp_int num_values;
svalue_t *svp;
svp = (svalue_t *)l;
if ( (num_values = EXTRACT_UCHAR(l->function.code)) == 0xff)
num_values = svp[-0x100].u.number;
svp -= num_values;
count_extra_ref_in_vector(svp, (size_t)num_values);
}
else
{
/* Count the referenced closures and objects */
if (type == CLOSURE_BOUND_LAMBDA)
{
lambda_t *l2 = l->function.lambda;
if (NULL != register_pointer(ptable, l2) )
count_extra_ref_in_closure(l2, CLOSURE_UNBOUND_LAMBDA);
}
else if (type == CLOSURE_ALIEN_LFUN)
{
count_extra_ref_in_object(l->function.alien.ob);
}
}
if (type != CLOSURE_UNBOUND_LAMBDA)
count_extra_ref_in_object(l->ob);
} /* count_extra_ref_in_closure() */
/*-------------------------------------------------------------------------*/
void
count_extra_ref_in_vector (svalue_t *svp, size_t num)
/* Count the extra_refs of all <num> values starting at <svp>.
*/
{
svalue_t *p;
if (!svp)
return;
for (p = svp; p < svp+num; p++)
{
switch(p->type)
{
case T_CLOSURE:
if (CLOSURE_MALLOCED(p->x.closure_type))
{
lambda_t *l;
l = p->u.lambda;
if ( NULL == register_pointer(ptable, l) )
continue;
count_extra_ref_in_closure(l, p->x.closure_type);
continue;
}
/* FALLTHROUGH */
case T_OBJECT:
{
count_extra_ref_in_object(p->u.ob);
continue;
}
case T_QUOTED_ARRAY:
case T_POINTER:
p->u.vec->extra_ref++;
if (NULL == register_pointer(ptable, p->u.vec) )
continue;
p->u.vec->extra_ref = 1;
count_extra_ref_in_vector(&p->u.vec->item[0], VEC_SIZE(p->u.vec));
continue;
case T_MAPPING:
if (NULL == register_pointer(ptable, p->u.map) ) continue;
walk_mapping(
p->u.map,
count_extra_ref_in_mapping_filter,
(void *)p->u.map->num_values
);
continue; /* no extra ref count implemented */
}
}
} /* count_extra_ref_in_vector() */
/*-------------------------------------------------------------------------*/
static void
check_extra_ref_in_vector (svalue_t *svp, size_t num)
/* Check the extra_refs of the <num> values starting at <svp>
*/
{
svalue_t *p;
if (!svp)
return;
for (p = svp; p < svp+num; p++)
{
switch(p->type)
{
case T_QUOTED_ARRAY:
case T_POINTER:
if (NULL == register_pointer(ptable, p->u.vec) )
continue;
check_extra_ref_in_vector(&p->u.vec->item[0], VEC_SIZE(p->u.vec));
p->u.vec->extra_ref = 0;
continue;
case T_MAPPING:
if (NULL == register_pointer(ptable, p->u.map) ) continue;
walk_mapping(
p->u.map,
check_extra_ref_in_mapping_filter,
(void *)((p_int)p->u.map->num_values)
);
continue; /* no extra ref count implemented */
}
}
} /* check_extra_ref_in_vector() */
/*-------------------------------------------------------------------------*/
void
check_a_lot_ref_counts (program_t *search_prog)
/* Loop through every object and variable in the game and check all
* reference counts. This will surely take some time and should be
* used only for debugging.
*
* If <search_prog> is set, the function will just count the references
* and print the information for the given program, if found.
*
* The function must be called after removing all destructed objects.
*
* TODO: No extra_refs implemented in mappings.
* TODO: Put this code somewhere else.
*/
{
object_t *ob;
int i;
check_a_lot_ref_counts_search_prog = search_prog;
/* Pass 1: Compute the ref counts.
*
* The pointer table keeps track of objects already visited,
* eliminating the need for a separate pass to clear the
* ref counts.
*/
ptable = new_pointer_table();
if (!ptable)
{
debug_message("%s Out of memory while checking all refcounts.\n"
, time_stamp());
return;
}
/* List of all objects.
*/
for (ob = obj_list; ob; ob = ob->next_all)
{
if (ob->flags & O_DESTRUCTED)
{
/* This shouldn't happen */
debug_message("%s Found destructed object '%s' where it shouldn't "
"be.\n", time_stamp(), ob->name);
continue;
}
if (O_VAR_SWAPPED(ob))
load_ob_from_swap(ob);
count_extra_ref_in_vector(ob->variables, (size_t)ob->extra_num_variables);
count_extra_ref_in_object(ob);
}
if (master_ob)
master_ob->extra_ref++;
if (d_flag > 3)
{
debug_message("%s obj_list evaluated\n", time_stamp());
}
/* The current stack.
*/
count_extra_ref_in_vector(VALUE_STACK, (size_t)(inter_sp - VALUE_STACK + 1));
if (d_flag > 3)
{
debug_message("%s stack evaluated\n", time_stamp());
}
/* Other variables and lists.
*/
count_extra_ref_from_call_outs();
count_extra_ref_from_wiz_list();
count_simul_efun_extra_refs(ptable);
count_comm_extra_refs();
#ifdef TRACE_CODE
{
int j;
for (j = TOTAL_TRACE_LENGTH; --j >= 0; )
{
if ( NULL != (ob = previous_objects[j]) )
count_extra_ref_in_object(ob);
}
}
#endif
count_extra_ref_in_vector(&indexing_quickfix, 1);
count_extra_ref_in_vector(&last_indexing_protector, 1);
null_vector.extra_ref++;
/* To cound the closure hooks properly, we have to fiddle the
* types a bit.
*/
for (i = NUM_DRIVER_HOOKS; --i >= 0; )
{
if (driver_hook[i].type == T_CLOSURE
&& driver_hook[i].x.closure_type == CLOSURE_LAMBDA)
{
driver_hook[i].x.closure_type = CLOSURE_UNBOUND_LAMBDA;
}
}
count_extra_ref_in_vector(driver_hook, NUM_DRIVER_HOOKS);
/* Undo the type fiddling.
*/
for (i = NUM_DRIVER_HOOKS; --i >= 0; )
{
if (driver_hook[i].type == T_CLOSURE
&& driver_hook[i].x.closure_type == CLOSURE_UNBOUND_LAMBDA)
{
driver_hook[i].x.closure_type = CLOSURE_LAMBDA;
}
}
/* Done with the counting */
free_pointer_table(ptable);
/* Was that all for this time? */
if (search_prog)
return;
/* Pass 3: Check the ref counts.
*
* The (new) pointer table is used as before.
*/
ptable = new_pointer_table();
if (!ptable)
{
debug_message("%s Out of memory while checking all refcounts.\n"
, time_stamp());
return;
}
for (ob = obj_list; ob; ob = ob->next_all) {
if (ob->flags & O_DESTRUCTED) /* shouldn't happen */
continue;
if (ob->ref != ob->extra_ref)
{
debug_message("%s Bad ref count in object %s, %ld - %ld\n"
, time_stamp()
, ob->name, ob->ref, ob->extra_ref);
}
else if ( !(ob->flags & O_SWAPPED) )
{
if (ob->prog->ref != ob->prog->extra_ref)
{
/* an inheriting file might be swapped */
if (time_to_swap + 1 > 0
&& ob->prog->ref > ob->prog->extra_ref)
{
debug_message("%s high ref count in prog %s, %ld - %ld\n"
, time_stamp(), ob->prog->name, ob->prog->ref
, ob->prog->extra_ref);
}
else
{
check_a_lot_ref_counts(ob->prog);
debug_message("%s Bad ref count in prog %s, %ld - %ld\n"
, time_stamp()
, ob->prog->name
, ob->prog->ref, ob->prog->extra_ref);
}
}
} /* !SWAPPED */
check_extra_ref_in_vector(ob->variables, (size_t)ob->extra_num_variables);
} /* for */
check_extra_ref_in_vector(VALUE_STACK, (size_t)(inter_sp - VALUE_STACK + 1));
free_pointer_table(ptable);
} /* check_a_lot_of_ref_counts() */
/*-------------------------------------------------------------------------*/
#endif /* DEBUG */
/*=========================================================================*/
/* E F U N S */
/*-------------------------------------------------------------------------*/
/* (Re)define a couple a macros for the efuns below
*/
#undef ERROR
#define ERROR(s) {inter_sp = sp; error(s);}
#undef TYPE_TEST1
#define TYPE_TEST1(arg1, type1, instruction) {\
if ((arg1)->type != (type1)) {\
bad_efun_arg(1, (instruction), sp);\
}\
}
#undef TYPE_TEST2
#define TYPE_TEST2(arg1, type2, instruction) {\
if ((arg1)->type != (type2)) {\
bad_efun_arg(2, (instruction), sp);\
}\
}
/*-------------------------------------------------------------------------*/
svalue_t *
f_trace (svalue_t *sp)
/* TEFUN trace()
*
* int trace(int traceflags)
*
* Sets the trace flags and returns the old trace flags. When
* tracing is on, a lot of information is printed during
* execution and too much output can crash your connection or
* even the whole driver.
*
* Tracing is done on a per-connection basis: each interactive(!)
* user may specifiy its own tracelevel and -prefix. Each gets the
* traceoutput for just the code executed during the evaluation
* of the commands he entered.
*
* The trace bits are:
*
* TRACE_NOTHING ( 0): stop tracing.
*
* TRACE_CALL ( 1): trace all calls to lfuns.
* TRACE_CALL_OTHER ( 2): trace call_others()s.
* TRACE_RETURN ( 4): trace function returns.
* TRACE_ARGS ( 8): print function arguments and results.
* TRACE_EXEC ( 16): trace all executed instructions.
* TRACE_HEART_BEAT ( 32): trace heartbeat code.
* TRACE_APPLY ( 64): trace driver applies.
* TRACE_OBJNAME (128): print the object names.
*
* TRACE_EXEC and TRACE_HEART_BEAT should be avoided as they cause massive
* output! TRACE_OBJNAME should be avoided when you know what you trace.
*
* The master-lfun valid_trace() is called to verify the
* usage of this efun.
*/
{
int ot;
interactive_t *ip;
TYPE_TEST1(sp, T_NUMBER, F_TRACE)
ot = -1;
/* If the command_giver is allowed to do so... */
if (command_giver
&& O_SET_INTERACTIVE(ip, command_giver))
{
svalue_t *arg;
assign_eval_cost();
inter_sp = _push_volatile_string("trace", sp);
push_number(sp->u.number);
arg = apply_master(STR_VALID_TRACE, 2);
if (arg)
{
/* ... then set the new tracelevel */
if (arg->type != T_NUMBER || arg->u.number != 0)
{
ot = ip->trace_level;
trace_level |=
ip->trace_level = sp->u.number;
}
}
}
/* Return the old level */
sp->u.number = ot;
SET_TRACE_EXEC;
return sp;
} /* f_trace() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_traceprefix (svalue_t *sp)
/* TEFUN traceprefix()
*
* string traceprefix(string prefix)
* string traceprefix(int dummy)
*
* If called with a string, only objects matching this prefix will be traced.
* The string must not contain a leading "/" because the object names are
* stored internally without it. If called with a number, the traceprefix will
* be ignored and all objects will be traced. Returns the last traceprefix or
* 0 if there wasn't any.
*
* The master-lfun valid_trace() is called to verify the usage of this
* efun.
*/
{
char *old;
interactive_t *ip;
if (sp->type != T_STRING && sp->type != T_NUMBER)
bad_xefun_arg(1, sp);
old = 0;
/* If the command_giver is allowed to do that... */
if (command_giver
&& O_SET_INTERACTIVE(ip, command_giver))
{
svalue_t *arg;
inter_sp = _push_volatile_string("traceprefix", sp);
inter_sp++; assign_svalue_no_free(inter_sp, sp);
assign_eval_cost();
arg = apply_master(STR_VALID_TRACE,2);
if (arg)
{
/* ... then so shall it be */
if (arg && (arg->type != T_NUMBER || arg->u.number))
{
old = ip->trace_prefix;
if (sp->type == T_STRING)
{
ip->trace_prefix = make_shared_string(sp->u.string);
}
else
ip->trace_prefix = NULL;
}
}
}
free_svalue(sp);
/* Return the old prefix */
if (old)
put_string(sp, old);
else
put_number(sp, 0);
return sp;
} /* f_traceprefix() */
/***************************************************************************/