UNIXworkcode

/******************************************************************************* * * * interpret.c -- Nirvana Editor macro interpreter * * * * Copyright (C) 1999 Mark Edel * * * * This is free software; you can redistribute it and/or modify it under the * * terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. In addition, you may distribute version of this program linked to * * Motif or Open Motif. See README for details. * * * * This software is distributed in the hope that it will be useful, but WITHOUT * * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * * for more details. * * * * You should have received a copy of the GNU General Public License along with * * software; if not, write to the Free Software Foundation, Inc., 59 Temple * * Place, Suite 330, Boston, MA 02111-1307 USA * * * * Nirvana Text Editor * * April, 1997 * * * * Written by Mark Edel * * * *******************************************************************************/ #ifdef HAVE_CONFIG_H #include "../config.h" #endif #include "interpret.h" #include "textBuf.h" #include "nedit.h" #include "menu.h" #include "text.h" #include "../util/rbTree.h" #include "../util/nedit_malloc.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #include <limits.h> #include <ctype.h> #include <errno.h> #ifndef __MVS__ #include <sys/param.h> #endif #include <X11/Intrinsic.h> #include <Xm/Xm.h> #include "window.h" #ifdef HAVE_DEBUG_H #include "../debug.h" #endif #define PROGRAM_SIZE 4096 /* Maximum program size */ #define MAX_ERR_MSG_LEN 256 /* Max. length for error messages */ #define LOOP_STACK_SIZE 200 /* (Approx.) Number of break/continue stmts allowed per program */ #define INSTRUCTION_LIMIT 100 /* Number of instructions the interpreter is allowed to execute before preempting and returning to allow other things to run */ /* Temporary markers placed in a branch address location to designate which loop address (break or continue) the location needs */ #define NEEDS_BREAK 1 #define NEEDS_CONTINUE 2 #define N_ARGS_ARG_SYM -1 /* special arg number meaning $n_args value */ enum opStatusCodes {STAT_OK=2, STAT_DONE, STAT_ERROR, STAT_PREEMPT}; static void addLoopAddr(Inst *addr); static void saveContext(RestartData *context); static void restoreContext(RestartData *context); static int returnNoVal(void); static int returnVal(void); static int returnValOrNone(int valOnStack); static int pushSymVal(void); static int pushArgVal(void); static int pushArgCount(void); static int pushArgArray(void); static int pushArraySymVal(void); static int dupStack(void); static int add(void); static int subtract(void); static int multiply(void); static int divide(void); static int modulo(void); static int negate(void); static int increment(void); static int decrement(void); static int gt(void); static int lt(void); static int ge(void); static int le(void); static int eq(void); static int ne(void); static int bitAnd(void); static int bitOr(void); static int and(void); static int or(void); static int not(void); static int power(void); static int concat(void); static int assign(void); static int callSubroutine(void); static int fetchRetVal(void); static int branch(void); static int branchTrue(void); static int branchFalse(void); static int branchNever(void); static int arrayRef(void); static int arrayAssign(void); static int arrayRefAndAssignSetup(void); static int beginArrayIter(void); static int arrayIter(void); static int inArray(void); static int deleteArrayElement(void); static void freeSymbolTable(Symbol *symTab); static int errCheck(const char *s); static int execError(const char *s1, const char *s2); static rbTreeNode *arrayEmptyAllocator(void); static rbTreeNode *arrayAllocateNode(rbTreeNode *src); static int arrayEntryCopyToNode(rbTreeNode *dst, rbTreeNode *src); static int arrayEntryCompare(rbTreeNode *left, rbTreeNode *right); static void arrayDisposeNode(rbTreeNode *src); static SparseArrayEntry *allocateSparseArrayEntry(void); /*#define DEBUG_ASSEMBLY*/ /*#define DEBUG_STACK*/ #if defined(DEBUG_ASSEMBLY) || defined(DEBUG_STACK) #define DEBUG_DISASSEMBLER static void disasm(Inst *inst, int nInstr); #endif /* #if defined(DEBUG_ASSEMBLY) || defined(DEBUG_STACK) */ #ifdef DEBUG_ASSEMBLY /* for disassembly */ #define DISASM(i, n) disasm(i, n) #else /* #ifndef DEBUG_ASSEMBLY */ #define DISASM(i, n) #endif /* #ifndef DEBUG_ASSEMBLY */ #ifdef DEBUG_STACK /* for run-time instruction and stack trace */ static void stackdump(int n, int extra); #define STACKDUMP(n, x) stackdump(n, x) #define DISASM_RT(i, n) disasm(i, n) #else /* #ifndef DEBUG_STACK */ #define STACKDUMP(n, x) #define DISASM_RT(i, n) #endif /* #ifndef DEBUG_STACK */ /* Global symbols and function definitions */ static Symbol *GlobalSymList = NULL; /* List of all memory allocated for strings */ static char *AllocatedStrings = NULL; typedef struct SparseArrayEntryWrapperTag { SparseArrayEntry data; /* LEAVE this as top entry */ int inUse; /* we use pointers to the data to refer to the entire struct */ struct SparseArrayEntryWrapperTag *next; } SparseArrayEntryWrapper; static SparseArrayEntryWrapper *AllocatedSparseArrayEntries = NULL; /* Message strings used in macros (so they don't get repeated every time the macros are used */ static const char *StackOverflowMsg = "macro stack overflow"; static const char *StackUnderflowMsg = "macro stack underflow"; static const char *StringToNumberMsg = "string could not be converted to number"; /* Temporary global data for use while accumulating programs */ static Symbol *LocalSymList = NULL; /* symbols local to the program */ static Inst Prog[PROGRAM_SIZE]; /* the program */ static Inst *ProgP; /* next free spot for code gen. */ static Inst *LoopStack[LOOP_STACK_SIZE]; /* addresses of break, cont stmts */ static Inst **LoopStackPtr = LoopStack; /* to fill at the end of a loop */ /* Global data for the interpreter */ static DataValue *TheStack; /* the stack */ static DataValue *StackP; /* next free spot on stack */ static DataValue *FrameP; /* frame pointer (start of local variables for the current subroutine invocation) */ static Inst *PC; /* program counter during execution */ static char *ErrMsg; /* global for returning error messages from executing functions */ static WindowInfo *InitiatingWindow = NULL; /* window from which macro was run */ static WindowInfo *FocusWindow; /* window on which macro commands operate */ static int PreemptRequest; /* passes preemption requests from called routines back up to the interpreter */ /* Array for mapping operations to functions for performing the operations Must correspond to the enum called "operations" in interpret.h */ static int (*OpFns[N_OPS])() = {returnNoVal, returnVal, pushSymVal, dupStack, add, subtract, multiply, divide, modulo, negate, increment, decrement, gt, lt, ge, le, eq, ne, bitAnd, bitOr, and, or, not, power, concat, assign, callSubroutine, fetchRetVal, branch, branchTrue, branchFalse, branchNever, arrayRef, arrayAssign, beginArrayIter, arrayIter, inArray, deleteArrayElement, pushArraySymVal, arrayRefAndAssignSetup, pushArgVal, pushArgCount, pushArgArray}; /* Stack-> symN-sym0(FP), argArray, nArgs, oldFP, retPC, argN-arg1, next, ... */ #define FP_ARG_ARRAY_CACHE_INDEX (-1) #define FP_ARG_COUNT_INDEX (-2) #define FP_OLD_FP_INDEX (-3) #define FP_RET_PC_INDEX (-4) #define FP_TO_ARGS_DIST (4) /* should be 0 - (above index) */ #define FP_GET_ITEM(xFrameP,xIndex) (*(xFrameP + xIndex)) #define FP_GET_ARG_ARRAY_CACHE(xFrameP) (FP_GET_ITEM(xFrameP, FP_ARG_ARRAY_CACHE_INDEX)) #define FP_GET_ARG_COUNT(xFrameP) (FP_GET_ITEM(xFrameP, FP_ARG_COUNT_INDEX).val.n) #define FP_GET_OLD_FP(xFrameP) ((FP_GET_ITEM(xFrameP, FP_OLD_FP_INDEX)).val.dataval) #define FP_GET_RET_PC(xFrameP) ((FP_GET_ITEM(xFrameP, FP_RET_PC_INDEX)).val.inst) #define FP_ARG_START_INDEX(xFrameP) (-(FP_GET_ARG_COUNT(xFrameP) + FP_TO_ARGS_DIST)) #define FP_GET_ARG_N(xFrameP,xN) (FP_GET_ITEM(xFrameP, xN + FP_ARG_START_INDEX(xFrameP))) #define FP_GET_SYM_N(xFrameP,xN) (FP_GET_ITEM(xFrameP, xN)) #define FP_GET_SYM_VAL(xFrameP,xSym) (FP_GET_SYM_N(xFrameP, xSym->value.val.n)) /* ** Initialize macro language global variables. Must be called before ** any macros are even parsed, because the parser uses action routine ** symbols to comprehend hyphenated names. */ void InitMacroGlobals(void) { XtActionsRec *actions; int i, nActions; static char argName[3] = "$x"; static DataValue dv = {NO_TAG, {0}}; /* Add action routines from NEdit menus and text widget */ actions = GetMenuActions(&nActions); for (i=0; i<nActions; i++) { dv.val.xtproc = actions[i].proc; InstallSymbol(actions[i].string, ACTION_ROUTINE_SYM, dv); } actions = TextGetActions(&nActions); for (i=0; i<nActions; i++) { dv.val.xtproc = actions[i].proc; InstallSymbol(actions[i].string, ACTION_ROUTINE_SYM, dv); } /* Add subroutine argument symbols ($1, $2, ..., $9) */ for (i=0; i<9; i++) { argName[1] = '1' + i; dv.val.n = i; InstallSymbol(argName, ARG_SYM, dv); } /* Add special symbol $n_args */ dv.val.n = N_ARGS_ARG_SYM; InstallSymbol("$n_args", ARG_SYM, dv); } /* ** To build a program for the interpreter, call BeginCreatingProgram, to ** begin accumulating the program, followed by calls to AddOp, AddSym, ** and InstallSymbol to add symbols and operations. When the new program ** is finished, collect the results with FinishCreatingProgram. This returns ** a self contained program that can be run with ExecuteMacro. */ /* ** Start collecting instructions for a program. Clears the program ** and the symbol table. */ void BeginCreatingProgram(void) { LocalSymList = NULL; ProgP = Prog; LoopStackPtr = LoopStack; } /* ** Finish up the program under construction, and return it (code and ** symbol table) as a package that ExecuteMacro can execute. This ** program must be freed with FreeProgram. */ Program *FinishCreatingProgram(void) { Program *newProg; int progLen, fpOffset = 0; Symbol *s; newProg = (Program *)NEditMalloc(sizeof(Program)); progLen = ((char *)ProgP) - ((char *)Prog); newProg->code = (Inst *)NEditMalloc(progLen); memcpy(newProg->code, Prog, progLen); newProg->localSymList = LocalSymList; LocalSymList = NULL; /* Local variables' values are stored on the stack. Here we assign frame pointer offsets to them. */ for (s = newProg->localSymList; s != NULL; s = s->next) s->value.val.n = fpOffset++; DISASM(newProg->code, ProgP - Prog); return newProg; } void FreeProgram(Program *prog) { freeSymbolTable(prog->localSymList); NEditFree(prog->code); NEditFree(prog); } /* ** Add an operator (instruction) to the end of the current program */ int AddOp(int op, char **msg) { if (ProgP >= &Prog[PROGRAM_SIZE]) { *msg = "macro too large"; return 0; } ProgP->func = OpFns[op]; ProgP++; return 1; } /* ** Add a symbol operand to the current program */ int AddSym(Symbol *sym, char **msg) { if (ProgP >= &Prog[PROGRAM_SIZE]) { *msg = "macro too large"; return 0; } ProgP->sym = sym; ProgP++; return 1; } /* ** Add an immediate value operand to the current program */ int AddImmediate(int value, char **msg) { if (ProgP >= &Prog[PROGRAM_SIZE]) { *msg = "macro too large"; return 0; } ProgP->value = value; ProgP++; return 1; } /* ** Add a branch offset operand to the current program */ int AddBranchOffset(Inst *to, char **msg) { if (ProgP >= &Prog[PROGRAM_SIZE]) { *msg = "macro too large"; return 0; } /* Should be ptrdiff_t for branch offsets */ ProgP->value = to - ProgP; ProgP++; return 1; } /* ** Return the address at which the next instruction will be stored */ Inst *GetPC(void) { return ProgP; } /* ** Swap the positions of two contiguous blocks of code. The first block ** running between locations start and boundary, and the second between ** boundary and end. */ void SwapCode(Inst *start, Inst *boundary, Inst *end) { #define reverseCode(L, H) \ do { register Inst t, *l = L, *h = H - 1; \ while (l < h) { t = *h; *h-- = *l; *l++ = t; } } while (0) /* double-reverse method: reverse elements of both parts then whole lot */ /* eg abcdefABCD -1-> edcbaABCD -2-> edcbaDCBA -3-> DCBAedcba */ reverseCode(start, boundary); /* 1 */ reverseCode(boundary, end); /* 2 */ reverseCode(start, end); /* 3 */ } /* ** Maintain a stack to save addresses of branch operations for break and ** continue statements, so they can be filled in once the information ** on where to branch is known. ** ** Call StartLoopAddrList at the beginning of a loop, AddBreakAddr or ** AddContinueAddr to register the address at which to store the branch ** address for a break or continue statement, and FillLoopAddrs to fill ** in all the addresses and return to the level of the enclosing loop. */ void StartLoopAddrList(void) { addLoopAddr(NULL); } int AddBreakAddr(Inst *addr) { if (LoopStackPtr == LoopStack) return 1; addLoopAddr(addr); addr->value = NEEDS_BREAK; return 0; } int AddContinueAddr(Inst *addr) { if (LoopStackPtr == LoopStack) return 1; addLoopAddr(addr); addr->value = NEEDS_CONTINUE; return 0; } static void addLoopAddr(Inst *addr) { if (LoopStackPtr > &LoopStack[LOOP_STACK_SIZE-1]) { fprintf(stderr, "NEdit: loop stack overflow in macro parser"); return; } *LoopStackPtr++ = addr; } void FillLoopAddrs(Inst *breakAddr, Inst *continueAddr) { while (True) { LoopStackPtr--; if (LoopStackPtr < LoopStack) { fprintf(stderr, "NEdit: internal error (lsu) in macro parser\n"); return; } if (*LoopStackPtr == NULL) break; if ((*LoopStackPtr)->value == NEEDS_BREAK) (*LoopStackPtr)->value = breakAddr - *LoopStackPtr; else if ((*LoopStackPtr)->value == NEEDS_CONTINUE) (*LoopStackPtr)->value = continueAddr - *LoopStackPtr; else fprintf(stderr, "NEdit: internal error (uat) in macro parser\n"); } } /* ** Execute a compiled macro, "prog", using the arguments in the array ** "args". Returns one of MACRO_DONE, MACRO_PREEMPT, or MACRO_ERROR. ** if MACRO_DONE is returned, the macro completed, and the returned value ** (if any) can be read from "result". If MACRO_PREEMPT is returned, the ** macro exceeded its alotted time-slice and scheduled... */ int ExecuteMacro(WindowInfo *window, Program *prog, int nArgs, DataValue *args, DataValue *result, RestartData **continuation, char **msg) { RestartData *context; static DataValue noValue = {NO_TAG, {0}}; Symbol *s; int i; /* Create an execution context (a stack, a stack pointer, a frame pointer, and a program counter) which will retain the program state across preemption and resumption of execution */ context = (RestartData *)NEditMalloc(sizeof(RestartData)); context->stack = (DataValue *)NEditMalloc(sizeof(DataValue) * STACK_SIZE); *continuation = context; context->stackP = context->stack; context->pc = prog->code; context->runWindow = window; context->focusWindow = window; /* Push arguments and call information onto the stack */ for (i=0; i<nArgs; i++) *(context->stackP++) = args[i]; context->stackP->val.subr = NULL; /* return PC */ context->stackP->tag = NO_TAG; context->stackP++; *(context->stackP++) = noValue; /* old FrameP */ context->stackP->tag = NO_TAG; /* nArgs */ context->stackP->val.n = nArgs; context->stackP++; *(context->stackP++) = noValue; /* cached arg array */ context->frameP = context->stackP; /* Initialize and make room on the stack for local variables */ for (s = prog->localSymList; s != NULL; s = s->next) { FP_GET_SYM_VAL(context->frameP, s) = noValue; context->stackP++; } /* Begin execution, return on error or preemption */ return ContinueMacro(context, result, msg); } /* ** Continue the execution of a suspended macro whose state is described in ** "continuation" */ int ContinueMacro(RestartData *continuation, DataValue *result, char **msg) { register int status, instCount = 0; register Inst *inst; RestartData oldContext; /* To allow macros to be invoked arbitrarily (such as those automatically triggered within smart-indent) within executing macros, this call is reentrant. */ saveContext(&oldContext); /* ** Execution Loop: Call the succesive routine addresses in the program ** until one returns something other than STAT_OK, then take action */ restoreContext(continuation); ErrMsg = NULL; for (;;) { /* Execute an instruction */ inst = PC++; status = (inst->func)(); /* If error return was not STAT_OK, return to caller */ if (status != STAT_OK) { if (status == STAT_PREEMPT) { saveContext(continuation); restoreContext(&oldContext); return MACRO_PREEMPT; } else if (status == STAT_ERROR) { *msg = ErrMsg; FreeRestartData(continuation); restoreContext(&oldContext); return MACRO_ERROR; } else if (status == STAT_DONE) { *msg = ""; *result = *--StackP; FreeRestartData(continuation); restoreContext(&oldContext); return MACRO_DONE; } } /* Count instructions executed. If the instruction limit is hit, preempt, store re-start information in continuation and give X, other macros, and other shell scripts a chance to execute */ instCount++; if (instCount >= INSTRUCTION_LIMIT) { saveContext(continuation); restoreContext(&oldContext); return MACRO_TIME_LIMIT; } } } /* ** If a macro is already executing, and requests that another macro be run, ** this can be called instead of ExecuteMacro to run it in the same context ** as if it were a subroutine. This saves the caller from maintaining ** separate contexts, and serializes processing of the two macros without ** additional work. */ void RunMacroAsSubrCall(Program *prog) { Symbol *s; static DataValue noValue = {NO_TAG, {0}}; /* See subroutine "callSubroutine" for a description of the stack frame for a subroutine call */ StackP->tag = NO_TAG; StackP->val.inst = PC; /* return PC */ StackP++; StackP->tag = NO_TAG; StackP->val.dataval = FrameP; /* old FrameP */ StackP++; StackP->tag = NO_TAG; /* nArgs */ StackP->val.n = 0; StackP++; *(StackP++) = noValue; /* cached arg array */ FrameP = StackP; PC = prog->code; for (s = prog->localSymList; s != NULL; s = s->next) { FP_GET_SYM_VAL(FrameP, s) = noValue; StackP++; } } void FreeRestartData(RestartData *context) { NEditFree(context->stack); NEditFree(context); } /* ** Cause a macro in progress to be preempted (called by commands which take ** a long time, or want to return to the event loop. Call ResumeMacroExecution ** to resume. */ void PreemptMacro(void) { PreemptRequest = True; } /* ** Reset the return value for a subroutine which caused preemption (this is ** how to return a value from a routine which preempts instead of returning ** a value directly). */ void ModifyReturnedValue(RestartData *context, DataValue dv) { if ((context->pc-1)->func == fetchRetVal) *(context->stackP-1) = dv; } /* ** Called within a routine invoked from a macro, returns the window in ** which the macro is executing (where the banner is, not where it is focused) */ WindowInfo *MacroRunWindow(void) { return InitiatingWindow; } /* ** Called within a routine invoked from a macro, returns the window to which ** the currently executing macro is focused (the window which macro commands ** modify, not the window from which the macro is being run) */ WindowInfo *MacroFocusWindow(void) { return FocusWindow; } /* ** Set the window to which macro subroutines and actions which operate on an ** implied window are directed. */ void SetMacroFocusWindow(WindowInfo *window) { FocusWindow = window; } /* ** install an array iteration symbol ** it is tagged as an integer but holds an array node pointer */ #define ARRAY_ITER_SYM_PREFIX "aryiter " Symbol *InstallIteratorSymbol(void) { char symbolName[sizeof(ARRAY_ITER_SYM_PREFIX) + TYPE_INT_STR_SIZE(int)]; DataValue value; static int interatorNameIndex = 0; sprintf(symbolName, ARRAY_ITER_SYM_PREFIX "#%d", interatorNameIndex); ++interatorNameIndex; value.tag = INT_TAG; value.val.arrayPtr = NULL; return(InstallSymbol(symbolName, LOCAL_SYM, value)); } /* ** Lookup a constant string by its value. This allows reuse of string ** constants and fixing a leak in the interpreter. */ Symbol *LookupStringConstSymbol(const char *value) { Symbol *s; for (s = GlobalSymList; s != NULL; s = s->next) { if (s->type == CONST_SYM && s->value.tag == STRING_TAG && !strcmp(s->value.val.str.rep, value)) { return(s); } } return(NULL); } /* ** install string str in the global symbol table with a string name */ Symbol *InstallStringConstSymbol(const char *str) { static int stringConstIndex = 0; char stringName[35]; DataValue value; Symbol *sym = LookupStringConstSymbol(str); if (sym) { return sym; } sprintf(stringName, "string #%d", stringConstIndex++); value.tag = STRING_TAG; AllocNStringCpy(&value.val.str, str); return(InstallSymbol(stringName, CONST_SYM, value)); } /* ** find a symbol in the symbol table */ Symbol *LookupSymbol(const char *name) { Symbol *s; for (s = LocalSymList; s != NULL; s = s->next) if (strcmp(s->name, name) == 0) return s; for (s = GlobalSymList; s != NULL; s = s->next) if (strcmp(s->name, name) == 0) return s; return NULL; } /* ** install symbol name in symbol table */ Symbol *InstallSymbol(const char *name, enum symTypes type, DataValue value) { Symbol *s; s = (Symbol *)NEditMalloc(sizeof(Symbol)); s->name = NEditStrdup(name); s->type = type; s->value = value; if (type == LOCAL_SYM) { s->next = LocalSymList; LocalSymList = s; } else { s->next = GlobalSymList; GlobalSymList = s; } return s; } /* ** Promote a symbol from local to global, removing it from the local symbol ** list. ** ** This is used as a forward declaration feature for macro functions. ** If a function is called (ie while parsing the macro) where the ** function isn't defined yet, the symbol is put into the GlobalSymList ** so that the function definition uses the same symbol. ** */ Symbol *PromoteToGlobal(Symbol *sym) { Symbol *s; if (sym->type != LOCAL_SYM) return sym; /* Remove sym from the local symbol list */ if (sym == LocalSymList) LocalSymList = sym->next; else { for (s = LocalSymList; s != NULL; s = s->next) { if (s->next == sym) { s->next = sym->next; break; } } } /* There are two scenarios which could make this check succeed: a) this sym is in the GlobalSymList as a LOCAL_SYM symbol b) there is another symbol as a non-LOCAL_SYM in the GlobalSymList Both are errors, without question. We currently just print this warning, but we should error out the parsing process. */ s = LookupSymbol(sym->name); if (sym == s) { /* case a) just make this symbol a GLOBAL_SYM symbol and return */ fprintf(stderr, "xnedit: To boldly go where no local sym has gone before: %s\n", sym->name); sym->type = GLOBAL_SYM; return sym; } else if (NULL != s) { /* case b) sym will shadow the old symbol from the GlobalSymList */ fprintf(stderr, "xnedit: duplicate symbol in LocalSymList and GlobalSymList: %s\n", sym->name); } /* Add the symbol directly to the GlobalSymList, because InstallSymbol() will allocate a new Symbol, which results in a memory leak of sym. Don't use MACRO_FUNCTION_SYM as type, because in macro.c:readCheckMacroString() we use ProgramFree() for the .val.prog, but this symbol has no program attached and ProgramFree() is not NULL pointer safe */ sym->type = GLOBAL_SYM; sym->next = GlobalSymList; GlobalSymList = sym; return sym; } /* ** Allocate memory for a string, and keep track of it, such that it ** can be recovered later using GarbageCollectStrings. (A linked list ** of pointers is maintained by threading through the memory behind ** the returned pointers). Length does not include the terminating null ** character, so to allocate space for a string of strlen == n, you must ** use AllocString(n+1). */ /*#define TRACK_GARBAGE_LEAKS*/ #ifdef TRACK_GARBAGE_LEAKS static int numAllocatedStrings = 0; static int numAllocatedSparseArrayElements = 0; #endif /* Allocate a new string buffer of length chars */ char *AllocString(int length) { char *mem; mem = (char*)NEditMalloc(length + sizeof(char *) + 1); *((char **)mem) = AllocatedStrings; AllocatedStrings = mem; #ifdef TRACK_GARBAGE_LEAKS ++numAllocatedStrings; #endif return mem + sizeof(char *) + 1; } /* * Allocate a new NString buffer of length chars (terminating \0 included), * The buffer length is initialized to length-1 and the terminating \0 is * filled in. */ int AllocNString(NString *string, int length) { char *mem; mem = (char*)NEditMalloc(length + sizeof(char *) + 1); if (!mem) { string->rep = 0; string->len = 0; return False; } *((char **)mem) = AllocatedStrings; AllocatedStrings = mem; #ifdef TRACK_GARBAGE_LEAKS ++numAllocatedStrings; #endif string->rep = mem + sizeof(char *) + 1; string->rep[length-1] = '\0'; /* forced \0 */ string->len = length-1; return True; } /* Allocate a new string buffer of length chars, and copy in the string s */ char *AllocStringNCpy(const char *s, int length) { char *p = AllocString(length + 1); /* add extra char for forced \0 */ if (!p) return p; if (!s) s = ""; p[length] = '\0'; /* forced \0 */ return strncpy(p, s, length); } /* * Allocate a new NString buffer of length chars (terminating \0 NOT included), * and copy at most length characters of the given string. * The buffer length is properly set and the buffer is guaranteed to be * \0-terminated. */ int AllocNStringNCpy(NString *string, const char *s, int length) { if (!AllocNString(string, length + 1)) /* add extra char for forced \0 */ return False; if (!s) s = ""; strncpy(string->rep, s, length); string->len = strlen(string->rep); /* re-calculate! */ return True; } /* Allocate a new copy of string s */ char *AllocStringCpy(const char *s) { return AllocStringNCpy(s, s ? strlen(s) : 0); } /* * Allocate a new NString buffer, containing a copy of the given string. * The length is set to the length of the string and resulting string is * guaranteed to be \0-terminated. */ int AllocNStringCpy(NString *string, const char *s) { size_t length = s ? strlen(s) : 0; if (!AllocNString(string, length + 1)) return False; if (s) strncpy(string->rep, s, length); return True; } static SparseArrayEntry *allocateSparseArrayEntry(void) { SparseArrayEntryWrapper *mem; mem = (SparseArrayEntryWrapper *)NEditMalloc(sizeof(SparseArrayEntryWrapper)); mem->next = AllocatedSparseArrayEntries; AllocatedSparseArrayEntries = mem; #ifdef TRACK_GARBAGE_LEAKS ++numAllocatedSparseArrayElements; #endif return(&(mem->data)); } static void MarkArrayContentsAsUsed(SparseArrayEntry *arrayPtr) { SparseArrayEntry *globalSEUse; if (arrayPtr) { ((SparseArrayEntryWrapper *)arrayPtr)->inUse = 1; for (globalSEUse = (SparseArrayEntry *)rbTreeBegin((rbTreeNode *)arrayPtr); globalSEUse != NULL; globalSEUse = (SparseArrayEntry *)rbTreeNext((rbTreeNode *)globalSEUse)) { ((SparseArrayEntryWrapper *)globalSEUse)->inUse = 1; /* test first because it may be read-only static string */ if (!(*(globalSEUse->key - 1))) { *(globalSEUse->key - 1) = 1; } if (globalSEUse->value.tag == STRING_TAG) { /* test first because it may be read-only static string */ if (!(*(globalSEUse->value.val.str.rep - 1))) { *(globalSEUse->value.val.str.rep - 1) = 1; } } else if (globalSEUse->value.tag == ARRAY_TAG) { MarkArrayContentsAsUsed(globalSEUse->value.val.arrayPtr); } } } } /* ** Collect strings that are no longer referenced from the global symbol ** list. THIS CAN NOT BE RUN WHILE ANY MACROS ARE EXECUTING. It must ** only be run after all macro activity has ceased. */ void GarbageCollectStrings(void) { SparseArrayEntryWrapper *nextAP, *thisAP; char *p, *next; Symbol *s; /* mark all strings as unreferenced */ for (p = AllocatedStrings; p != NULL; p = *((char **)p)) { *(p + sizeof(char *)) = 0; } for (thisAP = AllocatedSparseArrayEntries; thisAP != NULL; thisAP = thisAP->next) { thisAP->inUse = 0; } /* Sweep the global symbol list, marking which strings are still referenced */ for (s = GlobalSymList; s != NULL; s = s->next) { if (s->value.tag == STRING_TAG) { /* test first because it may be read-only static string */ if (!(*(s->value.val.str.rep - 1))) { *(s->value.val.str.rep - 1) = 1; } } else if (s->value.tag == ARRAY_TAG) { MarkArrayContentsAsUsed(s->value.val.arrayPtr); } } /* Collect all of the strings which remain unreferenced */ next = AllocatedStrings; AllocatedStrings = NULL; while (next != NULL) { p = next; next = *((char **)p); if (*(p + sizeof(char *)) != 0) { *((char **)p) = AllocatedStrings; AllocatedStrings = p; } else { #ifdef TRACK_GARBAGE_LEAKS --numAllocatedStrings; #endif NEditFree(p); } } nextAP = AllocatedSparseArrayEntries; AllocatedSparseArrayEntries = NULL; while (nextAP != NULL) { thisAP = nextAP; nextAP = nextAP->next; if (thisAP->inUse != 0) { thisAP->next = AllocatedSparseArrayEntries; AllocatedSparseArrayEntries = thisAP; } else { #ifdef TRACK_GARBAGE_LEAKS --numAllocatedSparseArrayElements; #endif NEditFree(thisAP); } } #ifdef TRACK_GARBAGE_LEAKS printf("str count = %d\nary count = %d\n", numAllocatedStrings, numAllocatedSparseArrayElements); #endif } /* ** Save and restore execution context to data structure "context" */ static void saveContext(RestartData *context) { context->stack = TheStack; context->stackP = StackP; context->frameP = FrameP; context->pc = PC; context->runWindow = InitiatingWindow; context->focusWindow = FocusWindow; } static void restoreContext(RestartData *context) { TheStack = context->stack; StackP = context->stackP; FrameP = context->frameP; PC = context->pc; InitiatingWindow = context->runWindow; FocusWindow = context->focusWindow; } static void freeSymbolTable(Symbol *symTab) { Symbol *s; while(symTab != NULL) { s = symTab; free(s->name); symTab = s->next; NEditFree(s); } } #define POP(dataVal) \ if (StackP == TheStack) \ return execError(StackUnderflowMsg, ""); \ dataVal = *--StackP; #define PUSH(dataVal) \ if (StackP >= &TheStack[STACK_SIZE]) \ return execError(StackOverflowMsg, ""); \ *StackP++ = dataVal; #define PEEK(dataVal, peekIndex) \ dataVal = *(StackP - peekIndex - 1); #define POP_INT(number) \ if (StackP == TheStack) \ return execError(StackUnderflowMsg, ""); \ --StackP; \ if (StackP->tag == STRING_TAG) { \ if (!StringToNum(StackP->val.str.rep, &number)) \ return execError(StringToNumberMsg, ""); \ } else if (StackP->tag == INT_TAG) \ number = StackP->val.n; \ else \ return(execError("can''t convert array to integer", NULL)); #define POP_STRING(string) \ if (StackP == TheStack) \ return execError(StackUnderflowMsg, ""); \ --StackP; \ if (StackP->tag == INT_TAG) { \ string = AllocString(TYPE_INT_STR_SIZE(int)); \ sprintf(string, "%d", StackP->val.n); \ } else if (StackP->tag == STRING_TAG) \ string = StackP->val.str.rep; \ else \ return(execError("can''t convert array to string", NULL)); #define PEEK_STRING(string, peekIndex) \ if ((StackP - peekIndex - 1)->tag == INT_TAG) { \ string = AllocString(TYPE_INT_STR_SIZE(int)); \ sprintf(string, "%d", (StackP - peekIndex - 1)->val.n); \ } \ else if ((StackP - peekIndex - 1)->tag == STRING_TAG) { \ string = (StackP - peekIndex - 1)->val.str.rep; \ } \ else { \ return(execError("can''t convert array to string", NULL)); \ } #define PEEK_INT(number, peekIndex) \ if ((StackP - peekIndex - 1)->tag == STRING_TAG) { \ if (!StringToNum((StackP - peekIndex - 1)->val.str.rep, &number)) { \ return execError(StringToNumberMsg, ""); \ } \ } else if ((StackP - peekIndex - 1)->tag == INT_TAG) { \ number = (StackP - peekIndex - 1)->val.n; \ } \ else { \ return(execError("can''t convert array to string", NULL)); \ } #define PUSH_INT(number) \ if (StackP >= &TheStack[STACK_SIZE]) \ return execError(StackOverflowMsg, ""); \ StackP->tag = INT_TAG; \ StackP->val.n = number; \ StackP++; #define PUSH_STRING(string, length) \ if (StackP >= &TheStack[STACK_SIZE]) \ return execError(StackOverflowMsg, ""); \ StackP->tag = STRING_TAG; \ StackP->val.str.rep = string; \ StackP->val.str.len = length; \ StackP++; #define BINARY_NUMERIC_OPERATION(operator) \ int n1, n2; \ DISASM_RT(PC-1, 1); \ STACKDUMP(2, 3); \ POP_INT(n2) \ POP_INT(n1) \ PUSH_INT(n1 operator n2) \ return STAT_OK; #define UNARY_NUMERIC_OPERATION(operator) \ int n; \ DISASM_RT(PC-1, 1); \ STACKDUMP(1, 3); \ POP_INT(n) \ PUSH_INT(operator n) \ return STAT_OK; /* ** copy a symbol's value onto the stack ** Before: Prog-> [Sym], next, ... ** TheStack-> next, ... ** After: Prog-> Sym, [next], ... ** TheStack-> [symVal], next, ... */ static int pushSymVal(void) { Symbol *s; int nArgs, argNum; DataValue symVal; DISASM_RT(PC-1, 2); STACKDUMP(0, 3); s = PC->sym; PC++; if (s->type == LOCAL_SYM) { symVal = FP_GET_SYM_VAL(FrameP, s); } else if (s->type == GLOBAL_SYM || s->type == CONST_SYM) { symVal = s->value; } else if (s->type == ARG_SYM) { nArgs = FP_GET_ARG_COUNT(FrameP); argNum = s->value.val.n; if (argNum >= nArgs) { return execError("referenced undefined argument: %s", s->name); } if (argNum == N_ARGS_ARG_SYM) { symVal.tag = INT_TAG; symVal.val.n = nArgs; } else { symVal = FP_GET_ARG_N(FrameP, argNum); } } else if (s->type == PROC_VALUE_SYM) { char *errMsg; if (!(s->value.val.subr)(FocusWindow, NULL, 0, &symVal, &errMsg)) { return execError(errMsg, s->name); } } else return execError("reading non-variable: %s", s->name); if (symVal.tag == NO_TAG) { return execError("variable not set: %s", s->name); } PUSH(symVal) return STAT_OK; } static int pushArgVal(void) { int nArgs, argNum; DISASM_RT(PC-1, 1); STACKDUMP(1, 3); POP_INT(argNum) --argNum; nArgs = FP_GET_ARG_COUNT(FrameP); if (argNum >= nArgs || argNum < 0) { char argStr[TYPE_INT_STR_SIZE(argNum)]; sprintf(argStr, "%d", argNum + 1); return execError("referenced undefined argument: $args[%s]", argStr); } PUSH(FP_GET_ARG_N(FrameP, argNum)); return STAT_OK; } static int pushArgCount(void) { DISASM_RT(PC-1, 1); STACKDUMP(0, 3); PUSH_INT(FP_GET_ARG_COUNT(FrameP)); return STAT_OK; } static int pushArgArray(void) { int nArgs, argNum; DataValue argVal, *resultArray; DISASM_RT(PC-1, 1); STACKDUMP(0, 3); nArgs = FP_GET_ARG_COUNT(FrameP); resultArray = &FP_GET_ARG_ARRAY_CACHE(FrameP); if (resultArray->tag != ARRAY_TAG) { resultArray->tag = ARRAY_TAG; resultArray->val.arrayPtr = ArrayNew(); for (argNum = 0; argNum < nArgs; ++argNum) { char intStr[TYPE_INT_STR_SIZE(argNum)]; sprintf(intStr, "%d", argNum + 1); argVal = FP_GET_ARG_N(FrameP, argNum); if (!ArrayInsert(resultArray, AllocStringCpy(intStr), &argVal)) { return(execError("array insertion failure", NULL)); } } } PUSH(*resultArray); return STAT_OK; } /* ** Push an array (by reference) onto the stack ** Before: Prog-> [ArraySym], makeEmpty, next, ... ** TheStack-> next, ... ** After: Prog-> ArraySym, makeEmpty, [next], ... ** TheStack-> [elemValue], next, ... ** makeEmpty is either true (1) or false (0): if true, and the element is not ** present in the array, create it. */ static int pushArraySymVal(void) { Symbol *sym; DataValue *dataPtr; int initEmpty; DISASM_RT(PC-1, 3); STACKDUMP(0, 3); sym = PC->sym; PC++; initEmpty = PC->value; PC++; if (sym->type == LOCAL_SYM) { dataPtr = &FP_GET_SYM_VAL(FrameP, sym); } else if (sym->type == GLOBAL_SYM) { dataPtr = &sym->value; } else { return execError("assigning to non-lvalue array or non-array: %s", sym->name); } if (initEmpty && dataPtr->tag == NO_TAG) { dataPtr->tag = ARRAY_TAG; dataPtr->val.arrayPtr = ArrayNew(); } if (dataPtr->tag == NO_TAG) { return execError("variable not set: %s", sym->name); } PUSH(*dataPtr) return STAT_OK; } /* ** assign top value to next symbol ** ** Before: Prog-> [symbol], next, ... ** TheStack-> [value], next, ... ** After: Prog-> symbol, [next], ... ** TheStack-> next, ... */ static int assign(void) { Symbol *sym; DataValue *dataPtr; DataValue value; DISASM_RT(PC-1, 2); STACKDUMP(1, 3); sym = PC->sym; PC++; if (sym->type != GLOBAL_SYM && sym->type != LOCAL_SYM) { if (sym->type == ARG_SYM) { return execError("assignment to function argument: %s", sym->name); } else if (sym->type == PROC_VALUE_SYM) { return execError("assignment to read-only variable: %s", sym->name); } else { return execError("assignment to non-variable: %s", sym->name); } } if (sym->type == LOCAL_SYM) { dataPtr = &FP_GET_SYM_VAL(FrameP, sym); } else { dataPtr = &sym->value; } POP(value) if (value.tag == ARRAY_TAG) { return ArrayCopy(dataPtr, &value); } *dataPtr = value; return STAT_OK; } /* ** copy the top value of the stack ** Before: TheStack-> value, next, ... ** After: TheStack-> value, value, next, ... */ static int dupStack(void) { DataValue value; DISASM_RT(PC-1, 1); STACKDUMP(1, 3); PEEK(value, 0) PUSH(value) return STAT_OK; } /* ** if left and right arguments are arrays, then the result is a new array ** in which all the keys from both the right and left are copied ** the values from the right array are used in the result array when the ** keys are the same ** Before: TheStack-> value2, value1, next, ... ** After: TheStack-> resValue, next, ... */ static int add(void) { DataValue leftVal, rightVal, resultArray; int n1, n2; DISASM_RT(PC-1, 1); STACKDUMP(2, 3); PEEK(rightVal, 0) if (rightVal.tag == ARRAY_TAG) { PEEK(leftVal, 1) if (leftVal.tag == ARRAY_TAG) { SparseArrayEntry *leftIter, *rightIter; resultArray.tag = ARRAY_TAG; resultArray.val.arrayPtr = ArrayNew(); POP(rightVal) POP(leftVal) leftIter = arrayIterateFirst(&leftVal); rightIter = arrayIterateFirst(&rightVal); while (leftIter || rightIter) { Boolean insertResult = True; if (leftIter && rightIter) { int compareResult = arrayEntryCompare((rbTreeNode *)leftIter, (rbTreeNode *)rightIter); if (compareResult < 0) { insertResult = ArrayInsert(&resultArray, leftIter->key, &leftIter->value); leftIter = arrayIterateNext(leftIter); } else if (compareResult > 0) { insertResult = ArrayInsert(&resultArray, rightIter->key, &rightIter->value); rightIter = arrayIterateNext(rightIter); } else { insertResult = ArrayInsert(&resultArray, rightIter->key, &rightIter->value); leftIter = arrayIterateNext(leftIter); rightIter = arrayIterateNext(rightIter); } } else if (leftIter) { insertResult = ArrayInsert(&resultArray, leftIter->key, &leftIter->value); leftIter = arrayIterateNext(leftIter); } else { insertResult = ArrayInsert(&resultArray, rightIter->key, &rightIter->value); rightIter = arrayIterateNext(rightIter); } if (!insertResult) { return(execError("array insertion failure", NULL)); } } PUSH(resultArray) } else { return(execError("can''t mix math with arrays and non-arrays", NULL)); } } else { POP_INT(n2) POP_INT(n1) PUSH_INT(n1 + n2) } return(STAT_OK); } /* ** if left and right arguments are arrays, then the result is a new array ** in which only the keys which exist in the left array but not in the right ** are copied ** Before: TheStack-> value2, value1, next, ... ** After: TheStack-> resValue, next, ... */ static int subtract(void) { DataValue leftVal, rightVal, resultArray; int n1, n2; DISASM_RT(PC-1, 1); STACKDUMP(2, 3); PEEK(rightVal, 0) if (rightVal.tag == ARRAY_TAG) { PEEK(leftVal, 1) if (leftVal.tag == ARRAY_TAG) { SparseArrayEntry *leftIter, *rightIter; resultArray.tag = ARRAY_TAG; resultArray.val.arrayPtr = ArrayNew(); POP(rightVal) POP(leftVal) leftIter = arrayIterateFirst(&leftVal); rightIter = arrayIterateFirst(&rightVal); while (leftIter) { Boolean insertResult = True; if (rightIter) { int compareResult = arrayEntryCompare((rbTreeNode *)leftIter, (rbTreeNode *)rightIter); if (compareResult < 0) { insertResult = ArrayInsert(&resultArray, leftIter->key, &leftIter->value); leftIter = arrayIterateNext(leftIter); } else if (compareResult > 0) { rightIter = arrayIterateNext(rightIter); } else { leftIter = arrayIterateNext(leftIter); rightIter = arrayIterateNext(rightIter); } } else { insertResult = ArrayInsert(&resultArray, leftIter->key, &leftIter->value); leftIter = arrayIterateNext(leftIter); } if (!insertResult) { return(execError("array insertion failure", NULL)); } } PUSH(resultArray) } else { return(execError("can''t mix math with arrays and non-arrays", NULL)); } } else { POP_INT(n2) POP_INT(n1) PUSH_INT(n1 - n2) } return(STAT_OK); } /* ** Other binary operators ** Before: TheStack-> value2, value1, next, ... ** After: TheStack-> resValue, next, ... ** ** Other unary operators ** Before: TheStack-> value, next, ... ** After: TheStack-> resValue, next, ... */ static int multiply(void) { BINARY_NUMERIC_OPERATION(*) } static int divide(void) { int n1, n2; DISASM_RT(PC-1, 1); STACKDUMP(2, 3); POP_INT(n2) POP_INT(n1) if (n2 == 0) { return execError("division by zero", ""); } PUSH_INT(n1 / n2) return STAT_OK; } static int modulo(void) { int n1, n2; DISASM_RT(PC-1, 1); STACKDUMP(2, 3); POP_INT(n2) POP_INT(n1) if (n2 == 0) { return execError("modulo by zero", ""); } PUSH_INT(n1 % n2) return STAT_OK; } static int negate(void) { UNARY_NUMERIC_OPERATION(-) } static int increment(void) { UNARY_NUMERIC_OPERATION(++) } static int decrement(void) { UNARY_NUMERIC_OPERATION(--) } static int gt(void) { BINARY_NUMERIC_OPERATION(>) } static int lt(void) { BINARY_NUMERIC_OPERATION(<) } static int ge(void) { BINARY_NUMERIC_OPERATION(>=) } static int le(void) { BINARY_NUMERIC_OPERATION(<=) } /* ** verify that compares are between integers and/or strings only ** Before: TheStack-> value1, value2, next, ... ** After: TheStack-> resValue, next, ... ** where resValue is 1 for true, 0 for false */ static int eq(void) { DataValue v1, v2; DISASM_RT(PC-1, 1); STACKDUMP(2, 3); POP(v1) POP(v2) if (v1.tag == INT_TAG && v2.tag == INT_TAG) { v1.val.n = v1.val.n == v2.val.n; } else if (v1.tag == STRING_TAG && v2.tag == STRING_TAG) { v1.val.n = !strcmp(v1.val.str.rep, v2.val.str.rep); } else if (v1.tag == STRING_TAG && v2.tag == INT_TAG) { int number; if (!StringToNum(v1.val.str.rep, &number)) { v1.val.n = 0; } else { v1.val.n = number == v2.val.n; } } else if (v2.tag == STRING_TAG && v1.tag == INT_TAG) { int number; if (!StringToNum(v2.val.str.rep, &number)) { v1.val.n = 0; } else { v1.val.n = number == v1.val.n; } } else { return(execError("incompatible types to compare", NULL)); } v1.tag = INT_TAG; PUSH(v1) return(STAT_OK); } /* negated eq() call */ static int ne(void) { eq(); return not(); } /* ** if left and right arguments are arrays, then the result is a new array ** in which only the keys which exist in both the right or left are copied ** the values from the right array are used in the result array ** Before: TheStack-> value2, value1, next, ... ** After: TheStack-> resValue, next, ... */ static int bitAnd(void) { DataValue leftVal, rightVal, resultArray; int n1, n2; DISASM_RT(PC-1, 1); STACKDUMP(2, 3); PEEK(rightVal, 0) if (rightVal.tag == ARRAY_TAG) { PEEK(leftVal, 1) if (leftVal.tag == ARRAY_TAG) { SparseArrayEntry *leftIter, *rightIter; resultArray.tag = ARRAY_TAG; resultArray.val.arrayPtr = ArrayNew(); POP(rightVal) POP(leftVal) leftIter = arrayIterateFirst(&leftVal); rightIter = arrayIterateFirst(&rightVal); while (leftIter && rightIter) { Boolean insertResult = True; int compareResult = arrayEntryCompare((rbTreeNode *)leftIter, (rbTreeNode *)rightIter); if (compareResult < 0) { leftIter = arrayIterateNext(leftIter); } else if (compareResult > 0) { rightIter = arrayIterateNext(rightIter); } else { insertResult = ArrayInsert(&resultArray, rightIter->key, &rightIter->value); leftIter = arrayIterateNext(leftIter); rightIter = arrayIterateNext(rightIter); } if (!insertResult) { return(execError("array insertion failure", NULL)); } } PUSH(resultArray) } else { return(execError("can''t mix math with arrays and non-arrays", NULL)); } } else { POP_INT(n2) POP_INT(n1) PUSH_INT(n1 & n2) } return(STAT_OK); } /* ** if left and right arguments are arrays, then the result is a new array ** in which only the keys which exist in either the right or left but not both ** are copied ** Before: TheStack-> value2, value1, next, ... ** After: TheStack-> resValue, next, ... */ static int bitOr(void) { DataValue leftVal, rightVal, resultArray; int n1, n2; DISASM_RT(PC-1, 1); STACKDUMP(2, 3); PEEK(rightVal, 0) if (rightVal.tag == ARRAY_TAG) { PEEK(leftVal, 1) if (leftVal.tag == ARRAY_TAG) { SparseArrayEntry *leftIter, *rightIter; resultArray.tag = ARRAY_TAG; resultArray.val.arrayPtr = ArrayNew(); POP(rightVal) POP(leftVal) leftIter = arrayIterateFirst(&leftVal); rightIter = arrayIterateFirst(&rightVal); while (leftIter || rightIter) { Boolean insertResult = True; if (leftIter && rightIter) { int compareResult = arrayEntryCompare((rbTreeNode *)leftIter, (rbTreeNode *)rightIter); if (compareResult < 0) { insertResult = ArrayInsert(&resultArray, leftIter->key, &leftIter->value); leftIter = arrayIterateNext(leftIter); } else if (compareResult > 0) { insertResult = ArrayInsert(&resultArray, rightIter->key, &rightIter->value); rightIter = arrayIterateNext(rightIter); } else { leftIter = arrayIterateNext(leftIter); rightIter = arrayIterateNext(rightIter); } } else if (leftIter) { insertResult = ArrayInsert(&resultArray, leftIter->key, &leftIter->value); leftIter = arrayIterateNext(leftIter); } else { insertResult = ArrayInsert(&resultArray, rightIter->key, &rightIter->value); rightIter = arrayIterateNext(rightIter); } if (!insertResult) { return(execError("array insertion failure", NULL)); } } PUSH(resultArray) } else { return(execError("can''t mix math with arrays and non-arrays", NULL)); } } else { POP_INT(n2) POP_INT(n1) PUSH_INT(n1 | n2) } return(STAT_OK); } static int and(void) { BINARY_NUMERIC_OPERATION(&&) } static int or(void) { BINARY_NUMERIC_OPERATION(||) } static int not(void) { UNARY_NUMERIC_OPERATION(!) } /* ** raise one number to the power of another ** Before: TheStack-> raisedBy, number, next, ... ** After: TheStack-> result, next, ... */ static int power(void) { int n1, n2, n3; DISASM_RT(PC-1, 1); STACKDUMP(2, 3); POP_INT(n2) POP_INT(n1) /* We need to round to deal with pow() giving results slightly above or below the real result since it deals with floating point numbers. Note: We're not really wanting rounded results, we merely want to deal with this simple issue. So, 2^-2 = .5, but we don't want to round this to 1. This is mainly intended to deal with 4^2 = 15.999996 and 16.000001. */ if (n2 < 0 && n1 != 1 && n1 != -1) { if (n1 != 0) { /* since we're integer only, nearly all negative exponents result in 0 */ n3 = 0; } else { /* allow error to occur */ n3 = (int)pow((double)n1, (double)n2); } } else { if ((n1 < 0) && (n2 & 1)) { /* round to nearest integer for negative values*/ n3 = (int)(pow((double)n1, (double)n2) - (double)0.5); } else { /* round to nearest integer for positive values*/ n3 = (int)(pow((double)n1, (double)n2) + (double)0.5); } } PUSH_INT(n3) return errCheck("exponentiation"); } /* ** concatenate two top items on the stack ** Before: TheStack-> str2, str1, next, ... ** After: TheStack-> result, next, ... */ static int concat(void) { char *s1, *s2, *out; int len1, len2; DISASM_RT(PC-1, 1); STACKDUMP(2, 3); POP_STRING(s2) POP_STRING(s1) len1 = strlen(s1); len2 = strlen(s2); out = AllocString(len1 + len2 + 1); strncpy(out, s1, len1); strcpy(&out[len1], s2); PUSH_STRING(out, len1 + len2) return STAT_OK; } /* ** Call a subroutine or function (user defined or built-in). Args are the ** subroutine's symbol, and the number of arguments which have been pushed ** on the stack. ** ** For a macro subroutine, the return address, frame pointer, number of ** arguments and space for local variables are added to the stack, and the ** PC is set to point to the new function. For a built-in routine, the ** arguments are popped off the stack, and the routine is just called. ** ** Before: Prog-> [subrSym], nArgs, next, ... ** TheStack-> argN-arg1, next, ... ** After: Prog-> next, ... -- (built-in called subr) ** TheStack-> retVal?, next, ... ** or: Prog-> (in called)next, ... -- (macro code called subr) ** TheStack-> symN-sym1(FP), argArray, nArgs, oldFP, retPC, argN-arg1, next, ... */ static int callSubroutine(void) { Symbol *sym, *s; int i, nArgs; static DataValue noValue = {NO_TAG, {0}}; Program *prog; char *errMsg; sym = PC->sym; PC++; nArgs = PC->value; PC++; DISASM_RT(PC-3, 3); STACKDUMP(nArgs, 3); /* ** If the subroutine is built-in, call the built-in routine */ if (sym->type == C_FUNCTION_SYM) { DataValue result; /* "pop" stack back to the first argument in the call stack */ StackP -= nArgs; /* Call the function and check for preemption */ PreemptRequest = False; if (!sym->value.val.subr(FocusWindow, StackP, nArgs, &result, &errMsg)) return execError(errMsg, sym->name); if (PC->func == fetchRetVal) { if (result.tag == NO_TAG) { return execError("%s does not return a value", sym->name); } PUSH(result); PC++; } return PreemptRequest ? STAT_PREEMPT : STAT_OK; } /* ** Call a macro subroutine: ** ** Push all of the required information to resume, and make space on the ** stack for local variables (and initialize them), on top of the argument ** values which are already there. */ if (sym->type == MACRO_FUNCTION_SYM) { StackP->tag = NO_TAG; /* return PC */ StackP->val.inst = PC; StackP++; StackP->tag = NO_TAG; /* old FrameP */ StackP->val.dataval = FrameP; StackP++; StackP->tag = NO_TAG; /* nArgs */ StackP->val.n = nArgs; StackP++; *(StackP++) = noValue; /* cached arg array */ FrameP = StackP; prog = sym->value.val.prog; PC = prog->code; for (s = prog->localSymList; s != NULL; s = s->next) { FP_GET_SYM_VAL(FrameP, s) = noValue; StackP++; } return STAT_OK; } /* ** Call an action routine */ if (sym->type == ACTION_ROUTINE_SYM) { String *argList; Cardinal numArgs = nArgs; XKeyEvent key_event; Display *disp; Window win; /* Create a fake event with a timestamp suitable for actions which need timestamps, a marker to indicate that the call was from a macro (to stop shell commands from putting up their own separate banner) */ disp=XtDisplay(InitiatingWindow->shell); win=XtWindow(InitiatingWindow->shell); key_event.type = KeyPress; key_event.send_event = MACRO_EVENT_MARKER; key_event.time=XtLastTimestampProcessed(XtDisplay(InitiatingWindow->shell)); /* The following entries are just filled in to avoid problems in strange cases, like calling "self_insert()" directly from the macro menu. In fact the display was sufficient to cure this crash. */ key_event.display=disp; key_event.window=key_event.root=key_event.subwindow=win; argList = (String *)NEditCalloc(nArgs, sizeof(*argList)); /* pop arguments off the stack and put them in the argument list */ for (i=nArgs-1; i>=0; i--) { POP_STRING(argList[i]) } /* Call the action routine and check for preemption */ PreemptRequest = False; sym->value.val.xtproc(FocusWindow->lastFocus, (XEvent *)&key_event, argList, &numArgs); NEditFree(argList); if (PC->func == fetchRetVal) { return execError("%s does not return a value", sym->name); } return PreemptRequest ? STAT_PREEMPT : STAT_OK; } /* Calling a non subroutine symbol */ return execError("%s is not a function or subroutine", sym->name); } /* ** This should never be executed, returnVal checks for the presence of this ** instruction at the PC to decide whether to push the function's return ** value, then skips over it without executing. */ static int fetchRetVal(void) { return execError("internal error: frv", NULL); } /* see comments for returnValOrNone() */ static int returnNoVal(void) { return returnValOrNone(False); } static int returnVal(void) { return returnValOrNone(True); } /* ** Return from a subroutine call ** Before: Prog-> [next], ... ** TheStack-> retVal?, ...(FP), argArray, nArgs, oldFP, retPC, argN-arg1, next, ... ** After: Prog-> next, ..., (in caller)[FETCH_RET_VAL?], ... ** TheStack-> retVal?, next, ... */ static int returnValOrNone(int valOnStack) { DataValue retVal; static DataValue noValue = {NO_TAG, {0}}; DataValue *newFrameP; int nArgs; DISASM_RT(PC-1, 1); STACKDUMP(StackP - FrameP + FP_GET_ARG_COUNT(FrameP) + FP_TO_ARGS_DIST, 3); /* return value is on the stack */ if (valOnStack) { POP(retVal); } /* get stored return information */ nArgs = FP_GET_ARG_COUNT(FrameP); newFrameP = FP_GET_OLD_FP(FrameP); PC = FP_GET_RET_PC(FrameP); /* pop past local variables */ StackP = FrameP; /* pop past function arguments */ StackP -= (FP_TO_ARGS_DIST + nArgs); FrameP = newFrameP; /* push returned value, if requsted */ if (PC == NULL) { if (valOnStack) { PUSH(retVal); } else { PUSH(noValue); } } else if (PC->func == fetchRetVal) { if (valOnStack) { PUSH(retVal); PC++; } else { return execError( "using return value of %s which does not return a value", ((PC-2)->sym->name)); } } /* NULL return PC indicates end of program */ return PC == NULL ? STAT_DONE : STAT_OK; } /* ** Unconditional branch offset by immediate operand ** ** Before: Prog-> [branchDest], next, ..., (branchdest)next ** After: Prog-> branchDest, next, ..., (branchdest)[next] */ static int branch(void) { DISASM_RT(PC-1, 2); STACKDUMP(0, 3); PC += PC->value; return STAT_OK; } /* ** Conditional branches if stack value is True/False (non-zero/0) to address ** of immediate operand (pops stack) ** ** Before: Prog-> [branchDest], next, ..., (branchdest)next ** After: either: Prog-> branchDest, [next], ... ** After: or: Prog-> branchDest, next, ..., (branchdest)[next] */ static int branchTrue(void) { int value; Inst *addr; DISASM_RT(PC-1, 2); STACKDUMP(1, 3); POP_INT(value) addr = PC + PC->value; PC++; if (value) PC = addr; return STAT_OK; } static int branchFalse(void) { int value; Inst *addr; DISASM_RT(PC-1, 2); STACKDUMP(1, 3); POP_INT(value) addr = PC + PC->value; PC++; if (!value) PC = addr; return STAT_OK; } /* ** Ignore the address following the instruction and continue. Why? So ** some code that uses conditional branching doesn't have to figure out ** whether to store a branch address. ** ** Before: Prog-> [branchDest], next, ... ** After: Prog-> branchDest, [next], ... */ static int branchNever(void) { DISASM_RT(PC-1, 2); STACKDUMP(0, 3); PC++; return STAT_OK; } /* ** recursively copy(duplicate) the sparse array nodes of an array ** this does not duplicate the key/node data since they are never ** modified, only replaced */ int ArrayCopy(DataValue *dstArray, DataValue *srcArray) { SparseArrayEntry *srcIter; dstArray->tag = ARRAY_TAG; dstArray->val.arrayPtr = ArrayNew(); srcIter = arrayIterateFirst(srcArray); while (srcIter) { if (srcIter->value.tag == ARRAY_TAG) { int errNum; DataValue tmpArray; errNum = ArrayCopy(&tmpArray, &srcIter->value); if (errNum != STAT_OK) { return(errNum); } if (!ArrayInsert(dstArray, srcIter->key, &tmpArray)) { return(execError("array copy failed", NULL)); } } else { if (!ArrayInsert(dstArray, srcIter->key, &srcIter->value)) { return(execError("array copy failed", NULL)); } } srcIter = arrayIterateNext(srcIter); } return(STAT_OK); } /* ** creates an allocated string of a single key for all the sub-scripts ** using ARRAY_DIM_SEP as a separator ** this function uses the PEEK macros in order to remove most limits on ** the number of arguments to an array ** I really need to optimize the size approximation rather than assuming ** a worst case size for every integer argument */ static int makeArrayKeyFromArgs(int nArgs, char **keyString, int leaveParams) { DataValue tmpVal; int sepLen = strlen(ARRAY_DIM_SEP); int keyLength = 0; int i; keyLength = sepLen * (nArgs - 1); for (i = nArgs - 1; i >= 0; --i) { PEEK(tmpVal, i) if (tmpVal.tag == INT_TAG) { keyLength += TYPE_INT_STR_SIZE(tmpVal.val.n); } else if (tmpVal.tag == STRING_TAG) { keyLength += tmpVal.val.str.len; } else { return(execError("can only index array with string or int.", NULL)); } } *keyString = AllocString(keyLength + 1); (*keyString)[0] = 0; for (i = nArgs - 1; i >= 0; --i) { if (i != nArgs - 1) { strcat(*keyString, ARRAY_DIM_SEP); } PEEK(tmpVal, i) if (tmpVal.tag == INT_TAG) { sprintf(&((*keyString)[strlen(*keyString)]), "%d", tmpVal.val.n); } else if (tmpVal.tag == STRING_TAG) { strcat(*keyString, tmpVal.val.str.rep); } else { return(execError("can only index array with string or int.", NULL)); } } if (!leaveParams) { for (i = nArgs - 1; i >= 0; --i) { POP(tmpVal) } } return(STAT_OK); } /* ** allocate an empty array node, this is used as the root node and never ** contains any data, only refernces to other nodes */ static rbTreeNode *arrayEmptyAllocator(void) { SparseArrayEntry *newNode = allocateSparseArrayEntry(); if (newNode) { newNode->key = NULL; newNode->value.tag = NO_TAG; } return((rbTreeNode *)newNode); } /* ** create and copy array node and copy contents, we merely copy pointers ** since they are never modified, only replaced */ static rbTreeNode *arrayAllocateNode(rbTreeNode *src) { SparseArrayEntry *newNode = allocateSparseArrayEntry(); if (newNode) { newNode->key = ((SparseArrayEntry *)src)->key; newNode->value = ((SparseArrayEntry *)src)->value; } return((rbTreeNode *)newNode); } /* ** copy array node data, we merely copy pointers since they are never ** modified, only replaced */ static int arrayEntryCopyToNode(rbTreeNode *dst, rbTreeNode *src) { ((SparseArrayEntry *)dst)->key = ((SparseArrayEntry *)src)->key; ((SparseArrayEntry *)dst)->value = ((SparseArrayEntry *)src)->value; return(1); } /* ** compare two array nodes returning an integer value similar to strcmp() */ static int arrayEntryCompare(rbTreeNode *left, rbTreeNode *right) { return(strcmp(((SparseArrayEntry *)left)->key, ((SparseArrayEntry *)right)->key)); } /* ** dispose an array node, garbage collection handles this, so we mark it ** to allow iterators in macro language to determine they have been unlinked */ static void arrayDisposeNode(rbTreeNode *src) { /* Let garbage collection handle this but mark it so iterators can tell */ src->left = NULL; src->right = NULL; src->parent = NULL; src->color = -1; } SparseArrayEntry *ArrayNew(void) { return((SparseArrayEntry *)rbTreeNew(arrayEmptyAllocator)); } /* ** insert a DataValue into an array, allocate the array if needed ** keyStr must be a string that was allocated with AllocString() */ Boolean ArrayInsert(DataValue* theArray, char* keyStr, DataValue* theValue) { SparseArrayEntry tmpEntry; rbTreeNode *insertedNode; tmpEntry.key = keyStr; tmpEntry.value = *theValue; if (theArray->val.arrayPtr == NULL) { theArray->val.arrayPtr = ArrayNew(); } if (theArray->val.arrayPtr != NULL) { insertedNode = rbTreeInsert((rbTreeNode*) (theArray->val.arrayPtr), (rbTreeNode *)&tmpEntry, arrayEntryCompare, arrayAllocateNode, arrayEntryCopyToNode); if (insertedNode) { return True; } else { return False; } } return False; } /* ** remove a node from an array whose key matches keyStr */ void ArrayDelete(DataValue *theArray, char *keyStr) { SparseArrayEntry searchEntry; if (theArray->val.arrayPtr) { searchEntry.key = keyStr; rbTreeDelete((rbTreeNode *)theArray->val.arrayPtr, (rbTreeNode *)&searchEntry, arrayEntryCompare, arrayDisposeNode); } } /* ** remove all nodes from an array */ void ArrayDeleteAll(DataValue *theArray) { if (theArray->val.arrayPtr) { rbTreeNode *iter = rbTreeBegin((rbTreeNode *)theArray->val.arrayPtr); while (iter) { rbTreeNode *nextIter = rbTreeNext(iter); rbTreeDeleteNode((rbTreeNode *)theArray->val.arrayPtr, iter, arrayDisposeNode); iter = nextIter; } } } /* ** returns the number of elements (nodes containing values) of an array */ unsigned ArraySize(DataValue* theArray) { if (theArray->val.arrayPtr) { return rbTreeSize((rbTreeNode *)theArray->val.arrayPtr); } else { return 0; } } /* ** retrieves an array node whose key matches ** returns 1 for success 0 for not found */ Boolean ArrayGet(DataValue* theArray, char* keyStr, DataValue* theValue) { SparseArrayEntry searchEntry; rbTreeNode *foundNode; if (theArray->val.arrayPtr) { searchEntry.key = keyStr; foundNode = rbTreeFind((rbTreeNode*) theArray->val.arrayPtr, (rbTreeNode*) &searchEntry, arrayEntryCompare); if (foundNode) { *theValue = ((SparseArrayEntry*) foundNode)->value; return True; } } return False; } /* ** get pointer to start iterating an array */ SparseArrayEntry *arrayIterateFirst(DataValue *theArray) { SparseArrayEntry *startPos; if (theArray->val.arrayPtr) { startPos = (SparseArrayEntry *)rbTreeBegin((rbTreeNode *)theArray->val.arrayPtr); } else { startPos = NULL; } return(startPos); } /* ** move iterator to next entry in array */ SparseArrayEntry *arrayIterateNext(SparseArrayEntry *iterator) { SparseArrayEntry *nextPos; if (iterator) { nextPos = (SparseArrayEntry *)rbTreeNext((rbTreeNode *)iterator); } else { nextPos = NULL; } return(nextPos); } /* ** evaluate an array element and push the result onto the stack ** ** Before: Prog-> [nDim], next, ... ** TheStack-> indnDim, ... ind1, ArraySym, next, ... ** After: Prog-> nDim, [next], ... ** TheStack-> indexedArrayVal, next, ... */ static int arrayRef(void) { int errNum; DataValue srcArray, valueItem; char *keyString = NULL; int nDim; nDim = PC->value; PC++; DISASM_RT(PC-2, 2); STACKDUMP(nDim, 3); if (nDim > 0) { errNum = makeArrayKeyFromArgs(nDim, &keyString, 0); if (errNum != STAT_OK) { return(errNum); } POP(srcArray) if (srcArray.tag == ARRAY_TAG) { if (!ArrayGet(&srcArray, keyString, &valueItem)) { return(execError("referenced array value not in array: %s", keyString)); } PUSH(valueItem) return(STAT_OK); } else { return(execError("operator [] on non-array", NULL)); } } else { POP(srcArray) if (srcArray.tag == ARRAY_TAG) { PUSH_INT(ArraySize(&srcArray)) return(STAT_OK); } else { return(execError("operator [] on non-array", NULL)); } } } /* ** assign to an array element of a referenced array on the stack ** ** Before: Prog-> [nDim], next, ... ** TheStack-> rhs, indnDim, ... ind1, ArraySym, next, ... ** After: Prog-> nDim, [next], ... ** TheStack-> next, ... */ static int arrayAssign(void) { char *keyString = NULL; DataValue srcValue, dstArray; int errNum; int nDim; nDim = PC->value; PC++; DISASM_RT(PC-2, 1); STACKDUMP(nDim, 3); if (nDim > 0) { POP(srcValue) errNum = makeArrayKeyFromArgs(nDim, &keyString, 0); if (errNum != STAT_OK) { return(errNum); } POP(dstArray) if (dstArray.tag != ARRAY_TAG && dstArray.tag != NO_TAG) { return(execError("cannot assign array element of non-array", NULL)); } if (srcValue.tag == ARRAY_TAG) { DataValue arrayCopyValue; errNum = ArrayCopy(&arrayCopyValue, &srcValue); srcValue = arrayCopyValue; if (errNum != STAT_OK) { return(errNum); } } if (ArrayInsert(&dstArray, keyString, &srcValue)) { return(STAT_OK); } else { return(execError("array member allocation failure", NULL)); } } return(execError("empty operator []", NULL)); } /* ** for use with assign-op operators (eg a[i,j] += k ** ** Before: Prog-> [binOp], nDim, next, ... ** TheStack-> [rhs], indnDim, ... ind1, next, ... ** After: Prog-> binOp, nDim, [next], ... ** TheStack-> [rhs], arrayValue, next, ... */ static int arrayRefAndAssignSetup(void) { int errNum; DataValue srcArray, valueItem, moveExpr; char *keyString = NULL; int binaryOp, nDim; binaryOp = PC->value; PC++; nDim = PC->value; PC++; DISASM_RT(PC-3, 3); STACKDUMP(nDim + 1, 3); if (binaryOp) { POP(moveExpr) } if (nDim > 0) { errNum = makeArrayKeyFromArgs(nDim, &keyString, 1); if (errNum != STAT_OK) { return(errNum); } PEEK(srcArray, nDim) if (srcArray.tag == ARRAY_TAG) { if (!ArrayGet(&srcArray, keyString, &valueItem)) { return(execError("referenced array value not in array: %s", keyString)); } PUSH(valueItem) if (binaryOp) { PUSH(moveExpr) } return(STAT_OK); } else { return(execError("operator [] on non-array", NULL)); } } else { return(execError("array[] not an lvalue", NULL)); } } /* ** setup symbol values for array iteration in interpreter ** ** Before: Prog-> [iter], ARRAY_ITER, iterVar, iter, endLoopBranch, next, ... ** TheStack-> [arrayVal], next, ... ** After: Prog-> iter, [ARRAY_ITER], iterVar, iter, endLoopBranch, next, ... ** TheStack-> [next], ... ** Where: ** iter is a symbol which gives the position of the iterator value in ** the stack frame ** arrayVal is the data value holding the array in question */ static int beginArrayIter(void) { Symbol *iterator; DataValue *iteratorValPtr; DataValue arrayVal; DISASM_RT(PC-1, 2); STACKDUMP(1, 3); iterator = PC->sym; PC++; POP(arrayVal) if (iterator->type == LOCAL_SYM) { iteratorValPtr = &FP_GET_SYM_VAL(FrameP, iterator); } else { return(execError("bad temporary iterator: %s", iterator->name)); } iteratorValPtr->tag = INT_TAG; if (arrayVal.tag != ARRAY_TAG) { return(execError("can''t iterate non-array", NULL)); } iteratorValPtr->val.arrayPtr = arrayIterateFirst(&arrayVal); return(STAT_OK); } /* ** copy key to symbol if node is still valid, marked bad by a color of -1 ** then move iterator to next node ** this allows iterators to progress even if you delete any node in the array ** except the item just after the current key ** ** Before: Prog-> iter, ARRAY_ITER, [iterVar], iter, endLoopBranch, next, ... ** TheStack-> [next], ... ** After: Prog-> iter, ARRAY_ITER, iterVar, iter, endLoopBranch, [next], ... ** TheStack-> [next], ... (unchanged) ** Where: ** iter is a symbol which gives the position of the iterator value in ** the stack frame (set up by BEGIN_ARRAY_ITER); that value refers ** to the array and a position within it ** iterVal is the programmer-visible symbol which will take the current ** key value ** endLoopBranch is the instruction offset to the instruction following the ** loop (measured from itself) ** arrayVal is the data value holding the array in question ** The return-to-start-of-loop branch (at the end of the loop) should address ** the ARRAY_ITER instruction */ static int arrayIter(void) { Symbol *iterator; Symbol *item; DataValue *iteratorValPtr; DataValue *itemValPtr; SparseArrayEntry *thisEntry; Inst *branchAddr; DISASM_RT(PC-1, 4); STACKDUMP(0, 3); item = PC->sym; PC++; iterator = PC->sym; PC++; branchAddr = PC + PC->value; PC++; if (item->type == LOCAL_SYM) { itemValPtr = &FP_GET_SYM_VAL(FrameP, item); } else if (item->type == GLOBAL_SYM) { itemValPtr = &(item->value); } else { return(execError("can''t assign to: %s", item->name)); } itemValPtr->tag = NO_TAG; if (iterator->type == LOCAL_SYM) { iteratorValPtr = &FP_GET_SYM_VAL(FrameP, iterator); } else { return(execError("bad temporary iterator: %s", iterator->name)); } thisEntry = iteratorValPtr->val.arrayPtr; if (thisEntry && thisEntry->nodePtrs.color != -1) { itemValPtr->tag = STRING_TAG; itemValPtr->val.str.rep = thisEntry->key; itemValPtr->val.str.len = strlen(thisEntry->key); iteratorValPtr->val.arrayPtr = arrayIterateNext(thisEntry); } else { PC = branchAddr; } return(STAT_OK); } /* ** determine if a key or keys exists in an array ** if the left argument is a string or integer a single check is performed ** if the key exists, 1 is pushed onto the stack, otherwise 0 ** if the left argument is an array 1 is pushed onto the stack if every key ** in the left array exists in the right array, otherwise 0 ** ** Before: Prog-> [next], ... ** TheStack-> [ArraySym], inSymbol, next, ... ** After: Prog-> [next], ... -- (unchanged) ** TheStack-> next, ... */ static int inArray(void) { DataValue theArray, leftArray, theValue; char *keyStr; int inResult = 0; DISASM_RT(PC-1, 1); STACKDUMP(2, 3); POP(theArray) if (theArray.tag != ARRAY_TAG) { return(execError("operator in on non-array", NULL)); } PEEK(leftArray, 0) if (leftArray.tag == ARRAY_TAG) { SparseArrayEntry *iter; POP(leftArray) inResult = 1; iter = arrayIterateFirst(&leftArray); while (inResult && iter) { inResult = inResult && ArrayGet(&theArray, iter->key, &theValue); iter = arrayIterateNext(iter); } } else { POP_STRING(keyStr) if (ArrayGet(&theArray, keyStr, &theValue)) { inResult = 1; } } PUSH_INT(inResult) return(STAT_OK); } /* ** remove a given key from an array unless nDim is 0, then all keys are removed ** ** for use with assign-op operators (eg a[i,j] += k ** Before: Prog-> [nDim], next, ... ** TheStack-> [indnDim], ... ind1, arrayValue, next, ... ** After: Prog-> nDim, [next], ... ** TheStack-> next, ... */ static int deleteArrayElement(void) { DataValue theArray; char *keyString = NULL; int nDim; nDim = PC->value; PC++; DISASM_RT(PC-2, 2); STACKDUMP(nDim + 1, 3); if (nDim > 0) { int errNum; errNum = makeArrayKeyFromArgs(nDim, &keyString, 0); if (errNum != STAT_OK) { return(errNum); } } POP(theArray) if (theArray.tag == ARRAY_TAG) { if (nDim > 0) { ArrayDelete(&theArray, keyString); } else { ArrayDeleteAll(&theArray); } } else { return(execError("attempt to delete from non-array", NULL)); } return(STAT_OK); } /* ** checks errno after operations which can set it. If an error occured, ** creates appropriate error messages and returns false */ static int errCheck(const char *s) { if (errno == EDOM) return execError("%s argument out of domain", s); else if (errno == ERANGE) return execError("%s result out of range", s); else return STAT_OK; } /* ** combine two strings in a static area and set ErrMsg to point to the ** result. Returns false so a single return execError() statement can ** be used to both process the message and return. */ static int execError(const char *s1, const char *s2) { static char msg[MAX_ERR_MSG_LEN]; sprintf(msg, s1, s2); ErrMsg = msg; return STAT_ERROR; } int StringToNum(const char *string, int *number) { const char *c = string; while (*c == ' ' || *c == '\t') { ++c; } if (*c == '+' || *c == '-') { ++c; } while (isdigit((unsigned char)*c)) { ++c; } while (*c == ' ' || *c == '\t') { ++c; } if (*c) { /* if everything went as expected, we should be at end, but we're not */ return False; } if (number) { if (sscanf(string, "%d", number) != 1) { /* This case is here to support old behavior */ *number = 0; } } return True; } #ifdef DEBUG_DISASSEMBLER /* dumping values in disassembly or stack dump */ static void dumpVal(DataValue dv) { switch (dv.tag) { case INT_TAG: printf("i=%d", dv.val.n); break; case STRING_TAG: { int k; char s[21]; char *src = dv.val.str.rep; if (!src) { printf("s=<NULL>"); } else { for (k = 0; src[k] && k < sizeof s - 1; k++) { s[k] = isprint(src[k]) ? src[k] : '?'; } s[k] = 0; printf("s=\"%s\"%s[%d]", s, src[k] ? "..." : "", strlen(src)); } } break; case ARRAY_TAG: printf("<array>"); break; case NO_TAG: if (!dv.val.inst) { printf("<no value>"); } else { printf("?%8p", dv.val.inst); } break; default: printf("UNKNOWN DATA TAG %d ?%8p", dv.tag, dv.val.inst); break; } } #endif /* #ifdef DEBUG_DISASSEMBLER */ #ifdef DEBUG_DISASSEMBLER /* For debugging code generation */ static void disasm(Inst *inst, int nInstr) { static const char *opNames[N_OPS] = { "RETURN_NO_VAL", /* returnNoVal */ "RETURN", /* returnVal */ "PUSH_SYM", /* pushSymVal */ "DUP", /* dupStack */ "ADD", /* add */ "SUB", /* subtract */ "MUL", /* multiply */ "DIV", /* divide */ "MOD", /* modulo */ "NEGATE", /* negate */ "INCR", /* increment */ "DECR", /* decrement */ "GT", /* gt */ "LT", /* lt */ "GE", /* ge */ "LE", /* le */ "EQ", /* eq */ "NE", /* ne */ "BIT_AND", /* bitAnd */ "BIT_OR", /* bitOr */ "AND", /* and */ "OR", /* or */ "NOT", /* not */ "POWER", /* power */ "CONCAT", /* concat */ "ASSIGN", /* assign */ "SUBR_CALL", /* callSubroutine */ "FETCH_RET_VAL", /* fetchRetVal */ "BRANCH", /* branch */ "BRANCH_TRUE", /* branchTrue */ "BRANCH_FALSE", /* branchFalse */ "BRANCH_NEVER", /* branchNever */ "ARRAY_REF", /* arrayRef */ "ARRAY_ASSIGN", /* arrayAssign */ "BEGIN_ARRAY_ITER", /* beginArrayIter */ "ARRAY_ITER", /* arrayIter */ "IN_ARRAY", /* inArray */ "ARRAY_DELETE", /* deleteArrayElement */ "PUSH_ARRAY_SYM", /* pushArraySymVal */ "ARRAY_REF_ASSIGN_SETUP", /* arrayRefAndAssignSetup */ "PUSH_ARG", /* $arg[expr] */ "PUSH_ARG_COUNT", /* $arg[] */ "PUSH_ARG_ARRAY" /* $arg */ }; int i, j; printf("\n"); for (i = 0; i < nInstr; ++i) { printf("Prog %8p ", &inst[i]); for (j = 0; j < N_OPS; ++j) { if (inst[i].func == OpFns[j]) { printf("%22s ", opNames[j]); if (j == OP_PUSH_SYM || j == OP_ASSIGN) { Symbol *sym = inst[i+1].sym; printf("%s", sym->name); if (sym->value.tag == STRING_TAG && strncmp(sym->name, "string #", 8) == 0) { dumpVal(sym->value); } ++i; } else if (j == OP_BRANCH || j == OP_BRANCH_FALSE || j == OP_BRANCH_NEVER || j == OP_BRANCH_TRUE) { printf("to=(%d) %p", inst[i+1].value, &inst[i+1] + inst[i+1].value); ++i; } else if (j == OP_SUBR_CALL) { printf("%s (%d arg)", inst[i+1].sym->name, inst[i+2].value); i += 2; } else if (j == OP_BEGIN_ARRAY_ITER) { printf("%s in", inst[i+1].sym->name); ++i; } else if (j == OP_ARRAY_ITER) { printf("%s = %s++ end-loop=(%d) %p", inst[i+1].sym->name, inst[i+2].sym->name, inst[i+3].value, &inst[i+3] + inst[i+3].value); i += 3; } else if (j == OP_ARRAY_REF || j == OP_ARRAY_DELETE || j == OP_ARRAY_ASSIGN) { printf("nDim=%d", inst[i+1].value); ++i; } else if (j == OP_ARRAY_REF_ASSIGN_SETUP) { printf("binOp=%s ", inst[i+1].value ? "true" : "false"); printf("nDim=%d", inst[i+2].value); i += 2; } else if (j == OP_PUSH_ARRAY_SYM) { printf("%s", inst[++i].sym->name); printf(" %s", inst[i+1].value ? "createAndRef" : "refOnly"); ++i; } printf("\n"); break; } } if (j == N_OPS) { printf("%x\n", inst[i].value); } } } #endif /* #ifdef DEBUG_DISASSEMBLER */ #ifdef DEBUG_STACK /* for run-time stack dumping */ #define STACK_DUMP_ARG_PREFIX "Arg" static void stackdump(int n, int extra) { /* TheStack-> symN-sym1(FP), argArray, nArgs, oldFP, retPC, argN-arg1, next, ... */ int nArgs = FP_GET_ARG_COUNT(FrameP); int i, offset; char buffer[sizeof(STACK_DUMP_ARG_PREFIX) + TYPE_INT_STR_SIZE(int)]; printf("Stack ----->\n"); for (i = 0; i < n + extra; i++) { char *pos = ""; DataValue *dv = &StackP[-i - 1]; if (dv < TheStack) { printf("--------------Stack base--------------\n"); break; } offset = dv - FrameP; printf("%4.4s", i < n ? ">>>>" : ""); printf("%8p ", dv); switch (offset) { case 0: pos = "FrameP"; break; /* first local symbol value */ case FP_ARG_ARRAY_CACHE_INDEX: pos = "args"; break; /* arguments array */ case FP_ARG_COUNT_INDEX: pos = "NArgs"; break; /* number of arguments */ case FP_OLD_FP_INDEX: pos = "OldFP"; break; case FP_RET_PC_INDEX: pos = "RetPC"; break; default: if (offset < -FP_TO_ARGS_DIST && offset >= -FP_TO_ARGS_DIST - nArgs) { sprintf(pos = buffer, STACK_DUMP_ARG_PREFIX "%d", offset + FP_TO_ARGS_DIST + nArgs + 1); } break; } printf("%-6s ", pos); dumpVal(*dv); printf("\n"); } } #endif /* ifdef DEBUG_STACK */