home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 5 Edit
/
05-Edit.zip
/
me34src.zip
/
me3
/
mc
/
mm.c
< prev
next >
Wrap
C/C++ Source or Header
|
1995-01-14
|
46KB
|
1,496 lines
/*
* mm.c : the Mutt Machine
* Craig Durland 6/87
* Added dstrings, more comments 3/91
* lists, ojbect manager mid '91
* See mm2.doc for lots of documentation.
*/
/* Copyright 1990 - 1993 Craig Durland
* Distributed under the terms of the GNU General Public License.
* Distributed "as is", without warranties of any kind, but comments,
* suggestions and bug reports are welcome.
*/
static char what[] = "@(#)MM2 (Mutt Machine II) v2.3 6/6/93";
#include <stdio.h>
#include <setjmp.h>
#include <os.h>
#include <const.h>
#include "opcode.h"
#include "mm.h"
#include "oman.h"
extern char *calloc(), *malloc(), *strcpy(), *strcat(), *l_to_a();
extern long atol();
char *MMvtoa();
typedef struct
{
uint8 type;
union { uint16 t; char *name; maddr addr; int32 laddr; } token;
int abase, vsptr; /* part of the stack frame */
} ositem;
#define OPL_ADDRESS 0xC5 /* !!! long address */
MMDatum RV, TV; /* Mutt Machine registers */
/* ******************************************************************** */
/* ************************ Object Management ************************* */
/* ******************************************************************** */
static int is_object();
#define IS_STRING(type) (((type) == STRING) || ((type) == OSTRING))
#define MAKE_STRING(rv) \
(((rv).type == OSTRING) ? OBJSTRING((rv).val.object) : (rv).val.str)
extern Object *OMcreate_object(), *OMextract_elements(),
*OMdup_object(), *OMnth_element();
extern ObjectPool *OMcreate_object_pool();
/* ********** local objects **************** */
#define MAX_LOCAL_OBJECTS ASTACKSIZ
static ObjectPool *local_object_pool, *tmp_object_pool;
static Object *local_object_table[MAX_LOCAL_OBJECTS];
static int lobj_max = 0, lobj_start = 0;
static void lobj_push(object) Object *object;
{
if (lobj_max == MAX_LOCAL_OBJECTS) MMbitch("Object table overflow");
local_object_table[lobj_max++] = object;
}
static Object *get_lobj(n) int n;
{ return local_object_table[lobj_start + n]; }
/* Routine to gc local objects.
* All live local objects are marked.
* If RV is an object and is in the local pool, need to mark it also.
* Notes:
* It would be easier to mark dead objects (don't have to mess with
* RV) but I don't know where the dead objects are in
* local_object_table[] - lobj_start and lobj_max reflect the
* current live range.
* Worrying about RV being in the local pool stinks - the only time it
* will matter is if a program is returning a (local) object when we
* gc. Then only time this regularly happens is when all programs
* are done and then it only matters if the application wants the
* object. Most of the time a OSTRING is sitting in RV that nobody
* cares about. Unfortunately, I don't know of a easy/fast way to
* get around these problems.
*/
static int local_gc_marker()
{
int j;
if (is_object(RV.type) && OMin_pool(local_object_pool, RV.val.object))
OMgc_mark_object(RV.val.object);
for (j = lobj_max; j--; ) OMgc_mark_object(local_object_table[j]);
return 1; /* live objects are marked */
}
/* ********** global objects **************** */
static ObjectPool *global_object_pool;
Object **MMglobal_object_table; /* Object *MMglobal_object_table[]; */
static void gobj_push(object, n) Object *object;
{
MMglobal_object_table[n] = object;
}
/* Routine to gc global objects.
* I only gc a block when the block is freed (since global objects live
* as long as the block does. When the block is freed, I need to free
* up all objects in the block.
* Input:
* object_table: Pointer to the block (being freed) object table.
* num_objects: Number of object in object_table.
* Notes:
* Call this when a block is freed.
* All global objects are in the same object pool.
* Only gc when a block is freed because thats the only time there
* will be garbage in this pool. So don't GC when run out of memory
* or when somebody gc's the world.
* I mark all dead objects (the ones in the block object table)
* because thats easy.
*/
static void gc_globals(object_table, num_objects) Object *object_table[];
{
int j;
if (num_objects == 0) return; /* avoid unnecessary work */
for (j = num_objects; j--; ) OMgc_mark_object(object_table[j]);
OMgc_pool(global_object_pool, 2); /* dead objects are marked */
}
/* ****************** Object Utilities ******************* */
/* Is type an object type?
* Notes:
* If type is STRING and points into a OSTRING, we are screwed. I
* don't think I do this however.
*/
static int is_object(type) int type;
{
return (type == OSTRING || type == LIST);
}
/* !!!
* Notes:
* I call OMset_object() alot and don't check for errors. This is bad
* but I'm real tired of error checking right now. Besides, only a
* few cases will cause problems and if there are errors, they will
* be out of memory problems - in which case not much is working
* anyway (probably). And these are "soft" failures - the data
* types don't change and the data is valid, just wrong. Could make
* for some fun Mutt debugging.
* Yes, I plan to fix it one of these years. Or I will avoid it by
* rewriting this stuff yet again.
* !!!
*/
/* !!! no workie much */
static Object *convert_to_object(pool, val) ObjectPool *pool; MMDatum *val;
{
int type;
Object *object;
type = val->type;
if (is_object(type)) return val->val.object;
if (type == STRING) type = OSTRING;
if (!(object = OMcreate_object(pool, type, 0))) return NULL;
switch (type)
{
case NUMBER: OMset_object(object, type, (long int)val->val.num); break;
case OSTRING: OMset_object(object, type, val->val.str); break;
default: return NULL;
}
return object;
}
/* !!! no workie much */
void MMconvert_to_datum(object, val) Object *object; MMDatum *val;
{
int type;
type = object->type;
switch (type)
{
case NUMBER: val->val.num = OBJNUMBER(object); break;
case LIST:
case OSTRING:
val->val.object = object; break;
}
val->type = type;
}
/* ******************************************************************** */
/* ************************* Stack Management ************************* */
/* ******************************************************************** */
extern int MMask_pgm;
int MMcurrent_block = -1; /* to force MMset_block() to sync the first time */
static MMStkFrame *prev_stkframe;
static int vsptr, asptr, osptr, abase, vbase, numargs;
static ositem opstack[OSTACKSIZ]; /* opcode stack */
static MMDatum argstack[ASTACKSIZ]; /* arg stack */
static uint8 varstack[VSTACKSIZ]; /* flotsam, vars and jetsam */
static maddr pc; /* MM program counter */
uint8 *MMglobal_vars; /* start of global variables */
#define asp() asptr
#define aspset(n) asptr = (n)
/* Initialize the Mutt Machine. Set all state variables, stacks, etc to
* their initial state.
* Notes
* This MUST be called before the first pgm is run.
* Call when a pgm aborts or halts.
* Don't need to call this a pgm is done because poping the stack last
* stack frame will restore things to this state.
*/
static void init_stacks()
{
asptr = osptr = vsptr = abase = vbase = numargs = 0;
prev_stkframe = NULL;
lobj_max = lobj_start = 0;
#if 0 /* !!!??? */
MMglobal_vars = NULL;
MMglobal_object_table = NULL;
MMset_block(0); /* I don't think I care */
#endif
#if 0
/* can't do because MMinit calls this before add_block is ever called */
/* Force the app to sync MMcurrent_block just in case they are out
* of sync.
*/
MMcurrent_block = -1;
MMset_block(0);
#endif
}
/* Save the current stack frame in mark & set up new a frame.
* Notes:
* Only need to save the block local data (MMglobal_vars and
* MMglobal_object_table) when a function call will switch blocks
* (such as calling an external pgm (eg via OPNAME or FADDR)), in
* other cases, they don't change.
*/
static void setframe(mark,startframe,flotsam) register MMStkFrame *mark;
{
mark->abase = abase; mark->startframe = abase = startframe;
mark->vbase = vbase; mark->vsptr = flotsam;
mark->numargs = numargs;
mark->pc = pc;
mark->prev_stkframe = prev_stkframe;
prev_stkframe = mark;
vbase = vsptr; /* set vbase after flotsam */
numargs = asp() -abase;
/* ??? Instead of putting gvars and global object table in stackframe,
* why not put in a block pointer and dig it out of there on reset?
*/
#define CBLOCK 1
#if CBLOCK
mark->block_id = MMcurrent_block;
#else
mark->gvars = MMglobal_vars;
mark->global_object_table = MMglobal_object_table;
#endif
mark->lobj_max = lobj_max; mark->lobj_start = lobj_start;
lobj_start = lobj_max;
}
static void resetframe(mark) /* reset a stack frame */
register MMStkFrame *mark;
{
aspset(mark->startframe);
abase = mark->abase; vbase = mark->vbase;
numargs = mark->numargs;
pc = mark->pc; vsptr = mark->vsptr;
#if CBLOCK
MMset_block(mark->block_id);
#else
MMglobal_vars = mark->gvars;
MMglobal_object_table = mark->global_object_table;
#endif
lobj_max = mark->lobj_max; lobj_start = mark->lobj_start;
}
static void pop_stkframe()
{
resetframe(prev_stkframe);
prev_stkframe = prev_stkframe->prev_stkframe;
}
/* Don't use this if you turn around and call MM().
* Set MMask_pgm to TRUE after you do the ask.
* This is ment for self contained opcodes.
*/
void MMset_ask_frame()
{
resetframe(prev_stkframe);
MMask_pgm = (MMask_pgm && numargs);
}
void MMreset_ask_frame()
{
prev_stkframe->numargs = numargs;
prev_stkframe->abase = abase;
MMask_pgm = TRUE; /* reset (ask-user) */
}
MMgonna_ask_pgm()
{
return (MMask_pgm && prev_stkframe->numargs);
}
static void vpush(val) MMDatum *val;
{
if (asptr == ASTACKSIZ) MMbitch("arg stack overflow");
argstack[asptr++] = *val;
}
static void vpop(val) MMDatum *val; { *val = argstack[--asptr]; }
/* Pull the nth arg out of the stack frame.
* This routine for people writing Mutt extensions. It is used to get
* parameters off the stack. For example, if you are writing the C
* code for "foo" and it is called like so: (foo 123), then when your
* foo code is called, you can MMpull_nth_arg(&RV,0) and RV will be a
* number with value 123.
* See also: MMnext_arg().
* Notes:
* Don't have to worry about garbage collection because I'm just
* copying pointers - the objects remain in the local stack and will
* not be collected.
* Input:
* val: Pointer to a var (MMDatum). Arg will be stashed there.
* Usually &RV.
* n: The arg you want to pull. 0 is the first and numargs is 1+
* the last (not that it helps you - you have to use (nargs) or
* MMpull_nth_arg() until it returns false.
* Output:
* val: MMDatum is filled in with pointers to nth stack arg. If it
* is an object string, it points to the contents of the string.
* Returns:
* TRUE: Got to the arg
* FALSE: n if out range (less than 0 or greater than the number of
* args)
*/
MMpull_nth_arg(val,n) MMDatum *val; int n; /* pull the nth arg */
{
if (n >= numargs || n < 0) return FALSE;
*val = argstack[abase+n];
if (val->type == OSTRING)
{
val->type = STRING;
val->val.str = OBJSTRING(val->val.object);
}
return TRUE;
}
/* Same as MMpull_nth_arg() 'cept no object conversion. Ment to for
* internal consumption.
*/
static int apulln(val,n) MMDatum *val; int n; /* pull the nth arg */
{
if (n >= numargs || n < 0) return FALSE;
*val = argstack[abase+n];
return TRUE;
}
/* Get the next arg in the stack frame, convert it to a string and store
* it in a buffer.
* Ment for stuff that wants a bunch of ascii info from something and
* does the conversions itself (like (ask)). Use this routine when
* writing a routine that can get info from either a user or Mutt pgm.
* Input:
* buf: Pointer to a area to store the ascii form of the var in.
* Returns:
* FALSE: No more args
* TRUE: all OK
* Munges:
* TV
* WARNING!
* Make sure this does NOT setjmp()!
*/
MMnext_arg(buf) char *buf; /* ask a pgm instead of user */
{
if (!MMpull_nth_arg(&TV,0)) { MMmoan("not that many args"); return FALSE; }
strcpy(buf,MMvtoa(&TV));
abase++; numargs--;
return TRUE;
}
static void set_MMvar(ptr,type) uint8 *ptr; /* var = RV */
{
switch (type)
{
case INT8:
case BOOLEAN: PUT_UINT8(ptr,RV.val.num); break;
case INT16: PUT_INT16(ptr,RV.val.num); break;
case INT32: PUT_INT32(ptr,RV.val.num); break;
case BLOB: PUT_INT32(ptr,(int32)RV.val.blob); break;
}
}
static void get_MMvar(ptr,type) uint8 *ptr; /* RV = var */
{
RV.type = type;
switch (type)
{
case INT8: RV.type = NUMBER; RV.val.num = GET_UINT8(ptr); break;
case INT16: RV.type = NUMBER; RV.val.num = GET_INT16(ptr); break;
case INT32: RV.type = NUMBER; RV.val.num = GET_INT32(ptr); break;
case BOOLEAN: RV.val.num = GET_UINT8(ptr); break;
case BLOB: RV.val.blob = (uint8 *)GET_INT32(ptr); break;
}
}
static uint8 *lalloc(n) /* alloc n bytes on varstack, 0 == noop */
{
uint8 *ptr = &varstack[vsptr];
vsptr += n;
if (vsptr > VSTACKSIZ) MMbitch("var stack overflow");
return ptr;
}
static char *pushstr(str) char *str;
{ return strcpy(lalloc(strlen(str) + 1),str); }
/* Check to see a string is out of the "live" stack frames. This can
* happen if a string is pushstr()'d, a function called and that
* function returns the string - the string is in a dead frame and can
* be overwritten.
* Since the string is already in varstack, we know it will fit.
* This is only called from PUSHRV and basically replaced a call to
* pushstr(). Can't use pushstr() because strcpy() might not handle
* overlapping strings.
* This routine is the result of one of those pain in the butt bugs that
* took me a long time to figure out. Oh well, it is not called much.
*/
static int vble(str) uint8 *str;
{
if ((varstack + vsptr) <= str && str < (varstack + VSTACKSIZ))
{
char *ptr;
RV.val.str = ptr = (char *)lalloc(strlen(str) + 1);
while (*ptr++ = *str++) ; /* copy upto and including the '\0' */
}
return FALSE;
}
static void opush(op) ositem *op;
{
if (osptr == OSTACKSIZ) MMbitch("opstack overflow");
opstack[osptr++] = *op;
}
static void opop(op) ositem *op; { *op = opstack[--osptr]; }
/* ******************************************************************** */
/* ********************** Handle Imbedded Types *********************** */
/* ******************************************************************** */
maddr pcat() { return pc; }
static maddr addr() /* grab relative addr at pc, advance the pc */
{
maddr a = pc +GET_INT16(pc + 1);
pc += sizeof(int16);
return a;
}
static int num8() /* grab a uint8 at the pc, advance the pc */
{
int n = GET_UINT8(pc + 1); /* assumes no sign extension ie 0xFF => 255 */
pc += sizeof(uint8);
return n;
}
static int num16() /* grab a int16 at the pc, advance the pc */
{
int n = GET_INT16(pc + 1);
pc += sizeof(int16);
return n;
}
static int32 num32() /* grab a int32 at the pc, advance the pc */
{
int32 n = GET_INT32(pc + 1);
pc += sizeof(int32);
return n;
}
#define STR() (char *)(MMglobal_vars - num16())
/* ******************************************************************** */
/* ************************* the Mutt Machine ************************* */
/* ******************************************************************** */
extern maddr MMblock_code(); /* in the application */
void MMabort_pgm();
static void exetern(), dotok(), convert_to();
static int n, n1;
static uint8 *blob;
static ositem op;
char result[RSIZ]; /* A stash to hold STRINGs */
/* MMask_pgm is initially FALSE so when pgms aren't running the outside
* world won't get confused.
*/
int MMask_pgm = FALSE;
#define opcode() *pc
#define incpc() pc++
/* Reset the Mutt Machine.
* This is called when no Mutt programs are running ie when the last
* program has finished running. It resets the stacks, garbage
* collects MM and the application and general clean up to make ready
* for the next program to run.
* Notes:
* If a program stops and leaves a (local) object in RV, it is not
* GCed. This is because an application may want to see the result
* of running a program. ??? I'm not sure this is a good idea. The
* object should be GCed when the next program runs and only cause a
* problem with big objects.
* Input:
* aborting: TRUE if this is being called because MM is aborting.
*/
static void reset_MM(aborting)
{
if (aborting)
{
init_stacks();
RV.type = NUMBER; /* so garbage collecter won't think this is a object */
}
MMgc_external_objects();
OMgc_pool(tmp_object_pool, 1); /* live (ie none) objects are marked */
OMgc_pool(local_object_pool, 0); /* OMgc_the_world(); */
MMask_pgm = FALSE;
}
/* The Mutt Machine main loop.
* Notes
* To avoid having to maintain a stack of stack frames, I use
* recursion. This means that I need to save a stack frame (eg for
* function calls), I call myself and let C save it for me.
* Input:
* startaddr: Address of the code to run. Must have set up a stack
* frame (ie all state vars are set to "proper" values).
* Result:
* Side effects up the wazoo.
*/
static void MM(startaddr) maddr startaddr;
{
MMStkFrame mark;
pc = startaddr;
while(TRUE)
{
switch(opcode())
{
case HALT: MMabort_pgm(0);
case DONE: goto done;
case ASKUSER: MMask_pgm = FALSE; RV.type = VOID; break;
case RVBOOL: RV.type = BOOLEAN; RV.val.num = num8(); break;
case RVNUM8: RV.type = NUMBER; RV.val.num = num8(); break;
case RVNUM16: RV.type = NUMBER; RV.val.num = num16(); break;
case RVNUM32: RV.type = NUMBER; RV.val.num = num32(); break;
case RVSTR: RV.type = STRING; RV.val.str = STR(); break;
case RVVOID: RV.type = VOID; break;
case ADD: vpop(&TV); RV.val.num += TV.val.num; break;
case SUB: vpop(&TV); RV.val.num = TV.val.num - RV.val.num; break;
case MUL: vpop(&TV); RV.val.num *= TV.val.num; break;
/* !!!??? divide by zero exceptable???? core dump */
case DIV: vpop(&TV); RV.val.num = TV.val.num / RV.val.num; break;
case CMP:
vpop(&TV);
if (IS_STRING(RV.type))
n = (0 == strcmp(MAKE_STRING(RV), MAKE_STRING(TV)));
else n = (RV.val.num == TV.val.num);
RV.type = BOOLEAN; RV.val.num = n;
break;
case NOT: RV.val.num = !RV.val.num; break;
case LT: /* (< x y) => (vpop < RV) */
vpop(&TV); RV.val.num = (TV.val.num < RV.val.num); RV.type = BOOLEAN;
break;
case LTE: /* (<= x y) => (vpop <= RV) */
vpop(&TV); RV.val.num = (TV.val.num <= RV.val.num); RV.type = BOOLEAN;
break;
case JMP: pc = addr(); continue;
case JMPTRUE:
if (RV.val.num) { pc = addr(); continue; }
addr();
break;
case JMPFALSE:
if (!RV.val.num) { pc = addr(); continue; }
addr();
break;
case ARG:
if (!apulln(&RV,(int)RV.val.num))
MMbitch("(arg n): Not that many args.");
break;
case NARGS: RV.type = NUMBER; RV.val.num = numargs; break;
case PUSHARGS:
n = RV.val.num;
while (apulln(&RV,n++)) vpush(&RV);
break;
case PUSHRV:
if (RV.type == STRING && (RV.val.str == result || vble(RV.val.str)))
RV.val.str = pushstr(RV.val.str);
else
if (RV.type & OPMASK) /* setup a fcn call */
{
switch (op.type = RV.type)
{
case OPTOKEN:
case OPXTOKEN: op.token.t = RV.val.num; break;
case OPADDRESS:
op.token.laddr = RV.val.num;
op.type = OPL_ADDRESS;
break;
case OPNAME:
op.token.name = RV.val.str; /* FADDR ensures not in result */
}
goto setop;
}
/* else just shove it */
case SHOVERV: vpush(&RV); break;
case DUP: vpop(&TV); vpush(&TV); vpush(&TV); break;
case POP: vpop(&TV); break;
case PUSHTOKEN:
op.type = OPTOKEN; op.token.t = num16();
setop:
op.abase = asp(); op.vsptr = vsptr; opush(&op);
break;
case PUSHXT: op.type = OPXTOKEN; op.token.t = num16(); goto setop;
case PUSHNAME: op.type = OPNAME; op.token.name = STR(); goto setop;
case PUSHADDR: op.type = OPADDRESS; op.token.addr = addr(); goto setop;
case FADDR:
switch(n = num8()) /* type: one of the OPxxxx code types */
{
case OPTOKEN:
case OPXTOKEN: RV.val.num = num16(); break;
case OPADDRESS:
#if CBLOCK
#else
find_block(); /* */
#endif
RV.val.num =
(((int32)MMcurrent_block << 24) | (addr() - MMblock_code()));
break;
case OPNAME: /* RV is STRING or OSTRING */
if (RV.type == OSTRING) /* protect against GC */
RV.val.str = pushstr(OBJSTRING(RV.val.object));
else /* STRING: protect against RV getting munged */
if (RV.val.str == result) RV.val.str = pushstr(RV.val.str);
break;
}
RV.type = n;
break;
case DOOP:
opop(&op); setframe(&mark,op.abase,op.vsptr);
switch(op.type)
{
case OPL_ADDRESS:
MMset_block((op.token.laddr >> 24) & 0xff);
MM(MMblock_code() + (op.token.laddr & 0xffffff));
break;
case OPADDRESS: MM(op.token.addr); break;
case OPTOKEN: dotok(op.token.t); break;
case OPNAME: RV.type = VOID; exetern(op.token.name); break;
case OPXTOKEN: RV.type = VOID; MMxtoken(op.token.t); break;
}
pop_stkframe();
break;
case TYPECHECK:
n = num8();
if (RV.type != n && !(n == STRING && RV.type == OSTRING))
MMbitch("Type mismatch");
break;
case LALLOC: lalloc(num16()); break;
case GETLVAR: /* (get-local-var type offset) */
n = num8(); /* type */
n1 = num16(); /* offset */
if (n == STRING) /* compiler should say OSTRING */
{
RV.type = OSTRING;
RV.val.object = get_lobj(n1);
}
else
if (n == LIST)
{
RV.type = LIST;
RV.val.object = get_lobj(n1);
}
else get_MMvar(&varstack[vbase + n1],n);
break;
case GETGVAR: /* (get-global-var type offset) */
n = num8(); /* type */
n1 = num16(); /* offset */
if (n == STRING) /* compiler should say OSTRING */
{
RV.type = OSTRING;
RV.val.object = MMglobal_object_table[n1];
}
else
if (n == LIST)
{
RV.type = LIST;
RV.val.object = MMglobal_object_table[n1];
}
else get_MMvar(MMglobal_vars + n1, n);
break;
case SETLVAR: /* (set-local-var type offset) */
n = num8(); /* type */
n1 = num16(); /* offset */
switch(n)
{
case STRING: /* var is a string object */
if (RV.type == STRING) /* string constant */
OMset_object(get_lobj(n1), OSTRING, RV.val.str);
else /* string object */
OMset_object(get_lobj(n1), OSTRING, OBJSTRING(RV.val.object));
break;
case LIST: /* var is a list object */
OMset_object(get_lobj(n1), LIST, RV.val.object);
break;
default: /* every other var type */
set_MMvar(&varstack[vbase + n1],n);
break;
}
break;
case SETGVAR: /* (set-global-var type offset) */
n = num8(); /* type */
n1 = num16(); /* offset */
switch(n)
{
case STRING: /* var is a string object */
if (RV.type == STRING) /* string constant */
OMset_object(MMglobal_object_table[n1], OSTRING, RV.val.str);
else /* string object */
OMset_object(MMglobal_object_table[n1],
OSTRING, OBJSTRING(RV.val.object));
break;
case LIST: /* var is a list object */
OMset_object(MMglobal_object_table[n1], LIST, RV.val.object);
break;
default: /* every other var type */
set_MMvar(MMglobal_vars + n1, n);
break;
}
break;
case RVLBASE:
RV.type = BLOB; RV.val.blob = varstack + vbase + num16(); break;
case RVGBASE:
RV.type = BLOB; RV.val.blob = MMglobal_vars + num16();
break;
case GETRVAR: /* (get-var-relative type) */
vpop(&TV); get_MMvar(RV.val.blob + TV.val.num,num8()); break;
case SETRVAR:
n = num8(); /* type */
/* !!! sleeze so I can set object args */
switch(n)
{
case STRING:
vpop(&TV);
if (TV.type != OSTRING) MMbitch("set-var-relative: wanted OSTRING!");
if (RV.type == STRING) /* string constant */
OMset_object(TV.val.object, OSTRING, RV.val.str);
else /* string object */
OMset_object(TV.val.object, OSTRING, OBJSTRING(RV.val.object));
break;
case LIST:
vpop(&TV);
if (TV.type != LIST) MMbitch("set-var-relative: wanted list!");
OMset_object(TV.val.object, LIST, RV.val.object);
break;
default:
vpop(&TV); blob = TV.val.blob; vpop(&TV);
set_MMvar(blob + TV.val.num, n);
}
break;
case CREATE_OBJ: /* (create-object global/local object-type offset) */
{
int global, type, offset;
Object *object;
global = num8();
type = num8();
offset = num16();
if (global) object = OMcreate_object(global_object_pool, type, 0);
else object = OMcreate_object( local_object_pool, type, 0);
if (!object) MMbitch("No memory to create object!");
if (global) gobj_push(object,offset);
else lobj_push(object);
break;
}
case LEN_OF: /* (length-of) */
switch (RV.type)
{
case STRING:
RV.val.num = (unsigned)strlen(RV.val.str); break;
case OSTRING:
case LIST:
RV.val.num = (unsigned)OMlength_of(RV.val.object); break;
default: RV.val.num = 0; break;
}
RV.type = NUMBER;
break;
case CONVERT_TO: /* (convert-to) */
vpop(&TV); /* type */
convert_to((int)TV.val.num, &RV);
break;
default: MMbitch("Invalid opcode");
}
incpc();
}
done: ;
}
/* Convert a MM type to another MM type.
* Valid conversions:
* NUMBER to:
* STRING or OSTRING: same as (concat). eg 123 -> "123"
* CHARACTER: 0x33 -> "3"
* BOOLEAN: 0 -> FALSE and !0 -> TRUE
* OSTRING to:
* NUMBER: "123" -> 123
* CHARACTER: "3" -> 0x33
* BOOLEAN: "TRUE" -> TRUE ?????????????????
* LIST to:
* No valid conversions.
* BOOLEAN to:
* NUMBER: TRUE -> 1 and FALSE -> 0
* STRING: TRUE -> "TRUE" ??????????????????
* BLOB to:
* No valid conversions.
* VOID to:
* No valid conversions.
* FCNPTR to:
* No valid conversions.
*
* Result
* val is converted (in place) to type.
*/
static void convert_to(type, val) MMDatum *val;
{
int vtype = val->type;
if (type == vtype || (type == OSTRING && IS_STRING(vtype))) return;
switch(vtype)
{
default:
booboo:
MMbitch("convert-to: Invalid conversion.");
break;
case NUMBER:
val->type = STRING;
switch(type)
{
default: goto booboo;
case OSTRING:
val->val.str = l_to_a((long int)val->val.num);
break;
case CHARACTER:
result[0] = (char)val->val.num; result[1] = '\0';
val->val.str = result;
break;
case BOOLEAN:
val->val.num = (val->val.num != 0);
val->type = BOOLEAN;
break;
}
break;
case STRING:
case OSTRING:
{
char *ptr = MAKE_STRING(*val);
val->type = type;
switch(type)
{
default: goto booboo;
case NUMBER:
val->val.num = atol(ptr);
break;
case CHARACTER:
val->type = NUMBER; val->val.num = ptr[0];
break;
}
break;
}
case BOOLEAN:
val->type = type;
switch(type)
{
default: goto booboo;
case NUMBER:
val->val.num = (val->val.num != 0);
break;
}
break;
}
}
/* ******************************************************************** */
/* ************************* Internal Tokens ************************** */
/* ******************************************************************** */
/* Internal tokens are like functions: they need a stack frame with
* args in it. Stack frames make it possible/easier to deal with
* functions that take a unknown number of args or type or need to
* diddle with callers (ancestor) stack frames.
* Drawbacks: takes time (and code) to create the stack frame.
*/
char *MMvtoa(val) MMDatum *val; /* MMDatum to ascii */
{
switch (val->type)
{
case BOOLEAN: return val->val.num ? "TRUE" : "FALSE";
case STRING: return val->val.str;
case VOID: return "VOID";
case NUMBER: return l_to_a((long int)val->val.num);
case OSTRING: return OBJSTRING(val->val.object);
case LIST: return "LIST";
}
return "BLOB";
}
void MMconcat() /* concatenate a bunch of strings or numbers */
{
register int n = 0;
*result = '\0';
while (apulln(&TV,n++)) strcat(result,MMvtoa(&TV));
}
static void mm_ask()
{
char prompt[RSIZ];
if (!MMgonna_ask_pgm()) { MMconcat(); strcpy(prompt,result); }
MMset_ask_frame();
if (MMask_pgm) /* grab arg off the arg stack */
{ if (!MMnext_arg(result)) MMabort_pgm(2); }
else MMask(prompt,result); /* ask the user */
RV.type = STRING; RV.val.str = result;
MMreset_ask_frame();
}
static void substr(string, n,z) char *string; int n,z;
{
OMnz_magic(strlen(string), &n,&z);
strcpy(result,&string[n]); result[z] = '\0';
RV.type = STRING; RV.val.str = result;
}
/* Input: RV contains object to extract from
*/
static void extract_em(n,z, atomize) int n,z, atomize;
{
Object *ptr;
switch(RV.type)
{
default: MMbitch("extract-element(s): invalid type!");
case STRING: substr(RV.val.str, n,z); break;
case OSTRING:
case LIST:
if (atomize)
{
if (ptr = OMnth_element(local_object_pool, RV.val.object, n))
MMconvert_to_datum(ptr, &RV);
}
else
if (ptr = OMextract_elements(local_object_pool, RV.val.object, n,z))
RV.val.object = ptr;
if (!ptr) MMbitch("extract-element(s): Out of memory!");
}
}
/* (insert-object object n new-object new-object ...)
* Notes:
* This can generate lots of garbage (if inserting NUMBERs or STRINGs).
* Have to put the garbage in a seprate pool so if a GC is done while
* I'm in the middle of the insert, they won't be collected and
* cause a core dump. I'll get rid of them later. Having the
* garbage in a seprate pool also makes it easy to get rid of them
* quickly (rather than wait for a big GC - which might be better.
* I don't know).
*/
static void insert_object()
{
int n, z;
Object *ptr;
apulln(&RV,0); /* object */
/* !!! check to make sure is object till compiler can do it for me! */
if (!is_object(RV.type)) MMbitch("insert-object: Not an object!");
apulln(&TV,1); n = TV.val.num; /* n */
for (z = 2; apulln(&TV, z++); )
{
/* !!!??? only insert one object because can't know where object ends so can
* insert next object after it
*/
ptr = convert_to_object(tmp_object_pool, &TV);
if (!ptr) continue; /* !!! not convertable or out of mem. Do what? */
OMinsert_object(RV.val.object, n++, ptr); /* !!!error check */
}
/* free all object in the temp pool */
OMgc_pool(tmp_object_pool, 1); /* live (ie none) objects are marked */
}
static void dotok(t)
{
int n,z;
switch(t)
{
case ASK: mm_ask(); break; /* (ask prompt) */
case MSG: /* (msg strings) */
MMconcat(); MMmsg(result); RV.type = STRING; RV.val.str = result; break;
case CONCAT: /* (concat string num ...) */
MMconcat(); RV.type = STRING; RV.val.str = result; break;
case INSERT_OBJ: /* (insert-object object n new-object new-object ...) */
insert_object();
break;
case EXTRACT_ELS: /* (extract-elements object n z) */
apulln(&RV,0); /* object */
apulln(&TV,1); n = TV.val.num; /* n */
apulln(&TV,2); z = TV.val.num; /* z */
extract_em(n,z, FALSE);
break;
case EXTRACT_EL: /* (extract-element object n) */
apulln(&RV,0); /* object */
apulln(&TV,1); /* n */
extract_em((int)TV.val.num,1, TRUE);
break;
case REMOVE_ELS: /* (remove-elements object n z) */
apulln(&RV,0); /* object */
apulln(&TV,1); n = TV.val.num; /* n */
apulln(&TV,2); z = TV.val.num; /* z */
if (is_object(RV.type)) OMremove_items(RV.val.object,n,z);
else MMbitch("remove-elements: invalid type!");
RV.type = VOID; /* ??? Return removed objects? */
break;
default: MMbitch("phooie");
}
}
/* ******************************************************************** */
/* ************************** The Front End *************************** */
/* ******************************************************************** */
extern maddr MMpgm_addr(); /* in mmaux() */
/* Execute external code.
* Notes
* Might switch blocks ie might need to change the global var and
* object table pointers.
* Input:
* name: name of the pgm to run.
* Result:
* MMglobal_vars and MMglobal_object_table might change.
*/
static void exetern(name) char *name; /* execute an external something */
{
int n;
if ((n = MMpgm_lookup(name)) != -1) MM(MMpgm_addr(n));
else
if (!MMaux_fcn(name))
MMbitch(strcat(strcpy(result,"Can't find pgm: "),name));
}
/* Load a compiled code file.
* Code file layout:
* Header (see mm.h)
* Code
* Routine names (a bunch of C strings)
* Routine addresses
* Notes:
* Need to set MMglobal_vars and MMglobal_object_table because a block
* lookup will not be done before we execute the MAIN code.
* If I can't add pgms (MMadd_pgm() complains), MMadd_pgm() needs to
* clean up whatever it needs to and call MMfree_block(). It can
* also just ignore pgms it can't add and return TRUE so that I'll
* go ahead and run the init code. The problem is if there are
* global objects and the init code doesn't run, they might not be
* initialized. At some later time a pgm in the block might be run,
* use a uninitialized object and the object routines might not know
* what to do with it (and bad things could happen).
* If we run out of memory or the init code doesn't run (because we
* ran out of memory), the global object table might not be
* initailized. If we then try to free the block, we may try to
* free garbage and cause all kinds of problems. A way around this
* is to initialize the global object table to NULL which the
* garbage collecters can understand (but not all the object
* commands can). The local object table doesn't have this problem
* because it only expands as objects are placed into it (hence no
* junk in it).
* Input:
* fname : Name of the file that has compiled Mutt code in it. The
* extension is changed to .mco and the application will open it (so
* it can do any path searching it wants to).
* complain: TRUE if you want me to print a message if fname can't be
* opened. All error messages (memory problems, etc) will always
* generate messages.
* Returns:
* NULL : Couldn't open fname, out of memory, the code is out of sync
* with this version of MM, etc.
* entry_point : the address of the start up code in the loaded block.
* Munges:
* MMglobal_object_table:
* MMglobal_vars:
* Points to the global var tables for the new block. Ready to
* run the blocks init code.
* Side effects:
* MMset_hooks() is called.
* Notes:
* !!! Need to check for fread() errors!
* Since MMglobal_object_table and MMglobal_vars are changed, you need
* to set a stack frame before call this routine.
*/
#define ABC 150 /* max number of addresses in I can get per read */
maddr MMload_code(fname,complain) char *fname;
{
extern FILE *MMopen_code_file();
address z;
char *block, *nmptr, buf[250];
FILE *fptr;
int j, block_id, num_pgms, num_global_objects;
uint8 bytes[ABC*sizeof(address)], *qtr, *global_vars;
unsigned int code_size, nmoffset, global_var_space;
maddr code, entrypt;
Object **global_object_table;
/* open the code file */
new_ext(buf,fname,".mco");
if ((fptr = MMopen_code_file(buf)) == NULL)
{ if (complain) MMmoan("Can't open code file"); return NULL; }
/* read and parse the header !!! error check*/
fread((char *)bytes,1,BYTES_IN_HEADER,fptr);
if (MM_MAGIC_NUMBER != GET_UINT8(&bytes[H_MAGIC_NUMBER]))
{ MMmoan("Versionits - recompile Mutt code."); goto booboo; }
z = GET_ADDRESS(&bytes[H_ENTRY_POINT]);
code_size = GET_UINT16 (&bytes[H_BYTES_OF_CODE]);
nmoffset = GET_UINT16 (&bytes[H_NAME_TABLE_OFFSET]);
num_pgms = GET_INT16 (&bytes[H_NUMBER_OF_PGMS]);
global_var_space = GET_UINT16 (&bytes[H_BYTES_OF_GVARS]);
num_global_objects = GET_UINT16 (&bytes[H_NUM_GLOBAL_OBJECTS]);
/* set vars for error handling */
global_object_table = NULL;
block = NULL;
/* take care of global objects:
* Object *MMglobal_object_table[num_global_objects];
* Note: there may not be any global objects.
* Zero out the pointers in case something fails.
*/
if (num_global_objects && NULL ==
(global_object_table =
(Object **)calloc(num_global_objects, sizeof(Object *))))
{
MMmoan("Can't allocate global object table");
goto booboo;
}
/* calculate size of code, name table and global vars */
if ((block = malloc(code_size + global_var_space)) == NULL)
{
MMmoan("Can't malloc code");
goto booboo;
}
/* Get the code, strings and name table. !!! error check */
fread(block,1,code_size,fptr);
code = (maddr)block;
entrypt = code + z;
nmptr = block + nmoffset;
global_vars = (uint8 *)(block + code_size);
/* create the block name and block */
MMblock_name(buf,fname);
if (-1 ==
(block_id = MMadd_block(buf,code, global_vars,
global_object_table, num_global_objects)))
{
booboo:
if (global_object_table) free((char *)MMglobal_object_table);
if (block) free((char *)block);
booboo1:
fclose(fptr);
return NULL;
}
/* add routine entry points (name, block_id, address) */
while (num_pgms)
{
j = (num_pgms < ABC) ? num_pgms : ABC; /* read as many as can/left */
num_pgms -= j; qtr = bytes;
fread(qtr,sizeof(address),j,fptr); /* !!! should test for NULL */
for (; j--; qtr += sizeof(address))
{
z = GET_ADDRESS(qtr); /* offset */
if (!MMadd_pgm(nmptr, block_id, code + z)) goto booboo1;
while (*nmptr++ != '\0') ; /* point to next name */
}
}
/* zero the global vars */
#if 1
memset((char *)global_vars, 0, global_var_space);
#else
for (j = 0; j < global_var_space; j++) global_vars[j] = 0;
#endif
fclose(fptr);
MMset_hooks();
MMset_block(block_id);
return entrypt;
}
/* free the code block allocated in MMload_code() */
void MMfree_block(code, block_object_table, objects_in_table)
maddr code;
Object *block_object_table[];
int objects_in_table;
{
/* gc the block objects */
gc_globals(block_object_table, objects_in_table);
if (block_object_table) free((char *)block_object_table);
free((char *)code);
}
/* ******************************************************************** */
/* ****************** Outside Access to Mutt Machine ****************** */
/* ******************************************************************** */
static jmp_buf env;
static int pgm_level = 0; /* keep track of recursion level for setjmp */
/* Is a pgm running?
* Returns:
* FALSE: no pgm running.
* n : n pgms are running (interrupts, (load) can cause more than one
* pgm to be running at the same time.
*/
MMpgm_running() { return pgm_level; }
/* Execute Mutt code: This is the front end to the Mutt Machine. This
* is the ONLY routine that calls MM() other than MM itself (exetern()
* is considered part of MM).
* If a child (sub pgm, etc) dies, the parent/everybody dies.
* Input:
* entrypt : address of Mutt code to be executed.
* Returns:
* TRUE : pgm ran to completation
* FALSE: pgm aborted
* Notes:
* Caller MUST have set up a stack frame!
* Hooks or interrupts can cause this code to recurse.
* I am careful to save MMask_pgm from reentrent or recursive code
* that is run while another pgm is running.
* A long jump buffer is set up so we can return here if the code
* aborts (by calling MMabort_pgm()).
* This routine is not called much (only by the application to run a
* program) so I don't have to worry (much) about speed.
* When MM() returns, a complete program has run (since MM doesn't
* call this).
* After a program has finished running, need to gc, reset and
* generally clean up. If recursing, only gc the local objects -
* can't reset because that would mess up the stack for the other
* running program. When no programs are running, can reset
* everything.
*/
int MMexecode(entrypt) maddr entrypt;
{
int old_ask_pgm = MMask_pgm;
MMask_pgm = TRUE;
pgm_level++;
if (pgm_level != 1 || setjmp(env) == 0)
{
MM(entrypt); /* might longjmp() */
/* pgm ran to completion */
pgm_level--;
MMask_pgm = old_ask_pgm;
pop_stkframe();
if (pgm_level == 0) reset_MM(FALSE);
else OMgc_pool(local_object_pool, 0);
return TRUE;
}
/* Pgm aborted. MMabort_pgm() resets stacks, MMask_pgm, objects, etc */
pgm_level = 0;
return FALSE;
}
/* Heres where the outside world fires off a Mutt pgm.
* Returns FALSE if pgm aborts.
* Note:
* If MMrun_pgm() gets called recursively and then aborts,
* everything is aborted.
*/
MMrun_pgm(n) /* run the nth Mutt pgm */
{
MMStkFrame mark;
setframe(&mark, asp(), vsptr);
return MMexecode(MMpgm_addr(n));
}
/* Next rouines allow external things to set up a stack frame,
* push args, etc and then run a pgm with that frame.
* Sequence: open frame, push args, run with args or (close frame
* and load).
* Note: guard your stack frame against recursion.
*/
void MMopen_frame(mark) MMStkFrame *mark;
{ mark->startframe = asp(); mark->vsptr = vsptr; }
void MMpush_arg(RV) MMDatum *RV;
{
if (RV->type == STRING) RV->val.str = pushstr(RV->val.str);
vpush(RV);
}
void MMclose_frame(mark) MMStkFrame *mark;
{ setframe(mark,mark->startframe,mark->vsptr); }
/* run the nth Mutt pgm with args */
MMrun_pgm_with_args(n,mark) MMStkFrame *mark;
{
MMclose_frame(mark);
return MMexecode(MMpgm_addr(n));
}
/* Load a code file (block) and run the MAIN code.
* Input:
* fname: Name of the file that contains the code. The application
* knows how to interpret this.
* complain: TRUE: Complain if can't open fname.
* Output:
* TRUE: Block loaded and MAIN code ran to completion.
* FALSE: Block didn't load, MAIN didn't run or something else failed.
* A message was probably issued.
* Notes
* Maintaining a stack frame here is sometimes redundant. If called
* from a pgm, the callee already has a stack frame. When called
* called directly from an application, it may or may not be as a
* result of a running pgm. If so, got a stack frame. If not, a
* call to init_stacks() after that last pgm is run would take care
* of everything.
* Since MMload_code() changes the global var pointers, need to set up
* a stack frame before that call.
*/
MMload(fname,complain) char *fname;
{
maddr entrypt;
MMStkFrame mark;
setframe(&mark,asp(),vsptr);
if ((entrypt = MMload_code(fname,complain)) != NULL)
return MMexecode(entrypt);
pop_stkframe(); /* didn't load code: reset global var pointers */
return FALSE;
}
/* Initialize the Mutt Machine. This is called by the application ONCE
* so MM can set things up.
* Call this AFTER everything else in your application has been
* initialized (this might call back into the application).
* Returns:
* TRUE: everything went as expected.
* FALSE: Mutt Machine can't be initialized, don't use it!
*/
int MMinitialize()
{
init_stacks();
if (!(local_object_pool = OMcreate_object_pool(local_gc_marker)) ||
!(global_object_pool = OMcreate_object_pool((pfi)NULL)) ||
!(tmp_object_pool = OMcreate_object_pool((pfi)NULL)))
return FALSE;
/* ??? malloc result? */
return TRUE;
}
/* ******************************************************************** */
/* ****************** Error handling ********************************** */
/* ******************************************************************** */
/* dump levels:
* 0 - No nuthin
* n - Implementer defined
*/
void MMabort_pgm(dump_level)
{
/* if (dump_level) MMtrace_back(dump_level); /* ??? */
#if 0
if (dump_level)
{
int n = 0;
MMStkFrame *mark;
mark = prev_stkframe;
/* how tell if current frame && prev frame same??? */
if (pcat() != mark->pc || MMcurrent_block != mark->block_id)
MMtrace_back(0, n++, MMcurrent_block, pcat()); /* */
for (mark = prev_stkframe; mark; mark = mark->prev_stkframe)
{
if (0 == mark->pc) break;
MMtrace_back(1, n++, mark->block_id, mark->pc);
}
}
#endif
reset_MM(TRUE);
longjmp(env,1);
}