summaryrefslogtreecommitdiff
path: root/lib/execute.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/execute.c')
-rw-r--r--lib/execute.c1715
1 files changed, 1715 insertions, 0 deletions
diff --git a/lib/execute.c b/lib/execute.c
new file mode 100644
index 0000000..3bf3c4b
--- /dev/null
+++ b/lib/execute.c
@@ -0,0 +1,1715 @@
+/*
+ * AUTHOR
+ * N. Nielsen
+ *
+ * LICENSE
+ * This software is in the public domain.
+ *
+ * The software is provided "as is", without warranty of any kind,
+ * express or implied, including but not limited to the warranties
+ * of merchantability, fitness for a particular purpose, and
+ * noninfringement. In no event shall the author(s) be liable for any
+ * claim, damages, or other liability, whether in an action of
+ * contract, tort, or otherwise, arising from, out of, or in connection
+ * with the software or the use or other dealings in the software.
+ *
+ * SUPPORT
+ * Send bug reports to: <nielsen@memberwebs.com>
+ */
+
+/* ----------------------------------------------------------------------
+ * Rep Execution Unit
+ * 2000-2001 Copyright, Nate Nielsen
+ */
+
+#include "common/usuals.h"
+#include "common/compat.h"
+#include "common/xstring.h"
+#include "lib/rlib.h"
+#include "priv.h"
+#include "execute.h"
+#include "ops.h"
+
+/* 100 million loops max! */
+#define MAX_PASSES 100000000
+
+/* NOTE: all *end* counts/pointers point to the last char of the actual data */
+
+/* Quick access macro for accessing VM registers */
+#define REGISTER(reg) \
+ (state->vmregs[ARG_GET_REGISTER(reg)])
+#define REGISTER2(reg, off) \
+ (state->vmregs[ARG_GET_REGISTER(reg) + (off)])
+
+/* =========================================================================
+ * MEMORY: Memory blocks are used by the code and are simply a set
+ * of numeric values indexed by key.
+ */
+
+/* _memoryAllocate: ---------------------------------------------------
+ * Helper which double the size of a 'memory' block if needed
+ */
+bool _memoryAllocate(memory* mem)
+{
+ ASSERT_PTR(mem);
+
+ if(!mem->thememory)
+ {
+ /* Allocate new set */
+ mem->thememory = (struct mem*)malloc(sizeof(struct mem) * BLOCK_SIZE);
+ if(!mem->thememory) return false;
+ mem->alloc = BLOCK_SIZE;
+ memoryClearAll(mem);
+ }
+
+ /* If no more space ... */
+ if(mem->alloc <= mem->cur)
+ {
+ /* ...double allocation */
+ size_t sz = sizeof(struct mem) * mem->alloc;
+ mem->thememory = (struct mem*)reallocf(mem->thememory, sz * 2);
+ if(!mem->thememory) return false;
+ mem->alloc *= 2;
+ }
+
+ return true;
+}
+
+
+/* _memoryFind: ----------------------------------------------------
+ * Find memory location for given key
+ */
+struct mem* _memoryFind(memory* mem, uint key)
+{
+ size_t i;
+ ASSERT_PTR(mem);
+
+ for(i = 0; i < mem->cur; i++)
+ {
+ if(key == mem->thememory[i].key)
+ return &(mem->thememory[i]);
+ }
+
+ return 0;
+}
+
+/* memoryInit: -----------------------------------------------------
+ * Initialize and or allocate a memory block
+ */
+bool memoryInit(memory* mem)
+{
+ zero(*mem);
+ return true;;
+}
+
+/* memoryFree: ----------------------------------------------------
+ * Free a memory block
+ */
+void memoryFree(memory* mem)
+{
+ if(mem)
+ {
+ ASSERT_PTR(mem);
+ if(mem->thememory)
+ free(mem->thememory);
+
+ mem->thememory = NULL;
+ }
+}
+
+/* memoryValue: --------------------------------------------------
+ * Get a value for key from memory
+ */
+uint* memoryValue(memory* mem, uint key)
+{
+ struct mem* m;
+ ASSERT_PTR(mem);
+ if(!_memoryAllocate(mem))
+ return NULL;
+
+ m = _memoryFind(mem, key);
+ if(!m)
+ {
+ /* Add new watermark */
+ mem->thememory[mem->cur].key = key;
+ mem->thememory[mem->cur].value = 0;
+ m = &(mem->thememory[mem->cur]);
+ mem->cur++;
+ }
+
+ return &(m->value);
+}
+
+/* memoryClearAll: ---------------------------------------------------
+ * Clear all values from block
+ */
+void memoryClearAll(memory* mem)
+{
+ ASSERT_PTR(mem);
+ mem->cur = 0;
+}
+
+
+/* =========================================================================
+ * DATA:
+ */
+
+/* _dataAllocate: ---------------------------------------------------
+ * Helper which double the size of a 'memory' block if needed
+ */
+bool _dataAllocate(data* dat)
+{
+ ASSERT_PTR(dat);
+
+ if(!dat->thedata)
+ {
+ /* Allocate new set */
+ dat->thedata = (struct dat*)malloc(sizeof(struct dat) * BLOCK_SIZE);
+ if(!dat->thedata) return false;
+ dat->alloc = BLOCK_SIZE;
+ dataClearAll(dat);
+ }
+
+ /* If no more space ... */
+ if(dat->alloc <= dat->cur)
+ {
+ /* ...double allocation */
+ size_t sz = sizeof(struct dat) * dat->alloc;
+ dat->thedata = (struct dat*)reallocf(dat->thedata, sz * 2);
+ if(!dat->thedata) return false;
+ dat->alloc *= 2;
+ }
+
+ return true;
+}
+
+
+/* _dataFind: ----------------------------------------------------
+ * Find memory location for given key
+ */
+struct dat* _dataFind(data* dat, void* key)
+{
+ size_t i;
+ ASSERT_PTR(dat);
+
+ for(i = 0; i < dat->cur; i++)
+ {
+ if(key == dat->thedata[i].key)
+ return &(dat->thedata[i]);
+ }
+
+ return 0;
+}
+
+/* dataInit: -----------------------------------------------------
+ * Initialize and or allocate a data block
+ */
+bool dataInit(data* dat)
+{
+ zero(*dat);
+ return true;
+}
+
+/* dataFree: ----------------------------------------------------
+ * Free a data block
+ */
+void dataFree(data* dat)
+{
+ if(dat)
+ {
+ ASSERT_PTR(dat);
+ if(dat->thedata)
+ {
+ dataClearAll(dat);
+ free(dat->thedata);
+ }
+
+ dat->thedata = NULL;
+ }
+}
+
+/* dataGetValue: --------------------------------------------------
+ * Get a value for key from data
+ */
+void* dataGetValue(data* dat, void* key)
+{
+ struct dat* d;
+ ASSERT_PTR(dat);
+
+ if(d = _dataFind(dat, key))
+ return d->value;
+
+ return NULL;
+}
+
+/* dataSetValue: --------------------------------------------------
+ * Set a value for key
+ */
+bool dataSetValue(data* dat, void* key, void* value)
+{
+ struct dat* d;
+ ASSERT_PTR(dat);
+ if(!_dataAllocate(dat))
+ return false;
+
+ if(d = _dataFind(dat, key))
+ {
+ d->value = value;
+ }
+ else
+ {
+ /* Add new one */
+ dat->thedata[dat->cur].key = key;
+ dat->thedata[dat->cur].value = value;
+ dat->cur++;
+ }
+
+ return true;
+}
+
+/* dataClearAll: ---------------------------------------------------
+ * Clear all values from block
+ */
+void dataClearAll(data* dat)
+{
+ size_t i;
+
+ ASSERT_PTR(dat);
+ if(dat->thedata)
+ {
+ for(i = 0; i < dat->cur; i++)
+ {
+ if(dat->thedata[i].value)
+ free(dat->thedata[i].value);
+ }
+ }
+
+ dat->cur = 0;
+}
+
+
+/* ========================================================================
+ * VARIABLES: Variables are strings indexed by a name.
+ */
+
+/* _variablesAllocate: ----------------------------------------------
+ * Expand a block of variables if necessary
+ */
+bool _variablesAllocate(variables* vars)
+{
+ ASSERT_PTR(vars);
+
+ if(!vars->thevars)
+ {
+ /* Allocate new set */
+ vars->thevars = (struct vari*)malloc(sizeof(struct vari) * BLOCK_SIZE);
+ if(!vars->thevars) return false;
+ vars->alloc = BLOCK_SIZE;
+ vars->cur = 0;
+ }
+
+ /* If no more space ... */
+ if(vars->alloc <= vars->cur)
+ {
+ /* ...double allocation */
+ size_t sz = sizeof(struct vari) * vars->alloc;
+ vars->thevars = (struct vari*)reallocf(vars->thevars, sz * 2);
+ if(!vars->thevars) return false;
+ vars->alloc *= 2;
+ }
+
+ return true;
+}
+
+/* _variablesNew: --------------------------------------------------------
+ * Make a new variable in a given block
+ */
+struct vari* _variablesNew(variables* vars, const char* name,
+ const char* val, size_t cnt)
+{
+ struct vari* v;
+ ASSERT_PTR(vars);
+
+ if(!_variablesAllocate(vars))
+ return NULL;
+
+ v = &(vars->thevars[vars->cur]);
+
+ /* Add new variable */
+ v->name = strdup(name);
+ v->value = (char*)malloc(sizeof(char) * (cnt + 2));
+ starclr(v->value);
+ starnadd(&(v->value), val, cnt);
+
+ /* Oops! */
+ if(!v->name || !v->value)
+ return NULL;
+
+ vars->cur++;
+
+ return v;
+}
+
+/* _variablesFind: ------------------------------------------------------
+ * Find a variable in the block by name
+ */
+struct vari* _variablesFind(variables* vars, const char* name)
+{
+ size_t i;
+ char* val;
+
+ ASSERT_PTR(vars);
+ i = vars->cur;
+
+ while(i--)
+ {
+ if(!strcasecmp(name, vars->thevars[i].name))
+ return &(vars->thevars[i]);
+ }
+
+ /* If not found then look up in the Environment */
+ if(val = getenv(name))
+ return _variablesNew(vars, name, val, strlen(val));
+
+ return NULL;
+}
+
+/* variablesInit: ------------------------------------------------------
+ * Initialize and possibly allocate a variable set
+ */
+bool variablesInit(variables* vars)
+{
+ zero(*vars);
+ return true;
+}
+
+/* variablesFree: ------------------------------------------------------
+ * Free a set of variables
+ */
+void variablesFree(variables* vars)
+{
+ if(vars)
+ {
+ ASSERT_PTR(vars);
+ if(vars->thevars)
+ free(vars->thevars);
+ vars->thevars = NULL;
+ }
+}
+
+/* variablesAddBytes: --------------------------------------------------
+ * Add a counted string to a variable.
+ */
+bool variablesAddBytes(variables* vars, const char* name,
+ const char* val, size_t cnt)
+{
+ struct vari* v;
+ ASSERT_PTR(vars);
+
+ v = _variablesFind(vars, name);
+ if(!v)
+ {
+ return _variablesNew(vars, name, val, cnt) != NULL;
+ }
+ else
+ {
+ /* Already have variable. Just add value */
+ ASSERT(v->value);
+ starnadd(&(v->value), val, cnt);
+ return v->value != NULL;
+ }
+}
+
+/* variablesAdd: -------------------------------------------------------
+ * Add a string to a variable.
+ */
+bool variablesAdd(variables* vars, const char* name, const char* val)
+{
+ ASSERT_PTR(vars);
+ return variablesAddBytes(vars, name, val, strlen(val));
+}
+
+/* variablesClear: ----------------------------------------------------
+ * Clear a variable's value.
+ */
+bool variablesClear(variables* vars, const char* name)
+{
+ struct vari* v;
+ ASSERT_PTR(vars);
+ if(v = _variablesFind(vars, name))
+ starclr(v->value);
+
+ return true;
+}
+
+/* variablesClearAll: -------------------------------------------------
+ * Delete all variables from set
+ */
+bool variablesClearAll(variables* vars)
+{
+ ASSERT_PTR(vars);
+ if(vars->thevars)
+ {
+ while(vars->cur--)
+ {
+ ASSERT(vars->thevars[vars->cur].name);
+ free(vars->thevars[vars->cur].name);
+ ASSERT(vars->thevars[vars->cur].value);
+ free(vars->thevars[vars->cur].value);
+ }
+
+ vars->cur = 0;
+ }
+
+ return true;
+}
+
+/* _escapeString: -----------------------------------------------------
+ * Helper function to make a string pass through the regex compiler
+ */
+static char* _escapeString(const char* string)
+{
+ const char kSpecialChars[] = "$^*(){}[]\\?+.";
+ size_t pos = 0;
+ size_t cnt = 0;
+ char* buff;
+
+ size_t len = strlen(string);
+
+ while((pos += strcspn(string + pos, kSpecialChars)) < len)
+ cnt++, pos++;
+
+ if(buff = (char*)malloc(sizeof(char) * (len + cnt + 1)))
+ {
+ pos = 0;
+ strcpy(buff, string);
+ while((pos += strcspn(buff + pos, kSpecialChars)) < len + cnt)
+ strins(buff + pos, "\\"), pos += 2;
+ }
+
+ return buff;
+}
+
+/* variablesSubstitute: -----------------------------------------------
+ * Perform variable and register substitution on a string
+ */
+int variablesSubstitute(variables* vars, r_stream* stream, r_script* script,
+ char** pstr, bool mode)
+{
+ size_t len = strlen(*pstr);
+ char* next = *pstr;
+ struct internal_state* state = stream->state;
+
+ ASSERT_PTR(vars);
+ ASSERT_PTR(state);
+
+ #define SYNTAX_ERROR(s) \
+ do{ \
+ scriptSetError(script, s); \
+ return R_SYNTAX; \
+ } while(0)
+
+ /*
+ If mode == true then we're substituting inside a regular expression
+ otherwise we're substituting in replaced text
+ */
+
+ /* Find next backslash or percent */
+ while(next += strcspn(next, "\\%"), next && next[0] != 0)
+ {
+ switch(*next)
+ {
+
+ /* It's a variable or a register */
+ case '%':
+
+ /* Is it a register? */
+ if(isdigit(next[1]))
+ {
+ /* Registers only in replace mode replace mode */
+ if(!mode)
+ {
+ uint reg = next[1] - 0x30;
+
+ /* Get register number */
+ if(reg < REGISTER(r_cg))
+ {
+ /* Save offset for reallocations */
+ size_t off = next - *pstr;
+ size_t reglen = REGISTER2(r_e0, reg) - REGISTER2(r_b0, reg);
+
+ /* Reallocate to fit register text */
+ strrsrv(*pstr, strlen(*pstr) + reglen);
+ if(!(*pstr)) return R_NOMEM;
+
+ /* offset next properly */
+ next = *pstr + off;
+
+ /* Replace \N with register text */
+ next = strnrep(next, 2,
+ (stream->nextIn + ABS_TO_REL(REGISTER2(r_b0, reg), stream->state)),
+ reglen);
+ }
+ else
+ /* If invalid number just blow away */
+ next = strnrep(next, 2, "", 0);
+ }
+ }
+
+
+ /* Otherwise it's a variable */
+ else
+ {
+ bool multi = false;
+ size_t len, off;
+ char temp;
+ struct vari* v;
+ const char* value;
+
+ /* Get the name */
+ len = strspn(next + 1, kValidIdentifier);
+ if(len == 0) SYNTAX_ERROR("Invalid variable name.");
+
+ /* Increment length for the % prefix */
+ len++;
+
+ /* Null terminate the variable name */
+ temp = next[len];
+ next[len] = 0;
+
+ /* Do we have this variable? */
+ if(v = _variablesFind(vars, next + 1))
+ value = v->value;
+ else
+ value = "\0\0";
+
+ /* Check if the variable is an array */
+ if(mode)
+ multi = starnext(value) ? true : false;
+
+ /* Unnull-terminate it */
+ next[len] = temp;
+
+ /* Get offset for reallocations */
+ off = next - *pstr;
+
+ /* Reallocate the string to accomodate new replacement */
+ strrsrv(*pstr, strlen(*pstr) + (starend(value) - value) + 4);
+ if(!*pstr) return R_NOMEM;
+
+ /* Offset next back properly */
+ next = *pstr + off;
+
+ /* Eat variable name and add open parentheses if needed
+ The '?:' after the opening paren denotes a non capturing
+ group */
+ next = strrep(next, len, multi ? "(?:" : "");
+
+ do
+ {
+ /* If in regular expression mode ... */
+ char* escval;
+ if(mode)
+ {
+ /* ... escape the value */
+ escval = _escapeString(value);
+ if(!escval) return R_NOMEM;
+ }
+
+ /* Insert the value */
+ next = strrep(next, 0, mode ? escval : value);
+
+ /* If in regular expression mode free escaped. */
+ if(mode)
+ free(escval);
+
+ /* If not multi then only put the first one in */
+ if(!multi)
+ break;
+
+ /* Add a pipe (alternation operator) if we have more values */
+ else
+ next = strrep(next, 0, starnext(value) ? "|" : "");
+
+ }
+ while(value = starnext(value));
+
+ /* Add closing parentheses if needed */
+ next = strrep(next, 0, multi ? ")" : "");
+ }
+ break;
+
+
+ /* It's a variable name or escaped character */
+ case '\\':
+
+ /*
+ Only unescape in replace mode
+ Regular expressions will unescape everything else
+ */
+ if(!mode)
+ next = strnrep(next, 1, "", 0);
+ else
+ next++;
+
+ next++;
+
+ break;
+ }
+ }
+
+ return R_OK;
+}
+
+/* variablesValidName: -------------------------------------------------
+ * Is the given name a valid variable name
+ */
+bool variablesValidName(const char* name)
+{
+ return strspn(name, kValidIdentifier) >= strlen(name);
+}
+
+/* variablesHasVars: ----------------------------------------------------
+ * Does the given string have variables or registers that need
+ * substitution?
+ */
+bool variablesHasVars(const char* string)
+{
+ const char* cur = string;
+
+ if(cur = strchr(cur, '%'))
+ {
+ if(!isEscaped(string, cur))
+ return true;
+
+ cur++;
+ }
+
+ return false;
+}
+
+/* =========================================================================
+ * LOCKS: Locks are used by the engine to implement the replacement locks
+ */
+
+/* _locksAllocate: -------------------------------------------------------
+ * Expand a block of locks if needed
+ */
+bool _locksAllocate(locks* lcks)
+{
+ if(!lcks->thelocks)
+ {
+ /* Allocate the first set */
+ lcks->thelocks = (struct lock*)malloc(sizeof(struct lock) * BLOCK_SIZE);
+ if(!lcks->thelocks) return false;
+ lcks->alloc = BLOCK_SIZE;
+ locksClearAll(lcks);
+ }
+
+ /* If not enough space ... */
+ if(lcks->alloc <= lcks->cur)
+ {
+ /* ... reallocate double */
+ size_t sz = sizeof(struct lock) * lcks->alloc;
+ lcks->thelocks = (struct lock*)reallocf(lcks->thelocks, sz * 2);
+ if(!lcks->thelocks) return false;
+ lcks->alloc *= 2;
+ }
+
+ return true;
+}
+
+/* locksInit: -------------------------------------------------------------
+ * Initialize and or allocate a block of locks
+ */
+bool locksInit(locks* lcks)
+{
+ zero(*lcks);
+ return true;
+}
+
+/* locksFree: -------------------------------------------------------------
+ * Free a set of locks
+ */
+void locksFree(locks* lcks)
+{
+ if(lcks)
+ {
+ ASSERT_PTR(lcks);
+ if(lcks->thelocks)
+ free(lcks->thelocks);
+ lcks->thelocks = NULL;
+ }
+}
+
+#define RANGE_ADD(bd, ed, b1, e1) \
+ (((bd) = (bd) < (b1) ? (bd) : (b1)), ((ed) = (ed) > (e1) ? (ed) : (e1)))
+
+#define RANGE_INTERSECTS(b1, e1, b2, e2) \
+ ((b1) < (e2) && (e1) > (b2))
+
+#define RANGE_BEFORE(b1, e1, b2, e2) \
+ ((b1) <= (b2) && (e1) <= (e2))
+
+
+/* locksAdd: ---------------------------------------------------------------
+ * Add a lock to the set
+ */
+bool locksAdd(locks* lcks, size_t beg, size_t end)
+{
+ size_t i = 0;
+
+ for(; i < lcks->cur; i++)
+ {
+ if(RANGE_INTERSECTS(beg, end, lcks->thelocks[i].beg, lcks->thelocks[i].end))
+ {
+ RANGE_ADD(lcks->thelocks[i].beg, lcks->thelocks[i].end, beg, end);
+
+ /* Clean up any doubles */
+ for(i = 0; i < lcks->cur - 1; i++)
+ {
+ if(RANGE_INTERSECTS(lcks->thelocks[i].beg, lcks->thelocks[i].end,
+ lcks->thelocks[i + 1].beg, lcks->thelocks[i + 1].end))
+ {
+ RANGE_ADD(lcks->thelocks[i].beg, lcks->thelocks[i].end,
+ lcks->thelocks[i + 1].beg, lcks->thelocks[i + 1].end);
+
+ lcks->cur--;
+ memmove(lcks->thelocks + i + 1, lcks->thelocks + i + 2,
+ sizeof(lcks->thelocks[0]) * (lcks->cur - (i + 1)));
+ i--;
+ }
+ }
+
+ return true;
+ }
+
+ if(RANGE_BEFORE(beg, end, lcks->thelocks[i].beg, lcks->thelocks[i].end))
+ break;
+ }
+
+ if(!_locksAllocate(lcks)) return false;
+
+ /* Move the locks one down */
+ memmove(lcks->thelocks + i + 1, lcks->thelocks + i,
+ sizeof(lcks->thelocks[0]) * (lcks->cur - i));
+
+ lcks->thelocks[i].beg = beg;
+ lcks->thelocks[i].end = end;
+
+ lcks->cur++;
+
+ return true;
+}
+
+#ifdef _DEBUG
+static void _locksTestIntersects()
+{
+ ASSERT(!RANGE_INTERSECTS(0x0F, 0x15, 0x15, 0x15));
+ ASSERT(RANGE_INTERSECTS(0x0F, 0x15, 0x0F, 0x15));
+ ASSERT(RANGE_INTERSECTS(0x10, 0x15, 0x0F, 0x15));
+ ASSERT(RANGE_INTERSECTS(0x0F, 0x12, 0x0F, 0x15));
+ ASSERT(RANGE_INTERSECTS(0x10, 0x12, 0x0F, 0x15));
+ ASSERT(RANGE_INTERSECTS(0x0F, 0x15, 0x10, 0x12));
+ ASSERT(RANGE_INTERSECTS(0x10, 0x1A, 0x0F, 0x15));
+ ASSERT(RANGE_INTERSECTS(0x02, 0x12, 0x0F, 0x15));
+ ASSERT(!RANGE_INTERSECTS(0x15, 0x15, 0x15, 0x15));
+ ASSERT(!RANGE_INTERSECTS(0x15, 0x15, 0x15, 0x20));
+}
+#endif
+
+/* locksCheck: -----------------------------------------------------------
+ * Check a range against the locks
+ */
+bool locksCheck(locks* lcks, r_stream* stream, size_t beg, size_t end)
+{
+ size_t cnt;
+
+#ifdef _DEBUG
+ _locksTestIntersects();
+#endif
+
+ for(cnt = 0; cnt < lcks->cur; cnt++)
+ if(RANGE_INTERSECTS(beg, end, lcks->thelocks[cnt].beg, lcks->thelocks[cnt].end))
+ return true;
+
+ return false;
+}
+
+
+/* ============================================================================
+ * REPLACEMENT LIST: Contains the text of any replacements. They're copied
+ * into the output later.
+ */
+
+/* replacementAlloc: ---------------------------------------------------------
+ * Prepare and allocate a replacement
+ */
+int replacementAlloc(r_stream* stream, const char* text, replacement** pprep)
+{
+ int ret;
+
+ size_t len = strlen(text);
+ ret = R_OK;
+
+ /* Allocate the replacement. Note that text hangs off the end */
+ *pprep = (replacement*)malloc(sizeof(replacement) + (sizeof(char) * len));
+ if(!*pprep)
+ ret = R_NOMEM;
+ else
+ {
+ /* Set it up properly */
+ memset(*pprep, 0, sizeof(replacement) + len);
+ (*pprep)->next = NULL;
+
+ /* Copy string on end of buffer */
+ strcpy((*pprep)->text, text);
+ }
+
+ return ret;
+}
+
+
+/* replacementAdd: -----------------------------------------------------------
+ * Insert replacement into queue in the right order (IMPORTANT!)
+ */
+void replacementAdd(replacement* repl, r_stream* stream)
+{
+ replacement top;
+ replacement* first = &top;
+ top.beg = top.end = 0;
+ top.next = stream->state->replaces;
+
+ /* Find appropriate pos */
+ while(first && first->next && (first->next->end < repl->end))
+ first = first->next;
+
+ while(first && first->next && (first->next->beg < repl->beg))
+ first = first->next;
+
+ /* Hook in */
+ repl->next = first->next;
+ first->next = repl;
+
+ stream->state->replaces = top.next;
+}
+
+
+/* replacementPop: -------------------------------------------------------
+ * Remove and return a replacement from the queue
+ */
+replacement* replacementPop(replacement* repl)
+{
+ replacement* ret = repl->next;
+ free(repl);
+ return ret;
+}
+
+/* replacementDump: ------------------------------------------------------
+ * Dump all replacements in the stream to stderr
+ */
+void replacementDump(r_stream* stream)
+{
+ replacement* repl = stream->state->replaces;
+
+ while(repl)
+ {
+ fprintf(stderr, " beg: %x end: %x text: \'%s\'\n", repl->beg, repl->end, repl->text);
+ repl = repl->next;
+ }
+}
+
+
+/* =========================================================================
+ * REGISTER HELPER FUNCTIONS
+ */
+
+/* regsSet: ----------------------------------------------------------
+ * Offset and copy an entire set of regs int the main registers
+ */
+static void regsSet(struct internal_state* state, int pcreregs[], size_t offset)
+{
+ size_t i;
+ for(i = 0; i < REGISTER(r_cg); i++)
+ {
+ REGISTER2(r_b0, i) = pcreregs[i * 2] + offset;
+ REGISTER2(r_e0, i) = pcreregs[(i * 2) + 1] + offset;
+ }
+
+ /* Set rest of registers as invalid */
+ for( ; i < MAX_REGS; i++)
+ {
+ REGISTER2(r_b0, i) = ~0;
+ REGISTER2(r_b0, i) = ~0;
+ }
+}
+
+
+/* ===========================================================================
+ * MAIN EXECUTION FUNCTIONS
+ */
+
+
+/* vmInit: --------------------------------------------------------------
+ * Initialize state for execution
+ */
+bool vmInit(r_stream* stream)
+{
+ struct internal_state* state = stream->state;
+ ASSERT(stream->state);
+
+ if(!variablesInit(&(state->vars)) ||
+ !memoryInit(&(state->mem)) ||
+ !locksInit(&(state->lcks)) ||
+ !dataInit(&(state->working)))
+ return R_NOMEM;
+
+ /* init replaces */
+ state->replaces = NULL;
+
+ return true;
+}
+
+
+/* vmFree: --------------------------------------------------------------
+ * Undo initialization
+ */
+void vmFree(r_stream* stream)
+{
+ struct internal_state* state = stream->state;
+ ASSERT(stream->state);
+
+ while(state->replaces)
+ state->replaces = replacementPop(state->replaces);
+
+ variablesFree(&(state->vars));
+ memoryFree(&(state->mem));
+ locksFree(&(state->lcks));
+ dataFree(&(state->working));
+}
+
+
+/* vmClean: -------------------------------------------------------------
+ * Prepare a VM for a totally new replacement operation
+ */
+void vmClean(r_stream* stream)
+{
+ struct internal_state* state = stream->state;
+ ASSERT(stream->state);
+
+ while(state->replaces)
+ state->replaces = replacementPop(state->replaces);
+
+ variablesClearAll(&(state->vars));
+ memoryClearAll(&(state->mem));
+ locksClearAll(&(state->lcks));
+ dataClearAll(&(state->working));
+ stream->state->offset = 0;
+}
+
+
+
+
+/* rvalue: --------------------------------------------------------------
+ * Return a value to be used on the right (value) side of an expression
+ */
+#ifdef USE_STACK_VARS
+uint rvalue(vmop_t* ops, struct internal_state* state,
+ memory* mem, memory* stackMem)
+#else
+uint rvalue(vmop_t* ops, struct internal_state* state, memory* mem)
+#endif
+{
+ switch(ARG_TYPE(*ops))
+ {
+ case ARG_VAL_TYPE:
+ return ARG_GET_VALUE(*((uint*)ops));
+ case ARG_REG_TYPE:
+ return REGISTER(*ops);
+ case ARG_MEM_TYPE:
+ return *(memoryValue(mem, ARG_GET_MEMORY(*((uint*)ops))));
+ case ARG_STACK_TYPE:
+#ifdef USE_STACK_VARS
+ return *(memoryValue(stackMem, ARG_GET_STACK(*((uint*)ops))));
+#else
+ return *(memoryValue(mem, ARG_GET_STACK(*((uint*)ops))));
+#endif
+ default:
+ ASSERT(false);
+ return 0;
+ }
+}
+
+
+/* lvalue: -----------------------------------------------------------------
+ * Return a value to be used on the left side (assigned) of an expression
+ */
+#ifdef USE_STACK_VARS
+static uint* lvalue(vmop_t* ops, struct internal_state* state,
+ memory* mem, memory* stackMem)
+#else
+static uint* lvalue(vmop_t* ops, struct internal_state* state, memory* mem)
+#endif
+{
+ switch(ARG_TYPE(*ops))
+ {
+ case ARG_VAL_TYPE:
+ ASSERT(false && "Can't put a value on left side.");
+ return 0;
+ case ARG_REG_TYPE:
+ return &(REGISTER(*ops));
+ case ARG_MEM_TYPE:
+ return memoryValue(mem, ARG_GET_MEMORY(*((uint*)ops)));
+ case ARG_STACK_TYPE:
+#ifdef USE_STACK_VARS
+ return memoryValue(stackMem, ARG_GET_STACK(*((uint*)ops)));
+#else
+ return memoryValue(mem, ARG_GET_MEMORY(*((uint*)ops)));
+#endif
+ default:
+ ASSERT(false);
+ return 0;
+ }
+}
+
+
+/* vmExecute: -------------------------------------------------------------
+ * The main VM run loop
+ */
+int vmExecute(r_stream* stream, r_script* script)
+{
+#ifdef USE_STACK_VARS
+ #define RVALUE(ops) rvalue(ops, state, &(state->mem), stackVars)
+ #define LVALUE(ops) lvalue(ops, state, &(state->mem), stackVars)
+#else
+ #define RVALUE(ops) rvalue(ops, state, &(state->mem))
+ #define LVALUE(ops) lvalue(ops, state, &(state->mem))
+#endif
+
+ #define PUSH_STACK(top, v) ((top)++[0] = (v))
+ #define POP_STACK(top) ((--top)[0])
+
+ /* Jump to cleanup label instead of return */
+ #define RETURN(r) \
+ do { \
+ retval = r; \
+ goto cleanup; \
+ } while (0)
+
+ struct internal_state* state;
+ int retval = R_OK;
+ vmop_t* ops;
+ uint passes = 0;
+
+ /* These are the registers passed to PCRE */
+ int pcreregs[MAX_REGS * 3];
+ int num_regs = 0;
+
+ /* And over here we have the stack */
+ uint* vmStack = NULL;
+ size_t allocStack = 0;
+ uint** stack;
+ #define STACK *stack
+
+#ifdef USE_STACK_VARS
+ memory* stackVars;
+#endif
+
+ /* The text buffer */
+ char* text = NULL;
+
+ /* We just setup some vars for easy access to structs */
+ state = stream->state;
+ ops = script->ops;
+ zero(state->vmregs);
+ ASSERT(script->ops);
+
+ stack = (uint**)&(state->vmregs[r_sp >> 2]);
+
+
+ /* Set the initial limits in the x1 and y1 registers */
+ REGISTER(r_x1) = REL_TO_ABS(0, state);
+ REGISTER(r_y1) = REL_TO_ABS(stream->availIn, state);
+
+ /* Stack variables */
+#ifdef USE_STACK_VARS
+ stackVars = (memory*)malloc(sizeof(memory));
+ if(!stackVars || !memoryInit(stackVars))
+ RETURN(R_NOMEM);
+#endif
+
+ /* Preallocate some memory for the text buffer */
+ text = malloc(sizeof(char) * 256);
+ if(!text) RETURN(R_NOMEM);
+ text[0] = 0;
+
+
+ while(1)
+ {
+ vmop_t op = *ops;
+ ops++;
+
+ /* The text buffer should always be pointing to a valid
+ block of memory */
+ ASSERT(text != NULL);
+
+
+ /* Check and see if we have enough stack and allocate if not */
+ if((STACK + 0x010) > (vmStack + allocStack))
+ {
+ size_t off = STACK - vmStack;
+ vmStack = (uint*)reallocf(vmStack, (allocStack + 0x080) * sizeof(uint));
+ if(!vmStack)
+ RETURN(R_NOMEM);
+
+ allocStack += 0x080;
+ STACK = vmStack + off;
+ }
+
+
+ /* Main switch which dispatches the ops */
+ switch(op)
+ {
+
+ /* end: Finished executing script (but can come back again for more) */
+ case o_end:
+ RETURN(R_OK);
+
+
+ /* nop: Do nothing */
+ case o_nop:
+ break;
+
+
+ /* push: Push a value on the stack */
+ case o_push:
+ PUSH_STACK(STACK, RVALUE(ops));
+
+ INC_ARGUMENT(ops);
+ break;
+
+
+ /* pop: Pop a value from the stack */
+ case o_pop:
+ *(LVALUE(ops)) = POP_STACK(STACK);
+ INC_ARGUMENT(ops);
+ break;
+
+
+ /* lock: Lock the area between the selected area */
+ case o_lock:
+ {
+ uint beg = RVALUE(ops);
+ INC_ARGUMENT(ops);
+
+ if(!locksAdd(&(state->lcks), beg, RVALUE(ops)))
+ RETURN(R_NOMEM);
+
+ INC_ARGUMENT(ops);
+
+ /* Locking is an action too */
+ REGISTER(r_ac) = 1;
+ }
+ break;
+
+
+ /* check: Check the selected area against any locks */
+ case o_check:
+ {
+ uint beg = RVALUE(ops);
+ INC_ARGUMENT(ops);
+
+ /* Check the lowest registers against the locks it against the locks */
+ REGISTER(r_fe) = locksCheck(&(state->lcks), stream, beg, RVALUE(ops))
+ ? 0 : 1;
+
+ INC_ARGUMENT(ops);
+ }
+ break;
+
+
+
+ /* match: Match a pattern buffer */
+ case o_match:
+ {
+ uint beg, end, begLimit, endLimit, begMatch, endMatch;
+ match_op* header;
+ pcre* re = NULL;
+ pcre_extra* extra = NULL;
+ bool cache = false;
+ size_t i = 0;
+ int rt;
+
+ beg = ABS_TO_REL(RVALUE(ops), state);
+ INC_ARGUMENT(ops);
+ end = ABS_TO_REL(RVALUE(ops), state);
+ INC_ARGUMENT(ops);
+
+ begLimit = ABS_TO_REL(REGISTER(r_x1), state);
+ endLimit = ABS_TO_REL(REGISTER(r_y1), state);
+
+ /* Determine what kind of a match we're talking about here */
+ header = (match_op*)ops;
+
+ /* This is the only type of regular expression we support
+ at the moment */
+ ASSERT(header->type & kMatchPcre);
+
+
+ /*
+ * Check if we've already cached the compiled
+ * regular expression.
+ * - We use the op header pointer as key to the pcre struct
+ * - And the pcre struct pointer as the key to the pcre_extra struct
+ */
+ if(re = (pcre*)dataGetValue(&(state->working), header))
+ {
+ /* This prevents the freeing of stuff below */
+ cache = true;
+
+ /* Get out the pcre_extra if present */
+ extra = (pcre_extra*)dataGetValue(&(state->working), re);
+ }
+
+ /* Otherwise we compile it */
+ else
+ {
+ match_op_pcre* pcreop = (match_op_pcre*)header;
+ char* pattern = strdup(pcreop->pattern);
+ int erroroffset;
+ const char* error = NULL;
+
+ /* If there's variables then don't cache and ... */
+ if(!(cache = !variablesHasVars(pattern)))
+ {
+ /* ... do variable substitution */
+ rt = variablesSubstitute(&(state->vars), stream, script, &pattern, true);
+ if(rt < 0) RETURN(rt);
+ }
+
+ /* Compile the pattern */
+ re = pcre_compile(pattern, pcreop->options,
+ &error, &erroroffset, NULL);
+
+ if(!re)
+ {
+ if(error)
+ scriptSetError(script, error);
+ RETURN(R_REGEXP);
+ }
+
+ /*
+ * If there's no variables in the regular expression
+ * then we can cache the compiled pcre and study it
+ */
+ if(cache)
+ {
+ if(!dataSetValue(&(state->working), header, re))
+ RETURN(R_NOMEM);
+
+ extra = pcre_study(re, 0, &error);
+ if(error)
+ {
+ scriptSetError(script, error);
+ RETURN(R_REGEXP);
+ }
+
+ if(extra && !dataSetValue(&(state->working), re, extra))
+ RETURN(R_NOMEM);
+ }
+
+ free(pattern);
+ }
+
+
+ begMatch = beg;
+ endMatch = end;
+
+ /* Set failed flag */
+ REGISTER(r_fe) = 0;
+
+ while(i < locksSize(&(state->lcks)) &&
+ locksEnd(&(state->lcks), i) <= REL_TO_ABS(beg, state))
+ i++;
+
+ do
+ {
+ if(i < locksSize(&(state->lcks)))
+ endMatch = ABS_TO_REL(locksBeg(&(state->lcks), i), state);
+ else if(i >= locksSize(&(state->lcks)))
+ endMatch = end;
+
+ /* If that put it too high then bring back */
+ if(endMatch > end)
+ endMatch = end;
+
+ /* If we haven't overstepped the bounds, search */
+ if(begMatch <= endMatch)
+ {
+ int opts = 0;
+
+ /*
+ * We have to do a little trickery here
+ * First we fool pcre_exec into thinking that our range start
+ * is the beginning of the string. We fix up the registers later
+ *
+ * Then we fool'm into thinking that the our search end is the
+ * end of the string, but if it's really not the end of the
+ * range then set not_eol so that '$' doesn't match.
+ */
+
+ if(endMatch != endLimit)
+ opts |= PCRE_NOTEOL;
+
+ /* Do actual search */
+ /* TODO: Get this ready for binary replacements */
+ rt = pcre_exec(re, extra,
+ ((char*)stream->nextIn + begLimit), /* Data */
+ endMatch - begLimit, /* size */
+ begMatch - begLimit, /* position */
+ opts, /* options */
+ pcreregs, /* group registers */
+ MAX_REGS * 3); /* number of registers */
+
+ /* These are programmer errors */
+ ASSERT(rt != PCRE_ERROR_NULL);
+ ASSERT(rt != PCRE_ERROR_BADOPTION);
+ ASSERT(rt != PCRE_ERROR_BADMAGIC);
+ ASSERT(rt != PCRE_ERROR_UNKNOWN_NODE);
+ ASSERT(rt != PCRE_ERROR_NOSUBSTRING);
+
+ if(rt == PCRE_ERROR_NOMEMORY)
+ RETURN(R_NOMEM);
+
+ if(rt >= 0)
+ {
+ /* Found a match! */
+
+ /* Do group register maintainance */
+ REGISTER(r_cg) = rt;
+ regsSet(state, pcreregs, REL_TO_ABS(begLimit, state));
+
+ /* Set succeeded flag */
+ REGISTER(r_fe) = 1;
+
+ /* Get out of here */
+ break;
+ }
+
+ /* Make sure we've got all the errors */
+ else if(rt != -1)
+ ASSERT(0);
+
+ }
+
+ /* The beginning of the next search block should be
+ the end of the current lock */
+ if(i < locksSize(&(state->lcks)))
+ begMatch = ABS_TO_REL(locksEnd(&(state->lcks), i), state);
+
+ i++;
+ }
+ while(endMatch < end);
+
+ if(!cache)
+ {
+ if(re)
+ free(re);
+ if(extra)
+ free(extra);
+ }
+
+ ops += match_op_size(*header);
+ }
+
+ break;
+
+
+
+ /* setvar: Add selected text to a variable */
+ case o_setvar:
+ {
+ var_op* pOp = (var_op*)ops;
+
+ /* TODO: Get this ready for binary replacements */
+ if(!variablesAddBytes(&(state->vars), (char*)pOp->name, text, strlen(text)))
+ RETURN(R_NOMEM);
+
+ ops += var_op_size(*pOp);
+ }
+ break;
+
+
+ /* setvar: clear a variable */
+ case o_clrvar:
+ {
+ var_op* pOp = (var_op*)ops;
+
+ /* TODO: Get this ready for binary variables */
+ variablesClear(&(state->vars), (char*)pOp->name);
+ ops += var_op_size(*pOp);
+ }
+ break;
+
+
+ case o_je:
+ case o_jne:
+ case o_jmp:
+
+ /* Get the conditional jumps out of the way */
+ if((op == o_je && REGISTER(r_fe) == 0) ||
+ (op == o_jne && REGISTER(r_fe) > 0))
+ {
+ INC_ARGUMENT(ops);
+ break;
+ }
+ else
+ {
+ /* Now do the jump */
+ vmop_t* opOld = ops;
+
+ /* Change the op to the indicated pos */
+ ops = script->ops + RVALUE(ops);
+
+ /*
+ * If a backwards jump then count as a "pass" thingy
+ * we use this count to catch endless loop errors
+ */
+
+ /* TODO: I don't think we need this anymore */
+ if(opOld > ops)
+ {
+ passes++;
+ if(passes > MAX_PASSES)
+ return R_LOOP;
+ }
+ }
+ break;
+
+
+ /* repl: Replace last match with given text */
+ case o_repl:
+ {
+ uint beg, end;
+ replacement* rep = NULL;
+ int rt;
+
+ beg = RVALUE(ops);
+ INC_ARGUMENT(ops);
+ end = RVALUE(ops);
+ INC_ARGUMENT(ops);
+
+ /* Get the formatted replacement */
+ rt = replacementAlloc(stream, text, &rep);
+ if(rt < 0)
+ RETURN(rt);
+
+ rep->beg = beg;
+ rep->end = end;
+
+ /* If we have a confirm function then ask about replacement */
+ if(stream->fMatch)
+ {
+ r_replace repl;
+ repl.from = ABS_TO_REL(beg, state);
+ repl.flen = end - beg;
+ repl.to = (byte*)rep->text;
+ repl.tlen = strlen(rep->text);
+ repl.offset = beg;
+
+ if(!(stream->fMatch)(stream, &repl))
+ {
+ free(rep);
+ rep = NULL;
+ }
+ }
+
+ if(rep)
+ {
+ /* Add it! */
+ REGISTER(r_ac) = 1;
+ stream->total++;
+ replacementAdd(rep, stream);
+ }
+ }
+
+ break;
+
+
+ /* stop: Stop execution of script */
+ case o_stop:
+ {
+ uint error = RVALUE(ops);
+ INC_ARGUMENT(ops);
+
+ scriptSetError(script, text);
+
+ if(error == 0)
+ RETURN(R_DONE);
+ else
+ RETURN(R_USER);
+ }
+ break;
+
+
+ /* cmp: compare two values and set registers accordingly */
+ case o_cmp:
+ {
+ uint arg1, arg2;
+
+ arg1 = RVALUE(ops);
+ INC_ARGUMENT(ops);
+ arg2 = RVALUE(ops);
+ INC_ARGUMENT(ops);
+
+ REGISTER(r_fe) = (arg1 == arg2) ? 1 : 0;
+ REGISTER(r_fg) = (arg1 > arg2) ? 1 : 0;
+ REGISTER(r_fl) = (arg1 < arg2) ? 1 : 0;
+ }
+ break;
+
+
+ /* test: Test whether value is 0 or not */
+ case o_test:
+ REGISTER(r_fe) = RVALUE(ops) != 0;
+ INC_ARGUMENT(ops);
+ break;
+
+
+ /* mov: Set one value to another */
+ case o_mov:
+ {
+ uint* dest = LVALUE(ops);
+ INC_ARGUMENT(ops);
+ *dest = RVALUE(ops);
+ INC_ARGUMENT(ops);
+ }
+ break;
+
+
+ /* call: Call a function */
+ case o_call:
+ {
+#ifdef USE_STACK_VARS
+ memory* newStack;
+#endif
+ uint pos = RVALUE(ops);
+ INC_ARGUMENT(ops);
+
+ /* Push current instruction pointer on the stack */
+ PUSH_STACK(STACK, ops - script->ops);
+
+ /* Change instruction pointer to function start */
+ ops = script->ops + pos;
+
+#ifdef USE_STACK_VARS
+ /* Push new set of stack variables */
+ newStack = (memory*)malloc(sizeof(memory));
+ if(!newStack || !memoryInit(newStack))
+ RETURN(R_NOMEM);
+
+ newStack->prev = stackVars;
+ stackVars = newStack;
+#endif
+ }
+ break;
+
+
+ /* ret: Return from a function */
+ case o_ret:
+ {
+#ifdef USE_STACK_VARS
+ memory* prev;
+#endif
+ /* Get location to return to */
+ uint pos = POP_STACK(STACK);
+
+ /* Change instruction pointer to returned location */
+ ops = script->ops + pos;
+
+#ifdef USE_STACK_VARS
+ /* Get previous set of stack vars */
+ ASSERT(stackVars->prev);
+ prev = stackVars->prev;
+ memoryFree(stackVars);
+ free(stackVars);
+ stackVars = prev;
+#endif
+ }
+ break;
+
+
+ /* add: Add 2 values */
+ case o_add:
+ {
+ /* Get first value */
+ uint* val = LVALUE(ops);
+ INC_ARGUMENT(ops);
+ *val += RVALUE(ops);
+ INC_ARGUMENT(ops);
+ }
+ break;
+
+
+ /* sub: Subtract values */
+ case o_sub:
+ {
+ /* Get first value */
+ uint* val = LVALUE(ops);
+ INC_ARGUMENT(ops);
+ *val -= RVALUE(ops);
+ INC_ARGUMENT(ops);
+ }
+ break;
+
+
+ /* text: set the text buffer */
+ case o_text:
+ {
+ text_op* top = (text_op*)ops;
+ int rt;
+
+ text = (char*)reallocf(text, top->len + 1);
+ if(!text) RETURN(R_NOMEM);
+
+ memcpy(text, top->string, top->len * sizeof(char));
+ text[top->len] = 0;
+
+ rt = variablesSubstitute(&(state->vars), stream, script, &text, false);
+ if(rt < 0) RETURN(rt);
+
+ ops += text_op_size(*top);
+ }
+ break;
+
+ /* msg: output text in buffer */
+ case o_msg:
+ {
+ if(stream->fMessage)
+ (stream->fMessage)(stream, text);
+ }
+ break;
+
+ default:
+ /* Invalid Instruction! */
+ ASSERT(false);
+ }
+ }
+
+cleanup:
+ if(vmStack)
+ free(vmStack);
+
+ while(stackVars)
+ {
+ memory* prev = stackVars->prev;
+ memoryFree(stackVars);
+ free(stackVars);
+ stackVars = prev;
+ }
+
+ return retval;
+}
+
+
+