| /* |
| * v m . c |
| * Forth Inspired Command Language - virtual machine methods |
| * Author: John Sadler (john_sadler@alum.mit.edu) |
| * Created: 19 July 1997 |
| * $Id: vm.c,v 1.17 2010/09/13 18:43:04 asau Exp $ |
| */ |
| /* |
| * This file implements the virtual machine of Ficl. Each virtual |
| * machine retains the state of an interpreter. A virtual machine |
| * owns a pair of stacks for parameters and return addresses, as |
| * well as a pile of state variables and the two dedicated registers |
| * of the interpreter. |
| */ |
| /* |
| * Copyright (c) 1997-2001 John Sadler (john_sadler@alum.mit.edu) |
| * All rights reserved. |
| * |
| * Get the latest Ficl release at http://ficl.sourceforge.net |
| * |
| * I am interested in hearing from anyone who uses Ficl. If you have |
| * a problem, a success story, a defect, an enhancement request, or |
| * if you would like to contribute to the Ficl release, please |
| * contact me by email at the address above. |
| * |
| * L I C E N S E and D I S C L A I M E R |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| */ |
| |
| #include "ficl.h" |
| |
| #if FICL_ROBUST >= 2 |
| #define FICL_VM_CHECK(vm) \ |
| FICL_VM_ASSERT(vm, (*(vm->ip - 1)) == vm->runningWord) |
| #else |
| #define FICL_VM_CHECK(vm) |
| #endif |
| |
| /* |
| * v m B r a n c h R e l a t i v e |
| */ |
| void |
| ficlVmBranchRelative(ficlVm *vm, int offset) |
| { |
| vm->ip += offset; |
| } |
| |
| /* |
| * v m C r e a t e |
| * Creates a virtual machine either from scratch (if vm is NULL on entry) |
| * or by resizing and reinitializing an existing VM to the specified stack |
| * sizes. |
| */ |
| ficlVm * |
| ficlVmCreate(ficlVm *vm, unsigned nPStack, unsigned nRStack) |
| { |
| if (vm == NULL) { |
| vm = (ficlVm *)ficlMalloc(sizeof (ficlVm)); |
| FICL_ASSERT(NULL, vm); |
| memset(vm, 0, sizeof (ficlVm)); |
| } |
| |
| if (vm->dataStack) |
| ficlStackDestroy(vm->dataStack); |
| vm->dataStack = ficlStackCreate(vm, "data", nPStack); |
| |
| if (vm->returnStack) |
| ficlStackDestroy(vm->returnStack); |
| vm->returnStack = ficlStackCreate(vm, "return", nRStack); |
| |
| #if FICL_WANT_FLOAT |
| if (vm->floatStack) |
| ficlStackDestroy(vm->floatStack); |
| vm->floatStack = ficlStackCreate(vm, "float", nPStack); |
| #endif |
| |
| ficlVmReset(vm); |
| return (vm); |
| } |
| |
| /* |
| * v m D e l e t e |
| * Free all memory allocated to the specified VM and its subordinate |
| * structures. |
| */ |
| void |
| ficlVmDestroy(ficlVm *vm) |
| { |
| if (vm) { |
| ficlFree(vm->dataStack); |
| ficlFree(vm->returnStack); |
| #if FICL_WANT_FLOAT |
| ficlFree(vm->floatStack); |
| #endif |
| ficlFree(vm); |
| } |
| } |
| |
| /* |
| * v m E x e c u t e |
| * Sets up the specified word to be run by the inner interpreter. |
| * Executes the word's code part immediately, but in the case of |
| * colon definition, the definition itself needs the inner interpreter |
| * to complete. This does not happen until control reaches ficlExec |
| */ |
| void |
| ficlVmExecuteWord(ficlVm *vm, ficlWord *pWord) |
| { |
| ficlVmInnerLoop(vm, pWord); |
| } |
| |
| static void |
| ficlVmOptimizeJumpToJump(ficlVm *vm, ficlIp ip) |
| { |
| ficlIp destination; |
| switch ((ficlInstruction)(*ip)) { |
| case ficlInstructionBranchParenWithCheck: |
| *ip = (ficlWord *)ficlInstructionBranchParen; |
| goto RUNTIME_FIXUP; |
| |
| case ficlInstructionBranch0ParenWithCheck: |
| *ip = (ficlWord *)ficlInstructionBranch0Paren; |
| RUNTIME_FIXUP: |
| ip++; |
| destination = ip + *(ficlInteger *)ip; |
| switch ((ficlInstruction)*destination) { |
| case ficlInstructionBranchParenWithCheck: |
| /* preoptimize where we're jumping to */ |
| ficlVmOptimizeJumpToJump(vm, destination); |
| /* FALLTHROUGH */ |
| case ficlInstructionBranchParen: |
| destination++; |
| destination += *(ficlInteger *)destination; |
| *ip = (ficlWord *)(destination - ip); |
| break; |
| } |
| } |
| } |
| |
| /* |
| * v m I n n e r L o o p |
| * the mysterious inner interpreter... |
| * This loop is the address interpreter that makes colon definitions |
| * work. Upon entry, it assumes that the IP points to an entry in |
| * a definition (the body of a colon word). It runs one word at a time |
| * until something does vmThrow. The catcher for this is expected to exist |
| * in the calling code. |
| * vmThrow gets you out of this loop with a longjmp() |
| */ |
| |
| #if FICL_ROBUST <= 1 |
| /* turn off stack checking for primitives */ |
| #define _CHECK_STACK(stack, top, pop, push) |
| #else |
| |
| #define _CHECK_STACK(stack, top, pop, push) \ |
| ficlStackCheckNospill(stack, top, pop, push) |
| |
| static FICL_PLATFORM_INLINE void |
| ficlStackCheckNospill(ficlStack *stack, ficlCell *top, int popCells, |
| int pushCells) |
| { |
| /* |
| * Why save and restore stack->top? |
| * So the simple act of stack checking doesn't force a "register" spill, |
| * which might mask bugs (places where we needed to spill but didn't). |
| * --lch |
| */ |
| ficlCell *oldTop = stack->top; |
| stack->top = top; |
| ficlStackCheck(stack, popCells, pushCells); |
| stack->top = oldTop; |
| } |
| |
| #endif /* FICL_ROBUST <= 1 */ |
| |
| #define CHECK_STACK(pop, push) \ |
| _CHECK_STACK(vm->dataStack, dataTop, pop, push) |
| #define CHECK_FLOAT_STACK(pop, push) \ |
| _CHECK_STACK(vm->floatStack, floatTop, pop, push) |
| #define CHECK_RETURN_STACK(pop, push) \ |
| _CHECK_STACK(vm->returnStack, returnTop, pop, push) |
| |
| #if FICL_WANT_FLOAT |
| #define FLOAT_LOCAL_VARIABLE_SPILL \ |
| vm->floatStack->top = floatTop; |
| #define FLOAT_LOCAL_VARIABLE_REFILL \ |
| floatTop = vm->floatStack->top; |
| #else |
| #define FLOAT_LOCAL_VARIABLE_SPILL |
| #define FLOAT_LOCAL_VARIABLE_REFILL |
| #endif /* FICL_WANT_FLOAT */ |
| |
| #if FICL_WANT_LOCALS |
| #define LOCALS_LOCAL_VARIABLE_SPILL \ |
| vm->returnStack->frame = frame; |
| #define LOCALS_LOCAL_VARIABLE_REFILL \ |
| frame = vm->returnStack->frame; |
| #else |
| #define LOCALS_LOCAL_VARIABLE_SPILL |
| #define LOCALS_LOCAL_VARIABLE_REFILL |
| #endif /* FICL_WANT_FLOAT */ |
| |
| #define LOCAL_VARIABLE_SPILL \ |
| vm->ip = (ficlIp)ip; \ |
| vm->dataStack->top = dataTop; \ |
| vm->returnStack->top = returnTop; \ |
| FLOAT_LOCAL_VARIABLE_SPILL \ |
| LOCALS_LOCAL_VARIABLE_SPILL |
| |
| #define LOCAL_VARIABLE_REFILL \ |
| ip = (ficlInstruction *)vm->ip; \ |
| dataTop = vm->dataStack->top; \ |
| returnTop = vm->returnStack->top; \ |
| FLOAT_LOCAL_VARIABLE_REFILL \ |
| LOCALS_LOCAL_VARIABLE_REFILL |
| |
| void |
| ficlVmInnerLoop(ficlVm *vm, ficlWord *fw) |
| { |
| register ficlInstruction *ip; |
| register ficlCell *dataTop; |
| register ficlCell *returnTop; |
| #if FICL_WANT_FLOAT |
| register ficlCell *floatTop; |
| ficlFloat f; |
| #endif /* FICL_WANT_FLOAT */ |
| #if FICL_WANT_LOCALS |
| register ficlCell *frame; |
| #endif /* FICL_WANT_LOCALS */ |
| jmp_buf *oldExceptionHandler; |
| jmp_buf exceptionHandler; |
| int except; |
| int once; |
| volatile int count; /* volatile because of longjmp */ |
| ficlInstruction instruction; |
| ficlInteger i; |
| ficlUnsigned u; |
| ficlCell c; |
| ficlCountedString *s; |
| ficlCell *cell; |
| char *cp; |
| |
| once = (fw != NULL); |
| if (once) |
| count = 1; |
| |
| oldExceptionHandler = vm->exceptionHandler; |
| /* This has to come before the setjmp! */ |
| vm->exceptionHandler = &exceptionHandler; |
| except = setjmp(exceptionHandler); |
| |
| LOCAL_VARIABLE_REFILL; |
| |
| if (except) { |
| LOCAL_VARIABLE_SPILL; |
| vm->exceptionHandler = oldExceptionHandler; |
| ficlVmThrow(vm, except); |
| } |
| |
| for (;;) { |
| if (once) { |
| if (!count--) |
| break; |
| instruction = (ficlInstruction)((void *)fw); |
| } else { |
| instruction = *ip++; |
| fw = (ficlWord *)instruction; |
| } |
| |
| AGAIN: |
| switch (instruction) { |
| case ficlInstructionInvalid: |
| ficlVmThrowError(vm, |
| "Error: NULL instruction executed!"); |
| return; |
| |
| case ficlInstruction1: |
| case ficlInstruction2: |
| case ficlInstruction3: |
| case ficlInstruction4: |
| case ficlInstruction5: |
| case ficlInstruction6: |
| case ficlInstruction7: |
| case ficlInstruction8: |
| case ficlInstruction9: |
| case ficlInstruction10: |
| case ficlInstruction11: |
| case ficlInstruction12: |
| case ficlInstruction13: |
| case ficlInstruction14: |
| case ficlInstruction15: |
| case ficlInstruction16: |
| CHECK_STACK(0, 1); |
| (++dataTop)->i = instruction; |
| continue; |
| |
| case ficlInstruction0: |
| case ficlInstructionNeg1: |
| case ficlInstructionNeg2: |
| case ficlInstructionNeg3: |
| case ficlInstructionNeg4: |
| case ficlInstructionNeg5: |
| case ficlInstructionNeg6: |
| case ficlInstructionNeg7: |
| case ficlInstructionNeg8: |
| case ficlInstructionNeg9: |
| case ficlInstructionNeg10: |
| case ficlInstructionNeg11: |
| case ficlInstructionNeg12: |
| case ficlInstructionNeg13: |
| case ficlInstructionNeg14: |
| case ficlInstructionNeg15: |
| case ficlInstructionNeg16: |
| CHECK_STACK(0, 1); |
| (++dataTop)->i = ficlInstruction0 - instruction; |
| continue; |
| |
| /* |
| * stringlit: Fetch the count from the dictionary, then push |
| * the address and count on the stack. Finally, update ip to |
| * point to the first aligned address after the string text. |
| */ |
| case ficlInstructionStringLiteralParen: { |
| ficlUnsigned8 length; |
| CHECK_STACK(0, 2); |
| |
| s = (ficlCountedString *)(ip); |
| length = s->length; |
| cp = s->text; |
| (++dataTop)->p = cp; |
| (++dataTop)->i = length; |
| |
| cp += length + 1; |
| cp = ficlAlignPointer(cp); |
| ip = (void *)cp; |
| continue; |
| } |
| |
| case ficlInstructionCStringLiteralParen: |
| CHECK_STACK(0, 1); |
| |
| s = (ficlCountedString *)(ip); |
| cp = s->text + s->length + 1; |
| cp = ficlAlignPointer(cp); |
| ip = (void *)cp; |
| (++dataTop)->p = s; |
| continue; |
| |
| #if FICL_WANT_OPTIMIZE == FICL_OPTIMIZE_FOR_SIZE |
| #if FICL_WANT_FLOAT |
| FLOAT_PUSH_CELL_POINTER_DOUBLE_MINIPROC: |
| *++floatTop = cell[1]; |
| /* intentional fall-through */ |
| FLOAT_PUSH_CELL_POINTER_MINIPROC: |
| *++floatTop = cell[0]; |
| continue; |
| |
| FLOAT_POP_CELL_POINTER_MINIPROC: |
| cell[0] = *floatTop--; |
| continue; |
| |
| FLOAT_POP_CELL_POINTER_DOUBLE_MINIPROC: |
| cell[0] = *floatTop--; |
| cell[1] = *floatTop--; |
| continue; |
| |
| #define FLOAT_PUSH_CELL_POINTER_DOUBLE(cp) \ |
| cell = (cp); goto FLOAT_PUSH_CELL_POINTER_DOUBLE_MINIPROC |
| #define FLOAT_PUSH_CELL_POINTER(cp) \ |
| cell = (cp); goto FLOAT_PUSH_CELL_POINTER_MINIPROC |
| #define FLOAT_POP_CELL_POINTER_DOUBLE(cp) \ |
| cell = (cp); goto FLOAT_POP_CELL_POINTER_DOUBLE_MINIPROC |
| #define FLOAT_POP_CELL_POINTER(cp) \ |
| cell = (cp); goto FLOAT_POP_CELL_POINTER_MINIPROC |
| #endif /* FICL_WANT_FLOAT */ |
| |
| /* |
| * Think of these as little mini-procedures. |
| * --lch |
| */ |
| PUSH_CELL_POINTER_DOUBLE_MINIPROC: |
| *++dataTop = cell[1]; |
| /* intentional fall-through */ |
| PUSH_CELL_POINTER_MINIPROC: |
| *++dataTop = cell[0]; |
| continue; |
| |
| POP_CELL_POINTER_MINIPROC: |
| cell[0] = *dataTop--; |
| continue; |
| POP_CELL_POINTER_DOUBLE_MINIPROC: |
| cell[0] = *dataTop--; |
| cell[1] = *dataTop--; |
| continue; |
| |
| #define PUSH_CELL_POINTER_DOUBLE(cp) \ |
| cell = (cp); goto PUSH_CELL_POINTER_DOUBLE_MINIPROC |
| #define PUSH_CELL_POINTER(cp) \ |
| cell = (cp); goto PUSH_CELL_POINTER_MINIPROC |
| #define POP_CELL_POINTER_DOUBLE(cp) \ |
| cell = (cp); goto POP_CELL_POINTER_DOUBLE_MINIPROC |
| #define POP_CELL_POINTER(cp) \ |
| cell = (cp); goto POP_CELL_POINTER_MINIPROC |
| |
| BRANCH_MINIPROC: |
| ip += *(ficlInteger *)ip; |
| continue; |
| |
| #define BRANCH() goto BRANCH_MINIPROC |
| |
| EXIT_FUNCTION_MINIPROC: |
| ip = (ficlInstruction *)((returnTop--)->p); |
| continue; |
| |
| #define EXIT_FUNCTION goto EXIT_FUNCTION_MINIPROC |
| |
| #else /* FICL_WANT_SIZE */ |
| |
| #if FICL_WANT_FLOAT |
| #define FLOAT_PUSH_CELL_POINTER_DOUBLE(cp) \ |
| cell = (cp); *++floatTop = cell[1]; *++floatTop = *cell; continue |
| #define FLOAT_PUSH_CELL_POINTER(cp) \ |
| cell = (cp); *++floatTop = *cell; continue |
| #define FLOAT_POP_CELL_POINTER_DOUBLE(cp) \ |
| cell = (cp); *cell = *floatTop--; cell[1] = *floatTop--; continue |
| #define FLOAT_POP_CELL_POINTER(cp) \ |
| cell = (cp); *cell = *floatTop--; continue |
| #endif /* FICL_WANT_FLOAT */ |
| |
| #define PUSH_CELL_POINTER_DOUBLE(cp) \ |
| cell = (cp); *++dataTop = cell[1]; *++dataTop = *cell; continue |
| #define PUSH_CELL_POINTER(cp) \ |
| cell = (cp); *++dataTop = *cell; continue |
| #define POP_CELL_POINTER_DOUBLE(cp) \ |
| cell = (cp); *cell = *dataTop--; cell[1] = *dataTop--; continue |
| #define POP_CELL_POINTER(cp) \ |
| cell = (cp); *cell = *dataTop--; continue |
| |
| #define BRANCH() ip += *(ficlInteger *)ip; continue |
| #define EXIT_FUNCTION() ip = (ficlInstruction *)((returnTop--)->p); continue |
| |
| #endif /* FICL_WANT_SIZE */ |
| |
| |
| /* |
| * This is the runtime for (literal). It assumes that it is |
| * part of a colon definition, and that the next ficlCell |
| * contains a value to be pushed on the parameter stack at |
| * runtime. This code is compiled by "literal". |
| */ |
| |
| case ficlInstructionLiteralParen: |
| CHECK_STACK(0, 1); |
| (++dataTop)->i = *ip++; |
| continue; |
| |
| case ficlInstruction2LiteralParen: |
| CHECK_STACK(0, 2); |
| (++dataTop)->i = ip[1]; |
| (++dataTop)->i = ip[0]; |
| ip += 2; |
| continue; |
| |
| #if FICL_WANT_LOCALS |
| /* |
| * Link a frame on the return stack, reserving nCells of space |
| * for locals - the value of nCells is the next ficlCell in |
| * the instruction stream. |
| * 1) Push frame onto returnTop |
| * 2) frame = returnTop |
| * 3) returnTop += nCells |
| */ |
| case ficlInstructionLinkParen: { |
| ficlInteger nCells = *ip++; |
| (++returnTop)->p = frame; |
| frame = returnTop + 1; |
| returnTop += nCells; |
| continue; |
| } |
| |
| /* |
| * Unink a stack frame previously created by stackLink |
| * 1) dataTop = frame |
| * 2) frame = pop() |
| */ |
| case ficlInstructionUnlinkParen: |
| returnTop = frame - 1; |
| frame = (returnTop--)->p; |
| continue; |
| |
| /* |
| * Immediate - cfa of a local while compiling - when executed, |
| * compiles code to fetch the value of a local given the |
| * local's index in the word's pfa |
| */ |
| #if FICL_WANT_FLOAT |
| case ficlInstructionGetF2LocalParen: |
| FLOAT_PUSH_CELL_POINTER_DOUBLE(frame + *ip++); |
| |
| case ficlInstructionGetFLocalParen: |
| FLOAT_PUSH_CELL_POINTER(frame + *ip++); |
| |
| case ficlInstructionToF2LocalParen: |
| FLOAT_POP_CELL_POINTER_DOUBLE(frame + *ip++); |
| |
| case ficlInstructionToFLocalParen: |
| FLOAT_POP_CELL_POINTER(frame + *ip++); |
| #endif /* FICL_WANT_FLOAT */ |
| |
| case ficlInstructionGet2LocalParen: |
| PUSH_CELL_POINTER_DOUBLE(frame + *ip++); |
| |
| case ficlInstructionGetLocalParen: |
| PUSH_CELL_POINTER(frame + *ip++); |
| |
| /* |
| * Immediate - cfa of a local while compiling - when executed, |
| * compiles code to store the value of a local given the |
| * local's index in the word's pfa |
| */ |
| |
| case ficlInstructionTo2LocalParen: |
| POP_CELL_POINTER_DOUBLE(frame + *ip++); |
| |
| case ficlInstructionToLocalParen: |
| POP_CELL_POINTER(frame + *ip++); |
| |
| /* |
| * Silly little minor optimizations. |
| * --lch |
| */ |
| case ficlInstructionGetLocal0: |
| PUSH_CELL_POINTER(frame); |
| |
| case ficlInstructionGetLocal1: |
| PUSH_CELL_POINTER(frame + 1); |
| |
| case ficlInstructionGet2Local0: |
| PUSH_CELL_POINTER_DOUBLE(frame); |
| |
| case ficlInstructionToLocal0: |
| POP_CELL_POINTER(frame); |
| |
| case ficlInstructionToLocal1: |
| POP_CELL_POINTER(frame + 1); |
| |
| case ficlInstructionTo2Local0: |
| POP_CELL_POINTER_DOUBLE(frame); |
| |
| #endif /* FICL_WANT_LOCALS */ |
| |
| case ficlInstructionPlus: |
| CHECK_STACK(2, 1); |
| i = (dataTop--)->i; |
| dataTop->i += i; |
| continue; |
| |
| case ficlInstructionMinus: |
| CHECK_STACK(2, 1); |
| i = (dataTop--)->i; |
| dataTop->i -= i; |
| continue; |
| |
| case ficlInstruction1Plus: |
| CHECK_STACK(1, 1); |
| dataTop->i++; |
| continue; |
| |
| case ficlInstruction1Minus: |
| CHECK_STACK(1, 1); |
| dataTop->i--; |
| continue; |
| |
| case ficlInstruction2Plus: |
| CHECK_STACK(1, 1); |
| dataTop->i += 2; |
| continue; |
| |
| case ficlInstruction2Minus: |
| CHECK_STACK(1, 1); |
| dataTop->i -= 2; |
| continue; |
| |
| case ficlInstructionDup: { |
| ficlInteger i = dataTop->i; |
| CHECK_STACK(0, 1); |
| (++dataTop)->i = i; |
| continue; |
| } |
| |
| case ficlInstructionQuestionDup: |
| CHECK_STACK(1, 2); |
| |
| if (dataTop->i != 0) { |
| dataTop[1] = dataTop[0]; |
| dataTop++; |
| } |
| |
| continue; |
| |
| case ficlInstructionSwap: { |
| ficlCell swap; |
| CHECK_STACK(2, 2); |
| swap = dataTop[0]; |
| dataTop[0] = dataTop[-1]; |
| dataTop[-1] = swap; |
| } |
| continue; |
| |
| case ficlInstructionDrop: |
| CHECK_STACK(1, 0); |
| dataTop--; |
| continue; |
| |
| case ficlInstruction2Drop: |
| CHECK_STACK(2, 0); |
| dataTop -= 2; |
| continue; |
| |
| case ficlInstruction2Dup: |
| CHECK_STACK(2, 4); |
| dataTop[1] = dataTop[-1]; |
| dataTop[2] = *dataTop; |
| dataTop += 2; |
| continue; |
| |
| case ficlInstructionOver: |
| CHECK_STACK(2, 3); |
| dataTop[1] = dataTop[-1]; |
| dataTop++; |
| continue; |
| |
| case ficlInstruction2Over: |
| CHECK_STACK(4, 6); |
| dataTop[1] = dataTop[-3]; |
| dataTop[2] = dataTop[-2]; |
| dataTop += 2; |
| continue; |
| |
| case ficlInstructionPick: |
| CHECK_STACK(1, 0); |
| i = dataTop->i; |
| if (i < 0) |
| continue; |
| CHECK_STACK(i + 2, i + 3); |
| *dataTop = dataTop[-i - 1]; |
| continue; |
| |
| /* |
| * Do stack rot. |
| * rot ( 1 2 3 -- 2 3 1 ) |
| */ |
| case ficlInstructionRot: |
| i = 2; |
| goto ROLL; |
| |
| /* |
| * Do stack roll. |
| * roll ( n -- ) |
| */ |
| case ficlInstructionRoll: |
| CHECK_STACK(1, 0); |
| i = (dataTop--)->i; |
| |
| if (i < 1) |
| continue; |
| |
| ROLL: |
| CHECK_STACK(i+1, i+2); |
| c = dataTop[-i]; |
| memmove(dataTop - i, dataTop - (i - 1), |
| i * sizeof (ficlCell)); |
| *dataTop = c; |
| continue; |
| |
| /* |
| * Do stack -rot. |
| * -rot ( 1 2 3 -- 3 1 2 ) |
| */ |
| case ficlInstructionMinusRot: |
| i = 2; |
| goto MINUSROLL; |
| |
| /* |
| * Do stack -roll. |
| * -roll ( n -- ) |
| */ |
| case ficlInstructionMinusRoll: |
| CHECK_STACK(1, 0); |
| i = (dataTop--)->i; |
| |
| if (i < 1) |
| continue; |
| |
| MINUSROLL: |
| CHECK_STACK(i+1, i+2); |
| c = *dataTop; |
| memmove(dataTop - (i - 1), dataTop - i, |
| i * sizeof (ficlCell)); |
| dataTop[-i] = c; |
| |
| continue; |
| |
| /* |
| * Do stack 2swap |
| * 2swap ( 1 2 3 4 -- 3 4 1 2 ) |
| */ |
| case ficlInstruction2Swap: { |
| ficlCell c2; |
| CHECK_STACK(4, 4); |
| |
| c = *dataTop; |
| c2 = dataTop[-1]; |
| |
| *dataTop = dataTop[-2]; |
| dataTop[-1] = dataTop[-3]; |
| |
| dataTop[-2] = c; |
| dataTop[-3] = c2; |
| continue; |
| } |
| |
| case ficlInstructionPlusStore: { |
| ficlCell *cell; |
| CHECK_STACK(2, 0); |
| cell = (ficlCell *)(dataTop--)->p; |
| cell->i += (dataTop--)->i; |
| continue; |
| } |
| |
| case ficlInstructionQuadFetch: { |
| ficlUnsigned32 *integer32; |
| CHECK_STACK(1, 1); |
| integer32 = (ficlUnsigned32 *)dataTop->i; |
| dataTop->u = (ficlUnsigned)*integer32; |
| continue; |
| } |
| |
| case ficlInstructionQuadStore: { |
| ficlUnsigned32 *integer32; |
| CHECK_STACK(2, 0); |
| integer32 = (ficlUnsigned32 *)(dataTop--)->p; |
| *integer32 = (ficlUnsigned32)((dataTop--)->u); |
| continue; |
| } |
| |
| case ficlInstructionWFetch: { |
| ficlUnsigned16 *integer16; |
| CHECK_STACK(1, 1); |
| integer16 = (ficlUnsigned16 *)dataTop->p; |
| dataTop->u = ((ficlUnsigned)*integer16); |
| continue; |
| } |
| |
| case ficlInstructionWStore: { |
| ficlUnsigned16 *integer16; |
| CHECK_STACK(2, 0); |
| integer16 = (ficlUnsigned16 *)(dataTop--)->p; |
| *integer16 = (ficlUnsigned16)((dataTop--)->u); |
| continue; |
| } |
| |
| case ficlInstructionCFetch: { |
| ficlUnsigned8 *integer8; |
| CHECK_STACK(1, 1); |
| integer8 = (ficlUnsigned8 *)dataTop->p; |
| dataTop->u = ((ficlUnsigned)*integer8); |
| continue; |
| } |
| |
| case ficlInstructionCStore: { |
| ficlUnsigned8 *integer8; |
| CHECK_STACK(2, 0); |
| integer8 = (ficlUnsigned8 *)(dataTop--)->p; |
| *integer8 = (ficlUnsigned8)((dataTop--)->u); |
| continue; |
| } |
| |
| |
| /* |
| * l o g i c a n d c o m p a r i s o n s |
| */ |
| |
| case ficlInstruction0Equals: |
| CHECK_STACK(1, 1); |
| dataTop->i = FICL_BOOL(dataTop->i == 0); |
| continue; |
| |
| case ficlInstruction0Less: |
| CHECK_STACK(1, 1); |
| dataTop->i = FICL_BOOL(dataTop->i < 0); |
| continue; |
| |
| case ficlInstruction0Greater: |
| CHECK_STACK(1, 1); |
| dataTop->i = FICL_BOOL(dataTop->i > 0); |
| continue; |
| |
| case ficlInstructionEquals: |
| CHECK_STACK(2, 1); |
| i = (dataTop--)->i; |
| dataTop->i = FICL_BOOL(dataTop->i == i); |
| continue; |
| |
| case ficlInstructionLess: |
| CHECK_STACK(2, 1); |
| i = (dataTop--)->i; |
| dataTop->i = FICL_BOOL(dataTop->i < i); |
| continue; |
| |
| case ficlInstructionULess: |
| CHECK_STACK(2, 1); |
| u = (dataTop--)->u; |
| dataTop->i = FICL_BOOL(dataTop->u < u); |
| continue; |
| |
| case ficlInstructionAnd: |
| CHECK_STACK(2, 1); |
| i = (dataTop--)->i; |
| dataTop->i = dataTop->i & i; |
| continue; |
| |
| case ficlInstructionOr: |
| CHECK_STACK(2, 1); |
| i = (dataTop--)->i; |
| dataTop->i = dataTop->i | i; |
| continue; |
| |
| case ficlInstructionXor: |
| CHECK_STACK(2, 1); |
| i = (dataTop--)->i; |
| dataTop->i = dataTop->i ^ i; |
| continue; |
| |
| case ficlInstructionInvert: |
| CHECK_STACK(1, 1); |
| dataTop->i = ~dataTop->i; |
| continue; |
| |
| /* |
| * r e t u r n s t a c k |
| */ |
| case ficlInstructionToRStack: |
| CHECK_STACK(1, 0); |
| CHECK_RETURN_STACK(0, 1); |
| *++returnTop = *dataTop--; |
| continue; |
| |
| case ficlInstructionFromRStack: |
| CHECK_STACK(0, 1); |
| CHECK_RETURN_STACK(1, 0); |
| *++dataTop = *returnTop--; |
| continue; |
| |
| case ficlInstructionFetchRStack: |
| CHECK_STACK(0, 1); |
| CHECK_RETURN_STACK(1, 1); |
| *++dataTop = *returnTop; |
| continue; |
| |
| case ficlInstruction2ToR: |
| CHECK_STACK(2, 0); |
| CHECK_RETURN_STACK(0, 2); |
| *++returnTop = dataTop[-1]; |
| *++returnTop = dataTop[0]; |
| dataTop -= 2; |
| continue; |
| |
| case ficlInstruction2RFrom: |
| CHECK_STACK(0, 2); |
| CHECK_RETURN_STACK(2, 0); |
| *++dataTop = returnTop[-1]; |
| *++dataTop = returnTop[0]; |
| returnTop -= 2; |
| continue; |
| |
| case ficlInstruction2RFetch: |
| CHECK_STACK(0, 2); |
| CHECK_RETURN_STACK(2, 2); |
| *++dataTop = returnTop[-1]; |
| *++dataTop = returnTop[0]; |
| continue; |
| |
| /* |
| * f i l l |
| * CORE ( c-addr u char -- ) |
| * If u is greater than zero, store char in each of u |
| * consecutive characters of memory beginning at c-addr. |
| */ |
| case ficlInstructionFill: { |
| char c; |
| char *memory; |
| CHECK_STACK(3, 0); |
| c = (char)(dataTop--)->i; |
| u = (dataTop--)->u; |
| memory = (char *)(dataTop--)->p; |
| |
| /* |
| * memset() is faster than the previous hand-rolled |
| * solution. --lch |
| */ |
| memset(memory, c, u); |
| continue; |
| } |
| |
| /* |
| * l s h i f t |
| * l-shift CORE ( x1 u -- x2 ) |
| * Perform a logical left shift of u bit-places on x1, |
| * giving x2. Put zeroes into the least significant bits |
| * vacated by the shift. An ambiguous condition exists if |
| * u is greater than or equal to the number of bits in a |
| * ficlCell. |
| * |
| * r-shift CORE ( x1 u -- x2 ) |
| * Perform a logical right shift of u bit-places on x1, |
| * giving x2. Put zeroes into the most significant bits |
| * vacated by the shift. An ambiguous condition exists |
| * if u is greater than or equal to the number of bits |
| * in a ficlCell. |
| */ |
| case ficlInstructionLShift: { |
| ficlUnsigned nBits; |
| ficlUnsigned x1; |
| CHECK_STACK(2, 1); |
| |
| nBits = (dataTop--)->u; |
| x1 = dataTop->u; |
| dataTop->u = x1 << nBits; |
| continue; |
| } |
| |
| case ficlInstructionRShift: { |
| ficlUnsigned nBits; |
| ficlUnsigned x1; |
| CHECK_STACK(2, 1); |
| |
| nBits = (dataTop--)->u; |
| x1 = dataTop->u; |
| dataTop->u = x1 >> nBits; |
| continue; |
| } |
| |
| /* |
| * m a x & m i n |
| */ |
| case ficlInstructionMax: { |
| ficlInteger n2; |
| ficlInteger n1; |
| CHECK_STACK(2, 1); |
| |
| n2 = (dataTop--)->i; |
| n1 = dataTop->i; |
| |
| dataTop->i = ((n1 > n2) ? n1 : n2); |
| continue; |
| } |
| |
| case ficlInstructionMin: { |
| ficlInteger n2; |
| ficlInteger n1; |
| CHECK_STACK(2, 1); |
| |
| n2 = (dataTop--)->i; |
| n1 = dataTop->i; |
| |
| dataTop->i = ((n1 < n2) ? n1 : n2); |
| continue; |
| } |
| |
| /* |
| * m o v e |
| * CORE ( addr1 addr2 u -- ) |
| * If u is greater than zero, copy the contents of u |
| * consecutive address units at addr1 to the u consecutive |
| * address units at addr2. After MOVE completes, the u |
| * consecutive address units at addr2 contain exactly |
| * what the u consecutive address units at addr1 contained |
| * before the move. |
| * NOTE! This implementation assumes that a char is the same |
| * size as an address unit. |
| */ |
| case ficlInstructionMove: { |
| ficlUnsigned u; |
| char *addr2; |
| char *addr1; |
| CHECK_STACK(3, 0); |
| |
| u = (dataTop--)->u; |
| addr2 = (dataTop--)->p; |
| addr1 = (dataTop--)->p; |
| |
| if (u == 0) |
| continue; |
| /* |
| * Do the copy carefully, so as to be |
| * correct even if the two ranges overlap |
| */ |
| /* Which ANSI C's memmove() does for you! Yay! --lch */ |
| memmove(addr2, addr1, u); |
| continue; |
| } |
| |
| /* |
| * s t o d |
| * s-to-d CORE ( n -- d ) |
| * Convert the number n to the double-ficlCell number d with |
| * the same numerical value. |
| */ |
| case ficlInstructionSToD: { |
| ficlInteger s; |
| CHECK_STACK(1, 2); |
| |
| s = dataTop->i; |
| |
| /* sign extend to 64 bits.. */ |
| (++dataTop)->i = (s < 0) ? -1 : 0; |
| continue; |
| } |
| |
| /* |
| * c o m p a r e |
| * STRING ( c-addr1 u1 c-addr2 u2 -- n ) |
| * Compare the string specified by c-addr1 u1 to the string |
| * specified by c-addr2 u2. The strings are compared, beginning |
| * at the given addresses, character by character, up to the |
| * length of the shorter string or until a difference is found. |
| * If the two strings are identical, n is zero. If the two |
| * strings are identical up to the length of the shorter string, |
| * n is minus-one (-1) if u1 is less than u2 and one (1) |
| * otherwise. If the two strings are not identical up to the |
| * length of the shorter string, n is minus-one (-1) if the |
| * first non-matching character in the string specified by |
| * c-addr1 u1 has a lesser numeric value than the corresponding |
| * character in the string specified by c-addr2 u2 and |
| * one (1) otherwise. |
| */ |
| case ficlInstructionCompare: |
| i = FICL_FALSE; |
| goto COMPARE; |
| |
| |
| case ficlInstructionCompareInsensitive: |
| i = FICL_TRUE; |
| goto COMPARE; |
| |
| COMPARE: |
| { |
| char *cp1, *cp2; |
| ficlUnsigned u1, u2, uMin; |
| int n = 0; |
| |
| CHECK_STACK(4, 1); |
| u2 = (dataTop--)->u; |
| cp2 = (char *)(dataTop--)->p; |
| u1 = (dataTop--)->u; |
| cp1 = (char *)(dataTop--)->p; |
| |
| uMin = (u1 < u2)? u1 : u2; |
| for (; (uMin > 0) && (n == 0); uMin--) { |
| int c1 = (unsigned char)*cp1++; |
| int c2 = (unsigned char)*cp2++; |
| |
| if (i) { |
| c1 = tolower(c1); |
| c2 = tolower(c2); |
| } |
| n = (c1 - c2); |
| } |
| |
| if (n == 0) |
| n = (int)(u1 - u2); |
| |
| if (n < 0) |
| n = -1; |
| else if (n > 0) |
| n = 1; |
| |
| (++dataTop)->i = n; |
| continue; |
| } |
| |
| /* |
| * r a n d o m |
| * Ficl-specific |
| */ |
| case ficlInstructionRandom: |
| (++dataTop)->i = random(); |
| continue; |
| |
| /* |
| * s e e d - r a n d o m |
| * Ficl-specific |
| */ |
| case ficlInstructionSeedRandom: |
| srandom((dataTop--)->i); |
| continue; |
| |
| case ficlInstructionGreaterThan: { |
| ficlInteger x, y; |
| CHECK_STACK(2, 1); |
| y = (dataTop--)->i; |
| x = dataTop->i; |
| dataTop->i = FICL_BOOL(x > y); |
| continue; |
| |
| case ficlInstructionUGreaterThan: |
| CHECK_STACK(2, 1); |
| u = (dataTop--)->u; |
| dataTop->i = FICL_BOOL(dataTop->u > u); |
| continue; |
| |
| } |
| |
| /* |
| * This function simply pops the previous instruction |
| * pointer and returns to the "next" loop. Used for exiting |
| * from within a definition. Note that exitParen is identical |
| * to semiParen - they are in two different functions so that |
| * "see" can correctly identify the end of a colon definition, |
| * even if it uses "exit". |
| */ |
| case ficlInstructionExitParen: |
| case ficlInstructionSemiParen: |
| EXIT_FUNCTION(); |
| |
| /* |
| * The first time we run "(branch)", perform a "peephole |
| * optimization" to see if we're jumping to another |
| * unconditional jump. If so, just jump directly there. |
| */ |
| case ficlInstructionBranchParenWithCheck: |
| LOCAL_VARIABLE_SPILL; |
| ficlVmOptimizeJumpToJump(vm, vm->ip - 1); |
| LOCAL_VARIABLE_REFILL; |
| goto BRANCH_PAREN; |
| |
| /* |
| * Same deal with branch0. |
| */ |
| case ficlInstructionBranch0ParenWithCheck: |
| LOCAL_VARIABLE_SPILL; |
| ficlVmOptimizeJumpToJump(vm, vm->ip - 1); |
| LOCAL_VARIABLE_REFILL; |
| /* intentional fall-through */ |
| |
| /* |
| * Runtime code for "(branch0)"; pop a flag from the stack, |
| * branch if 0. fall through otherwise. |
| * The heart of "if" and "until". |
| */ |
| case ficlInstructionBranch0Paren: |
| CHECK_STACK(1, 0); |
| |
| if ((dataTop--)->i) { |
| /* |
| * don't branch, but skip over branch |
| * relative address |
| */ |
| ip += 1; |
| continue; |
| } |
| /* otherwise, take branch (to else/endif/begin) */ |
| /* intentional fall-through! */ |
| |
| /* |
| * Runtime for "(branch)" -- expects a literal offset in the |
| * next compilation address, and branches to that location. |
| */ |
| case ficlInstructionBranchParen: |
| BRANCH_PAREN: |
| BRANCH(); |
| |
| case ficlInstructionOfParen: { |
| ficlUnsigned a, b; |
| |
| CHECK_STACK(2, 1); |
| |
| a = (dataTop--)->u; |
| b = dataTop->u; |
| |
| if (a == b) { |
| /* fall through */ |
| ip++; |
| /* remove CASE argument */ |
| dataTop--; |
| } else { |
| /* take branch to next of or endcase */ |
| BRANCH(); |
| } |
| |
| continue; |
| } |
| |
| case ficlInstructionDoParen: { |
| ficlCell index, limit; |
| |
| CHECK_STACK(2, 0); |
| |
| index = *dataTop--; |
| limit = *dataTop--; |
| |
| /* copy "leave" target addr to stack */ |
| (++returnTop)->i = *(ip++); |
| *++returnTop = limit; |
| *++returnTop = index; |
| |
| continue; |
| } |
| |
| case ficlInstructionQDoParen: { |
| ficlCell index, limit, leave; |
| |
| CHECK_STACK(2, 0); |
| |
| index = *dataTop--; |
| limit = *dataTop--; |
| |
| leave.i = *ip; |
| |
| if (limit.u == index.u) { |
| ip = leave.p; |
| } else { |
| ip++; |
| *++returnTop = leave; |
| *++returnTop = limit; |
| *++returnTop = index; |
| } |
| |
| continue; |
| } |
| |
| case ficlInstructionLoopParen: |
| case ficlInstructionPlusLoopParen: { |
| ficlInteger index; |
| ficlInteger limit; |
| int direction = 0; |
| |
| index = returnTop->i; |
| limit = returnTop[-1].i; |
| |
| if (instruction == ficlInstructionLoopParen) |
| index++; |
| else { |
| ficlInteger increment; |
| CHECK_STACK(1, 0); |
| increment = (dataTop--)->i; |
| index += increment; |
| direction = (increment < 0); |
| } |
| |
| if (direction ^ (index >= limit)) { |
| /* nuke the loop indices & "leave" addr */ |
| returnTop -= 3; |
| ip++; /* fall through the loop */ |
| } else { /* update index, branch to loop head */ |
| returnTop->i = index; |
| BRANCH(); |
| } |
| |
| continue; |
| } |
| |
| |
| /* |
| * Runtime code to break out of a do..loop construct |
| * Drop the loop control variables; the branch address |
| * past "loop" is next on the return stack. |
| */ |
| case ficlInstructionLeave: |
| /* almost unloop */ |
| returnTop -= 2; |
| /* exit */ |
| EXIT_FUNCTION(); |
| |
| case ficlInstructionUnloop: |
| returnTop -= 3; |
| continue; |
| |
| case ficlInstructionI: |
| *++dataTop = *returnTop; |
| continue; |
| |
| case ficlInstructionJ: |
| *++dataTop = returnTop[-3]; |
| continue; |
| |
| case ficlInstructionK: |
| *++dataTop = returnTop[-6]; |
| continue; |
| |
| case ficlInstructionDoesParen: { |
| ficlDictionary *dictionary = ficlVmGetDictionary(vm); |
| dictionary->smudge->code = |
| (ficlPrimitive)ficlInstructionDoDoes; |
| dictionary->smudge->param[0].p = ip; |
| ip = (ficlInstruction *)((returnTop--)->p); |
| continue; |
| } |
| |
| case ficlInstructionDoDoes: { |
| ficlCell *cell; |
| ficlIp tempIP; |
| |
| CHECK_STACK(0, 1); |
| |
| cell = fw->param; |
| tempIP = (ficlIp)((*cell).p); |
| (++dataTop)->p = (cell + 1); |
| (++returnTop)->p = (void *)ip; |
| ip = (ficlInstruction *)tempIP; |
| continue; |
| } |
| |
| #if FICL_WANT_FLOAT |
| case ficlInstructionF2Fetch: |
| CHECK_FLOAT_STACK(0, 2); |
| CHECK_STACK(1, 0); |
| FLOAT_PUSH_CELL_POINTER_DOUBLE((dataTop--)->p); |
| |
| case ficlInstructionFFetch: |
| CHECK_FLOAT_STACK(0, 1); |
| CHECK_STACK(1, 0); |
| FLOAT_PUSH_CELL_POINTER((dataTop--)->p); |
| |
| case ficlInstructionF2Store: |
| CHECK_FLOAT_STACK(2, 0); |
| CHECK_STACK(1, 0); |
| FLOAT_POP_CELL_POINTER_DOUBLE((dataTop--)->p); |
| |
| case ficlInstructionFStore: |
| CHECK_FLOAT_STACK(1, 0); |
| CHECK_STACK(1, 0); |
| FLOAT_POP_CELL_POINTER((dataTop--)->p); |
| #endif /* FICL_WANT_FLOAT */ |
| |
| /* |
| * two-fetch CORE ( a-addr -- x1 x2 ) |
| * |
| * Fetch the ficlCell pair x1 x2 stored at a-addr. |
| * x2 is stored at a-addr and x1 at the next consecutive |
| * ficlCell. It is equivalent to the sequence |
| * DUP ficlCell+ @ SWAP @ . |
| */ |
| case ficlInstruction2Fetch: |
| CHECK_STACK(1, 2); |
| PUSH_CELL_POINTER_DOUBLE((dataTop--)->p); |
| |
| /* |
| * fetch CORE ( a-addr -- x ) |
| * |
| * x is the value stored at a-addr. |
| */ |
| case ficlInstructionFetch: |
| CHECK_STACK(1, 1); |
| PUSH_CELL_POINTER((dataTop--)->p); |
| |
| /* |
| * two-store CORE ( x1 x2 a-addr -- ) |
| * Store the ficlCell pair x1 x2 at a-addr, with x2 at a-addr |
| * and x1 at the next consecutive ficlCell. It is equivalent |
| * to the sequence SWAP OVER ! ficlCell+ ! |
| */ |
| case ficlInstruction2Store: |
| CHECK_STACK(3, 0); |
| POP_CELL_POINTER_DOUBLE((dataTop--)->p); |
| |
| /* |
| * store CORE ( x a-addr -- ) |
| * Store x at a-addr. |
| */ |
| case ficlInstructionStore: |
| CHECK_STACK(2, 0); |
| POP_CELL_POINTER((dataTop--)->p); |
| |
| case ficlInstructionComma: { |
| ficlDictionary *dictionary; |
| CHECK_STACK(1, 0); |
| |
| dictionary = ficlVmGetDictionary(vm); |
| ficlDictionaryAppendCell(dictionary, *dataTop--); |
| continue; |
| } |
| |
| case ficlInstructionCComma: { |
| ficlDictionary *dictionary; |
| char c; |
| CHECK_STACK(1, 0); |
| |
| dictionary = ficlVmGetDictionary(vm); |
| c = (char)(dataTop--)->i; |
| ficlDictionaryAppendCharacter(dictionary, c); |
| continue; |
| } |
| |
| case ficlInstructionCells: |
| CHECK_STACK(1, 1); |
| dataTop->i *= sizeof (ficlCell); |
| continue; |
| |
| case ficlInstructionCellPlus: |
| CHECK_STACK(1, 1); |
| dataTop->i += sizeof (ficlCell); |
| continue; |
| |
| case ficlInstructionStar: |
| CHECK_STACK(2, 1); |
| i = (dataTop--)->i; |
| dataTop->i *= i; |
| continue; |
| |
| case ficlInstructionNegate: |
| CHECK_STACK(1, 1); |
| dataTop->i = - dataTop->i; |
| continue; |
| |
| case ficlInstructionSlash: |
| CHECK_STACK(2, 1); |
| i = (dataTop--)->i; |
| dataTop->i /= i; |
| continue; |
| |
| /* |
| * slash-mod CORE ( n1 n2 -- n3 n4 ) |
| * Divide n1 by n2, giving the single-ficlCell remainder n3 |
| * and the single-ficlCell quotient n4. An ambiguous condition |
| * exists if n2 is zero. If n1 and n2 differ in sign, the |
| * implementation-defined result returned will be the |
| * same as that returned by either the phrase |
| * >R S>D R> FM/MOD or the phrase >R S>D R> SM/REM. |
| * NOTE: Ficl complies with the second phrase |
| * (symmetric division) |
| */ |
| case ficlInstructionSlashMod: { |
| ficl2Integer n1; |
| ficlInteger n2; |
| ficl2IntegerQR qr; |
| |
| CHECK_STACK(2, 2); |
| n2 = dataTop[0].i; |
| FICL_INTEGER_TO_2INTEGER(dataTop[-1].i, n1); |
| |
| qr = ficl2IntegerDivideSymmetric(n1, n2); |
| dataTop[-1].i = qr.remainder; |
| dataTop[0].i = FICL_2UNSIGNED_GET_LOW(qr.quotient); |
| continue; |
| } |
| |
| case ficlInstruction2Star: |
| CHECK_STACK(1, 1); |
| dataTop->i <<= 1; |
| continue; |
| |
| case ficlInstruction2Slash: |
| CHECK_STACK(1, 1); |
| dataTop->i >>= 1; |
| continue; |
| |
| case ficlInstructionStarSlash: { |
| ficlInteger x, y, z; |
| ficl2Integer prod; |
| CHECK_STACK(3, 1); |
| |
| z = (dataTop--)->i; |
| y = (dataTop--)->i; |
| x = dataTop->i; |
| |
| prod = ficl2IntegerMultiply(x, y); |
| dataTop->i = FICL_2UNSIGNED_GET_LOW( |
| ficl2IntegerDivideSymmetric(prod, z).quotient); |
| continue; |
| } |
| |
| case ficlInstructionStarSlashMod: { |
| ficlInteger x, y, z; |
| ficl2Integer prod; |
| ficl2IntegerQR qr; |
| |
| CHECK_STACK(3, 2); |
| |
| z = (dataTop--)->i; |
| y = dataTop[0].i; |
| x = dataTop[-1].i; |
| |
| prod = ficl2IntegerMultiply(x, y); |
| qr = ficl2IntegerDivideSymmetric(prod, z); |
| |
| dataTop[-1].i = qr.remainder; |
| dataTop[0].i = FICL_2UNSIGNED_GET_LOW(qr.quotient); |
| continue; |
| } |
| |
| #if FICL_WANT_FLOAT |
| case ficlInstructionF0: |
| CHECK_FLOAT_STACK(0, 1); |
| (++floatTop)->f = 0.0f; |
| continue; |
| |
| case ficlInstructionF1: |
| CHECK_FLOAT_STACK(0, 1); |
| (++floatTop)->f = 1.0f; |
| continue; |
| |
| case ficlInstructionFNeg1: |
| CHECK_FLOAT_STACK(0, 1); |
| (++floatTop)->f = -1.0f; |
| continue; |
| |
| /* |
| * Floating point literal execution word. |
| */ |
| case ficlInstructionFLiteralParen: |
| CHECK_FLOAT_STACK(0, 1); |
| |
| /* |
| * Yes, I'm using ->i here, |
| * but it's really a float. --lch |
| */ |
| (++floatTop)->i = *ip++; |
| continue; |
| |
| /* |
| * Do float addition r1 + r2. |
| * f+ ( r1 r2 -- r ) |
| */ |
| case ficlInstructionFPlus: |
| CHECK_FLOAT_STACK(2, 1); |
| |
| f = (floatTop--)->f; |
| floatTop->f += f; |
| continue; |
| |
| /* |
| * Do float subtraction r1 - r2. |
| * f- ( r1 r2 -- r ) |
| */ |
| case ficlInstructionFMinus: |
| CHECK_FLOAT_STACK(2, 1); |
| |
| f = (floatTop--)->f; |
| floatTop->f -= f; |
| continue; |
| |
| /* |
| * Do float multiplication r1 * r2. |
| * f* ( r1 r2 -- r ) |
| */ |
| case ficlInstructionFStar: |
| CHECK_FLOAT_STACK(2, 1); |
| |
| f = (floatTop--)->f; |
| floatTop->f *= f; |
| continue; |
| |
| /* |
| * Do float negation. |
| * fnegate ( r -- r ) |
| */ |
| case ficlInstructionFNegate: |
| CHECK_FLOAT_STACK(1, 1); |
| |
| floatTop->f = -(floatTop->f); |
| continue; |
| |
| /* |
| * Do float division r1 / r2. |
| * f/ ( r1 r2 -- r ) |
| */ |
| case ficlInstructionFSlash: |
| CHECK_FLOAT_STACK(2, 1); |
| |
| f = (floatTop--)->f; |
| floatTop->f /= f; |
| continue; |
| |
| /* |
| * Do float + integer r + n. |
| * f+i ( r n -- r ) |
| */ |
| case ficlInstructionFPlusI: |
| CHECK_FLOAT_STACK(1, 1); |
| CHECK_STACK(1, 0); |
| |
| f = (ficlFloat)(dataTop--)->f; |
| floatTop->f += f; |
| continue; |
| |
| /* |
| * Do float - integer r - n. |
| * f-i ( r n -- r ) |
| */ |
| case ficlInstructionFMinusI: |
| CHECK_FLOAT_STACK(1, 1); |
| CHECK_STACK(1, 0); |
| |
| f = (ficlFloat)(dataTop--)->f; |
| floatTop->f -= f; |
| continue; |
| |
| /* |
| * Do float * integer r * n. |
| * f*i ( r n -- r ) |
| */ |
| case ficlInstructionFStarI: |
| CHECK_FLOAT_STACK(1, 1); |
| CHECK_STACK(1, 0); |
| |
| f = (ficlFloat)(dataTop--)->f; |
| floatTop->f *= f; |
| continue; |
| |
| /* |
| * Do float / integer r / n. |
| * f/i ( r n -- r ) |
| */ |
| case ficlInstructionFSlashI: |
| CHECK_FLOAT_STACK(1, 1); |
| CHECK_STACK(1, 0); |
| |
| f = (ficlFloat)(dataTop--)->f; |
| floatTop->f /= f; |
| continue; |
| |
| /* |
| * Do integer - float n - r. |
| * i-f ( n r -- r ) |
| */ |
| case ficlInstructionIMinusF: |
| CHECK_FLOAT_STACK(1, 1); |
| CHECK_STACK(1, 0); |
| |
| f = (ficlFloat)(dataTop--)->f; |
| floatTop->f = f - floatTop->f; |
| continue; |
| |
| /* |
| * Do integer / float n / r. |
| * i/f ( n r -- r ) |
| */ |
| case ficlInstructionISlashF: |
| CHECK_FLOAT_STACK(1, 1); |
| CHECK_STACK(1, 0); |
| |
| f = (ficlFloat)(dataTop--)->f; |
| floatTop->f = f / floatTop->f; |
| continue; |
| |
| /* |
| * Do integer to float conversion. |
| * int>float ( n -- r ) |
| */ |
| case ficlInstructionIntToFloat: |
| CHECK_STACK(1, 0); |
| CHECK_FLOAT_STACK(0, 1); |
| |
| (++floatTop)->f = ((dataTop--)->f); |
| continue; |
| |
| /* |
| * Do float to integer conversion. |
| * float>int ( r -- n ) |
| */ |
| case ficlInstructionFloatToInt: |
| CHECK_STACK(0, 1); |
| CHECK_FLOAT_STACK(1, 0); |
| |
| (++dataTop)->i = ((floatTop--)->i); |
| continue; |
| |
| /* |
| * Add a floating point number to contents of a variable. |
| * f+! ( r n -- ) |
| */ |
| case ficlInstructionFPlusStore: { |
| ficlCell *cell; |
| |
| CHECK_STACK(1, 0); |
| CHECK_FLOAT_STACK(1, 0); |
| |
| cell = (ficlCell *)(dataTop--)->p; |
| cell->f += (floatTop--)->f; |
| continue; |
| } |
| |
| /* |
| * Do float stack drop. |
| * fdrop ( r -- ) |
| */ |
| case ficlInstructionFDrop: |
| CHECK_FLOAT_STACK(1, 0); |
| floatTop--; |
| continue; |
| |
| /* |
| * Do float stack ?dup. |
| * f?dup ( r -- r ) |
| */ |
| case ficlInstructionFQuestionDup: |
| CHECK_FLOAT_STACK(1, 2); |
| |
| if (floatTop->f != 0) |
| goto FDUP; |
| |
| continue; |
| |
| /* |
| * Do float stack dup. |
| * fdup ( r -- r r ) |
| */ |
| case ficlInstructionFDup: |
| CHECK_FLOAT_STACK(1, 2); |
| |
| FDUP: |
| floatTop[1] = floatTop[0]; |
| floatTop++; |
| continue; |
| |
| /* |
| * Do float stack swap. |
| * fswap ( r1 r2 -- r2 r1 ) |
| */ |
| case ficlInstructionFSwap: |
| CHECK_FLOAT_STACK(2, 2); |
| |
| c = floatTop[0]; |
| floatTop[0] = floatTop[-1]; |
| floatTop[-1] = c; |
| continue; |
| |
| /* |
| * Do float stack 2drop. |
| * f2drop ( r r -- ) |
| */ |
| case ficlInstructionF2Drop: |
| CHECK_FLOAT_STACK(2, 0); |
| |
| floatTop -= 2; |
| continue; |
| |
| /* |
| * Do float stack 2dup. |
| * f2dup ( r1 r2 -- r1 r2 r1 r2 ) |
| */ |
| case ficlInstructionF2Dup: |
| CHECK_FLOAT_STACK(2, 4); |
| |
| floatTop[1] = floatTop[-1]; |
| floatTop[2] = *floatTop; |
| floatTop += 2; |
| continue; |
| |
| /* |
| * Do float stack over. |
| * fover ( r1 r2 -- r1 r2 r1 ) |
| */ |
| case ficlInstructionFOver: |
| CHECK_FLOAT_STACK(2, 3); |
| |
| floatTop[1] = floatTop[-1]; |
| floatTop++; |
| continue; |
| |
| /* |
| * Do float stack 2over. |
| * f2over ( r1 r2 r3 -- r1 r2 r3 r1 r2 ) |
| */ |
| case ficlInstructionF2Over: |
| CHECK_FLOAT_STACK(4, 6); |
| |
| floatTop[1] = floatTop[-2]; |
| floatTop[2] = floatTop[-1]; |
| floatTop += 2; |
| continue; |
| |
| /* |
| * Do float stack pick. |
| * fpick ( n -- r ) |
| */ |
| case ficlInstructionFPick: |
| CHECK_STACK(1, 0); |
| c = *dataTop--; |
| CHECK_FLOAT_STACK(c.i+2, c.i+3); |
| |
| floatTop[1] = floatTop[- c.i - 1]; |
| continue; |
| |
| /* |
| * Do float stack rot. |
| * frot ( r1 r2 r3 -- r2 r3 r1 ) |
| */ |
| case ficlInstructionFRot: |
| i = 2; |
| goto FROLL; |
| |
| /* |
| * Do float stack roll. |
| * froll ( n -- ) |
| */ |
| case ficlInstructionFRoll: |
| CHECK_STACK(1, 0); |
| i = (dataTop--)->i; |
| |
| if (i < 1) |
| continue; |
| |
| FROLL: |
| CHECK_FLOAT_STACK(i+1, i+2); |
| c = floatTop[-i]; |
| memmove(floatTop - i, floatTop - (i - 1), |
| i * sizeof (ficlCell)); |
| *floatTop = c; |
| |
| continue; |
| |
| /* |
| * Do float stack -rot. |
| * f-rot ( r1 r2 r3 -- r3 r1 r2 ) |
| */ |
| case ficlInstructionFMinusRot: |
| i = 2; |
| goto FMINUSROLL; |
| |
| |
| /* |
| * Do float stack -roll. |
| * f-roll ( n -- ) |
| */ |
| case ficlInstructionFMinusRoll: |
| CHECK_STACK(1, 0); |
| i = (dataTop--)->i; |
| |
| if (i < 1) |
| continue; |
| |
| FMINUSROLL: |
| CHECK_FLOAT_STACK(i+1, i+2); |
| c = *floatTop; |
| memmove(floatTop - (i - 1), floatTop - i, |
| i * sizeof (ficlCell)); |
| floatTop[-i] = c; |
| |
| continue; |
| |
| /* |
| * Do float stack 2swap |
| * f2swap ( r1 r2 r3 r4 -- r3 r4 r1 r2 ) |
| */ |
| case ficlInstructionF2Swap: { |
| ficlCell c2; |
| CHECK_FLOAT_STACK(4, 4); |
| |
| c = *floatTop; |
| c2 = floatTop[-1]; |
| |
| *floatTop = floatTop[-2]; |
| floatTop[-1] = floatTop[-3]; |
| |
| floatTop[-2] = c; |
| floatTop[-3] = c2; |
| continue; |
| } |
| |
| /* |
| * Do float 0= comparison r = 0.0. |
| * f0= ( r -- T/F ) |
| */ |
| case ficlInstructionF0Equals: |
| CHECK_FLOAT_STACK(1, 0); |
| CHECK_STACK(0, 1); |
| |
| (++dataTop)->i = FICL_BOOL((floatTop--)->f != 0.0f); |
| continue; |
| |
| /* |
| * Do float 0< comparison r < 0.0. |
| * f0< ( r -- T/F ) |
| */ |
| case ficlInstructionF0Less: |
| CHECK_FLOAT_STACK(1, 0); |
| CHECK_STACK(0, 1); |
| |
| (++dataTop)->i = FICL_BOOL((floatTop--)->f < 0.0f); |
| continue; |
| |
| /* |
| * Do float 0> comparison r > 0.0. |
| * f0> ( r -- T/F ) |
| */ |
| case ficlInstructionF0Greater: |
| CHECK_FLOAT_STACK(1, 0); |
| CHECK_STACK(0, 1); |
| |
| (++dataTop)->i = FICL_BOOL((floatTop--)->f > 0.0f); |
| continue; |
| |
| /* |
| * Do float = comparison r1 = r2. |
| * f= ( r1 r2 -- T/F ) |
| */ |
| case ficlInstructionFEquals: |
| CHECK_FLOAT_STACK(2, 0); |
| CHECK_STACK(0, 1); |
| |
| f = (floatTop--)->f; |
| (++dataTop)->i = FICL_BOOL((floatTop--)->f == f); |
| continue; |
| |
| /* |
| * Do float < comparison r1 < r2. |
| * f< ( r1 r2 -- T/F ) |
| */ |
| case ficlInstructionFLess: |
| CHECK_FLOAT_STACK(2, 0); |
| CHECK_STACK(0, 1); |
| |
| f = (floatTop--)->f; |
| (++dataTop)->i = FICL_BOOL((floatTop--)->f < f); |
| continue; |
| |
| /* |
| * Do float > comparison r1 > r2. |
| * f> ( r1 r2 -- T/F ) |
| */ |
| case ficlInstructionFGreater: |
| CHECK_FLOAT_STACK(2, 0); |
| CHECK_STACK(0, 1); |
| |
| f = (floatTop--)->f; |
| (++dataTop)->i = FICL_BOOL((floatTop--)->f > f); |
| continue; |
| |
| |
| /* |
| * Move float to param stack (assumes they both fit in a |
| * single ficlCell) f>s |
| */ |
| case ficlInstructionFFrom: |
| CHECK_FLOAT_STACK(1, 0); |
| CHECK_STACK(0, 1); |
| |
| *++dataTop = *floatTop--; |
| continue; |
| |
| case ficlInstructionToF: |
| CHECK_FLOAT_STACK(0, 1); |
| CHECK_STACK(1, 0); |
| |
| *++floatTop = *dataTop--; |
| continue; |
| |
| #endif /* FICL_WANT_FLOAT */ |
| |
| /* |
| * c o l o n P a r e n |
| * This is the code that executes a colon definition. It |
| * assumes that the virtual machine is running a "next" loop |
| * (See the vm.c for its implementation of member function |
| * vmExecute()). The colon code simply copies the address of |
| * the first word in the list of words to interpret into IP |
| * after saving its old value. When we return to the "next" |
| * loop, the virtual machine will call the code for each |
| * word in turn. |
| */ |
| case ficlInstructionColonParen: |
| (++returnTop)->p = (void *)ip; |
| ip = (ficlInstruction *)(fw->param); |
| continue; |
| |
| case ficlInstructionCreateParen: |
| CHECK_STACK(0, 1); |
| (++dataTop)->p = (fw->param + 1); |
| continue; |
| |
| case ficlInstructionVariableParen: |
| CHECK_STACK(0, 1); |
| (++dataTop)->p = fw->param; |
| continue; |
| |
| /* |
| * c o n s t a n t P a r e n |
| * This is the run-time code for "constant". It simply returns |
| * the contents of its word's first data ficlCell. |
| */ |
| |
| #if FICL_WANT_FLOAT |
| case ficlInstructionF2ConstantParen: |
| CHECK_FLOAT_STACK(0, 2); |
| FLOAT_PUSH_CELL_POINTER_DOUBLE(fw->param); |
| |
| case ficlInstructionFConstantParen: |
| CHECK_FLOAT_STACK(0, 1); |
| FLOAT_PUSH_CELL_POINTER(fw->param); |
| #endif /* FICL_WANT_FLOAT */ |
| |
| case ficlInstruction2ConstantParen: |
| CHECK_STACK(0, 2); |
| PUSH_CELL_POINTER_DOUBLE(fw->param); |
| |
| case ficlInstructionConstantParen: |
| CHECK_STACK(0, 1); |
| PUSH_CELL_POINTER(fw->param); |
| |
| #if FICL_WANT_USER |
| case ficlInstructionUserParen: { |
| ficlInteger i = fw->param[0].i; |
| (++dataTop)->p = &vm->user[i]; |
| continue; |
| } |
| #endif |
| |
| default: |
| /* |
| * Clever hack, or evil coding? You be the judge. |
| * |
| * If the word we've been asked to execute is in fact |
| * an *instruction*, we grab the instruction, stow it |
| * in "i" (our local cache of *ip), and *jump* to the |
| * top of the switch statement. --lch |
| */ |
| if (((ficlInstruction)fw->code > |
| ficlInstructionInvalid) && |
| ((ficlInstruction)fw->code < ficlInstructionLast)) { |
| instruction = (ficlInstruction)fw->code; |
| goto AGAIN; |
| } |
| |
| LOCAL_VARIABLE_SPILL; |
| (vm)->runningWord = fw; |
| fw->code(vm); |
| LOCAL_VARIABLE_REFILL; |
| continue; |
| } |
| } |
| |
| LOCAL_VARIABLE_SPILL; |
| vm->exceptionHandler = oldExceptionHandler; |
| } |
| |
| /* |
| * v m G e t D i c t |
| * Returns the address dictionary for this VM's system |
| */ |
| ficlDictionary * |
| ficlVmGetDictionary(ficlVm *vm) |
| { |
| FICL_VM_ASSERT(vm, vm); |
| return (vm->callback.system->dictionary); |
| } |
| |
| /* |
| * v m G e t S t r i n g |
| * Parses a string out of the VM input buffer and copies up to the first |
| * FICL_COUNTED_STRING_MAX characters to the supplied destination buffer, a |
| * ficlCountedString. The destination string is NULL terminated. |
| * |
| * Returns the address of the first unused character in the dest buffer. |
| */ |
| char * |
| ficlVmGetString(ficlVm *vm, ficlCountedString *counted, char delimiter) |
| { |
| ficlString s = ficlVmParseStringEx(vm, delimiter, 0); |
| |
| if (FICL_STRING_GET_LENGTH(s) > FICL_COUNTED_STRING_MAX) { |
| FICL_STRING_SET_LENGTH(s, FICL_COUNTED_STRING_MAX); |
| } |
| |
| strncpy(counted->text, FICL_STRING_GET_POINTER(s), |
| FICL_STRING_GET_LENGTH(s)); |
| counted->text[FICL_STRING_GET_LENGTH(s)] = '\0'; |
| counted->length = (ficlUnsigned8)FICL_STRING_GET_LENGTH(s); |
| |
| return (counted->text + FICL_STRING_GET_LENGTH(s) + 1); |
| } |
| |
| /* |
| * v m G e t W o r d |
| * vmGetWord calls vmGetWord0 repeatedly until it gets a string with |
| * non-zero length. |
| */ |
| ficlString |
| ficlVmGetWord(ficlVm *vm) |
| { |
| ficlString s = ficlVmGetWord0(vm); |
| |
| if (FICL_STRING_GET_LENGTH(s) == 0) { |
| ficlVmThrow(vm, FICL_VM_STATUS_RESTART); |
| } |
| |
| return (s); |
| } |
| |
| /* |
| * v m G e t W o r d 0 |
| * Skip leading whitespace and parse a space delimited word from the tib. |
| * Returns the start address and length of the word. Updates the tib |
| * to reflect characters consumed, including the trailing delimiter. |
| * If there's nothing of interest in the tib, returns zero. This function |
| * does not use vmParseString because it uses isspace() rather than a |
| * single delimiter character. |
| */ |
| ficlString |
| ficlVmGetWord0(ficlVm *vm) |
| { |
| char *trace = ficlVmGetInBuf(vm); |
| char *stop = ficlVmGetInBufEnd(vm); |
| ficlString s; |
| ficlUnsigned length = 0; |
| char c = 0; |
| |
| trace = ficlStringSkipSpace(trace, stop); |
| FICL_STRING_SET_POINTER(s, trace); |
| |
| /* Please leave this loop this way; it makes Purify happier. --lch */ |
| for (;;) { |
| if (trace == stop) |
| break; |
| c = *trace; |
| if (isspace((unsigned char)c)) |
| break; |
| length++; |
| trace++; |
| } |
| |
| FICL_STRING_SET_LENGTH(s, length); |
| |
| /* skip one trailing delimiter */ |
| if ((trace != stop) && isspace((unsigned char)c)) |
| trace++; |
| |
| ficlVmUpdateTib(vm, trace); |
| |
| return (s); |
| } |
| |
| /* |
| * v m G e t W o r d T o P a d |
| * Does vmGetWord and copies the result to the pad as a NULL terminated |
| * string. Returns the length of the string. If the string is too long |
| * to fit in the pad, it is truncated. |
| */ |
| int |
| ficlVmGetWordToPad(ficlVm *vm) |
| { |
| ficlString s; |
| char *pad = (char *)vm->pad; |
| s = ficlVmGetWord(vm); |
| |
| if (FICL_STRING_GET_LENGTH(s) > FICL_PAD_SIZE) |
| FICL_STRING_SET_LENGTH(s, FICL_PAD_SIZE); |
| |
| strncpy(pad, FICL_STRING_GET_POINTER(s), FICL_STRING_GET_LENGTH(s)); |
| pad[FICL_STRING_GET_LENGTH(s)] = '\0'; |
| return ((int)(FICL_STRING_GET_LENGTH(s))); |
| } |
| |
| /* |
| * v m P a r s e S t r i n g |
| * Parses a string out of the input buffer using the delimiter |
| * specified. Skips leading delimiters, marks the start of the string, |
| * and counts characters to the next delimiter it encounters. It then |
| * updates the vm input buffer to consume all these chars, including the |
| * trailing delimiter. |
| * Returns the address and length of the parsed string, not including the |
| * trailing delimiter. |
| */ |
| ficlString |
| ficlVmParseString(ficlVm *vm, char delimiter) |
| { |
| return (ficlVmParseStringEx(vm, delimiter, 1)); |
| } |
| |
| ficlString |
| ficlVmParseStringEx(ficlVm *vm, char delimiter, char skipLeadingDelimiters) |
| { |
| ficlString s; |
| char *trace = ficlVmGetInBuf(vm); |
| char *stop = ficlVmGetInBufEnd(vm); |
| char c; |
| |
| if (skipLeadingDelimiters) { |
| while ((trace != stop) && (*trace == delimiter)) |
| trace++; |
| } |
| |
| FICL_STRING_SET_POINTER(s, trace); /* mark start of text */ |
| |
| /* find next delimiter or end of line */ |
| for (c = *trace; |
| (trace != stop) && (c != delimiter) && (c != '\r') && (c != '\n'); |
| c = *++trace) { |
| ; |
| } |
| |
| /* set length of result */ |
| FICL_STRING_SET_LENGTH(s, trace - FICL_STRING_GET_POINTER(s)); |
| |
| /* gobble trailing delimiter */ |
| if ((trace != stop) && (*trace == delimiter)) |
| trace++; |
| |
| ficlVmUpdateTib(vm, trace); |
| return (s); |
| } |
| |
| |
| /* |
| * v m P o p |
| */ |
| ficlCell |
| ficlVmPop(ficlVm *vm) |
| { |
| return (ficlStackPop(vm->dataStack)); |
| } |
| |
| /* |
| * v m P u s h |
| */ |
| void |
| ficlVmPush(ficlVm *vm, ficlCell c) |
| { |
| ficlStackPush(vm->dataStack, c); |
| } |
| |
| /* |
| * v m P o p I P |
| */ |
| void |
| ficlVmPopIP(ficlVm *vm) |
| { |
| vm->ip = (ficlIp)(ficlStackPopPointer(vm->returnStack)); |
| } |
| |
| /* |
| * v m P u s h I P |
| */ |
| void |
| ficlVmPushIP(ficlVm *vm, ficlIp newIP) |
| { |
| ficlStackPushPointer(vm->returnStack, (void *)vm->ip); |
| vm->ip = newIP; |
| } |
| |
| /* |
| * v m P u s h T i b |
| * Binds the specified input string to the VM and clears >IN (the index) |
| */ |
| void |
| ficlVmPushTib(ficlVm *vm, char *text, ficlInteger nChars, ficlTIB *pSaveTib) |
| { |
| if (pSaveTib) { |
| *pSaveTib = vm->tib; |
| } |
| vm->tib.text = text; |
| vm->tib.end = text + nChars; |
| vm->tib.index = 0; |
| } |
| |
| void |
| ficlVmPopTib(ficlVm *vm, ficlTIB *pTib) |
| { |
| if (pTib) { |
| vm->tib = *pTib; |
| } |
| } |
| |
| /* |
| * v m Q u i t |
| */ |
| void |
| ficlVmQuit(ficlVm *vm) |
| { |
| ficlStackReset(vm->returnStack); |
| vm->restart = 0; |
| vm->ip = NULL; |
| vm->runningWord = NULL; |
| vm->state = FICL_VM_STATE_INTERPRET; |
| vm->tib.text = NULL; |
| vm->tib.end = NULL; |
| vm->tib.index = 0; |
| vm->pad[0] = '\0'; |
| vm->sourceId.i = 0; |
| } |
| |
| /* |
| * v m R e s e t |
| */ |
| void |
| ficlVmReset(ficlVm *vm) |
| { |
| ficlVmQuit(vm); |
| ficlStackReset(vm->dataStack); |
| #if FICL_WANT_FLOAT |
| ficlStackReset(vm->floatStack); |
| #endif |
| vm->base = 10; |
| } |
| |
| /* |
| * v m S e t T e x t O u t |
| * Binds the specified output callback to the vm. If you pass NULL, |
| * binds the default output function (ficlTextOut) |
| */ |
| void |
| ficlVmSetTextOut(ficlVm *vm, ficlOutputFunction textOut) |
| { |
| vm->callback.textOut = textOut; |
| } |
| |
| void |
| ficlVmTextOut(ficlVm *vm, char *text) |
| { |
| ficlCallbackTextOut((ficlCallback *)vm, text); |
| } |
| |
| |
| void |
| ficlVmErrorOut(ficlVm *vm, char *text) |
| { |
| ficlCallbackErrorOut((ficlCallback *)vm, text); |
| } |
| |
| |
| /* |
| * v m T h r o w |
| */ |
| void |
| ficlVmThrow(ficlVm *vm, int except) |
| { |
| if (vm->exceptionHandler) |
| longjmp(*(vm->exceptionHandler), except); |
| } |
| |
| void |
| ficlVmThrowError(ficlVm *vm, char *fmt, ...) |
| { |
| va_list list; |
| |
| va_start(list, fmt); |
| vsprintf(vm->pad, fmt, list); |
| va_end(list); |
| strcat(vm->pad, "\n"); |
| |
| ficlVmErrorOut(vm, vm->pad); |
| longjmp(*(vm->exceptionHandler), FICL_VM_STATUS_ERROR_EXIT); |
| } |
| |
| void |
| ficlVmThrowErrorVararg(ficlVm *vm, char *fmt, va_list list) |
| { |
| vsprintf(vm->pad, fmt, list); |
| /* |
| * well, we can try anyway, we're certainly not |
| * returning to our caller! |
| */ |
| va_end(list); |
| strcat(vm->pad, "\n"); |
| |
| ficlVmErrorOut(vm, vm->pad); |
| longjmp(*(vm->exceptionHandler), FICL_VM_STATUS_ERROR_EXIT); |
| } |
| |
| /* |
| * f i c l E v a l u a t e |
| * Wrapper for ficlExec() which sets SOURCE-ID to -1. |
| */ |
| int |
| ficlVmEvaluate(ficlVm *vm, char *s) |
| { |
| int returnValue; |
| ficlCell id = vm->sourceId; |
| ficlString string; |
| vm->sourceId.i = -1; |
| FICL_STRING_SET_FROM_CSTRING(string, s); |
| returnValue = ficlVmExecuteString(vm, string); |
| vm->sourceId = id; |
| return (returnValue); |
| } |
| |
| /* |
| * f i c l E x e c |
| * Evaluates a block of input text in the context of the |
| * specified interpreter. Emits any requested output to the |
| * interpreter's output function. |
| * |
| * Contains the "inner interpreter" code in a tight loop |
| * |
| * Returns one of the VM_XXXX codes defined in ficl.h: |
| * VM_OUTOFTEXT is the normal exit condition |
| * VM_ERREXIT means that the interpreter encountered a syntax error |
| * and the vm has been reset to recover (some or all |
| * of the text block got ignored |
| * VM_USEREXIT means that the user executed the "bye" command |
| * to shut down the interpreter. This would be a good |
| * time to delete the vm, etc -- or you can ignore this |
| * signal. |
| */ |
| int |
| ficlVmExecuteString(ficlVm *vm, ficlString s) |
| { |
| ficlSystem *system = vm->callback.system; |
| ficlDictionary *dictionary = system->dictionary; |
| |
| int except; |
| jmp_buf vmState; |
| jmp_buf *oldState; |
| ficlTIB saveficlTIB; |
| |
| FICL_VM_ASSERT(vm, vm); |
| FICL_VM_ASSERT(vm, system->interpreterLoop[0]); |
| |
| ficlVmPushTib(vm, FICL_STRING_GET_POINTER(s), |
| FICL_STRING_GET_LENGTH(s), &saveficlTIB); |
| |
| /* |
| * Save and restore VM's jmp_buf to enable nested calls to ficlExec |
| */ |
| oldState = vm->exceptionHandler; |
| |
| /* This has to come before the setjmp! */ |
| vm->exceptionHandler = &vmState; |
| except = setjmp(vmState); |
| |
| switch (except) { |
| case 0: |
| if (vm->restart) { |
| vm->runningWord->code(vm); |
| vm->restart = 0; |
| } else { /* set VM up to interpret text */ |
| ficlVmPushIP(vm, &(system->interpreterLoop[0])); |
| } |
| |
| ficlVmInnerLoop(vm, 0); |
| break; |
| |
| case FICL_VM_STATUS_RESTART: |
| vm->restart = 1; |
| except = FICL_VM_STATUS_OUT_OF_TEXT; |
| break; |
| |
| case FICL_VM_STATUS_OUT_OF_TEXT: |
| ficlVmPopIP(vm); |
| #if 0 /* we dont output prompt in loader */ |
| if ((vm->state != FICL_VM_STATE_COMPILE) && |
| (vm->sourceId.i == 0)) |
| ficlVmTextOut(vm, FICL_PROMPT); |
| #endif |
| break; |
| |
| case FICL_VM_STATUS_USER_EXIT: |
| case FICL_VM_STATUS_INNER_EXIT: |
| case FICL_VM_STATUS_BREAK: |
| break; |
| |
| case FICL_VM_STATUS_QUIT: |
| if (vm->state == FICL_VM_STATE_COMPILE) { |
| ficlDictionaryAbortDefinition(dictionary); |
| #if FICL_WANT_LOCALS |
| ficlDictionaryEmpty(system->locals, |
| system->locals->forthWordlist->size); |
| #endif |
| } |
| ficlVmQuit(vm); |
| break; |
| |
| case FICL_VM_STATUS_ERROR_EXIT: |
| case FICL_VM_STATUS_ABORT: |
| case FICL_VM_STATUS_ABORTQ: |
| default: /* user defined exit code?? */ |
| if (vm->state == FICL_VM_STATE_COMPILE) { |
| ficlDictionaryAbortDefinition(dictionary); |
| #if FICL_WANT_LOCALS |
| ficlDictionaryEmpty(system->locals, |
| system->locals->forthWordlist->size); |
| #endif |
| } |
| ficlDictionaryResetSearchOrder(dictionary); |
| ficlVmReset(vm); |
| break; |
| } |
| |
| vm->exceptionHandler = oldState; |
| ficlVmPopTib(vm, &saveficlTIB); |
| return (except); |
| } |
| |
| /* |
| * f i c l E x e c X T |
| * Given a pointer to a ficlWord, push an inner interpreter and |
| * execute the word to completion. This is in contrast with vmExecute, |
| * which does not guarantee that the word will have completed when |
| * the function returns (ie in the case of colon definitions, which |
| * need an inner interpreter to finish) |
| * |
| * Returns one of the VM_XXXX exception codes listed in ficl.h. Normal |
| * exit condition is VM_INNEREXIT, Ficl's private signal to exit the |
| * inner loop under normal circumstances. If another code is thrown to |
| * exit the loop, this function will re-throw it if it's nested under |
| * itself or ficlExec. |
| * |
| * NOTE: this function is intended so that C code can execute ficlWords |
| * given their address in the dictionary (xt). |
| */ |
| int |
| ficlVmExecuteXT(ficlVm *vm, ficlWord *pWord) |
| { |
| int except; |
| jmp_buf vmState; |
| jmp_buf *oldState; |
| ficlWord *oldRunningWord; |
| |
| FICL_VM_ASSERT(vm, vm); |
| FICL_VM_ASSERT(vm, vm->callback.system->exitInnerWord); |
| |
| /* |
| * Save the runningword so that RESTART behaves correctly |
| * over nested calls. |
| */ |
| oldRunningWord = vm->runningWord; |
| /* |
| * Save and restore VM's jmp_buf to enable nested calls |
| */ |
| oldState = vm->exceptionHandler; |
| /* This has to come before the setjmp! */ |
| vm->exceptionHandler = &vmState; |
| except = setjmp(vmState); |
| |
| if (except) |
| ficlVmPopIP(vm); |
| else |
| ficlVmPushIP(vm, &(vm->callback.system->exitInnerWord)); |
| |
| switch (except) { |
| case 0: |
| ficlVmExecuteWord(vm, pWord); |
| ficlVmInnerLoop(vm, 0); |
| break; |
| |
| case FICL_VM_STATUS_INNER_EXIT: |
| case FICL_VM_STATUS_BREAK: |
| break; |
| |
| case FICL_VM_STATUS_RESTART: |
| case FICL_VM_STATUS_OUT_OF_TEXT: |
| case FICL_VM_STATUS_USER_EXIT: |
| case FICL_VM_STATUS_QUIT: |
| case FICL_VM_STATUS_ERROR_EXIT: |
| case FICL_VM_STATUS_ABORT: |
| case FICL_VM_STATUS_ABORTQ: |
| default: /* user defined exit code?? */ |
| if (oldState) { |
| vm->exceptionHandler = oldState; |
| ficlVmThrow(vm, except); |
| } |
| break; |
| } |
| |
| vm->exceptionHandler = oldState; |
| vm->runningWord = oldRunningWord; |
| return (except); |
| } |
| |
| /* |
| * f i c l P a r s e N u m b e r |
| * Attempts to convert the NULL terminated string in the VM's pad to |
| * a number using the VM's current base. If successful, pushes the number |
| * onto the param stack and returns FICL_TRUE. Otherwise, returns FICL_FALSE. |
| * (jws 8/01) Trailing decimal point causes a zero ficlCell to be pushed. (See |
| * the standard for DOUBLE wordset. |
| */ |
| int |
| ficlVmParseNumber(ficlVm *vm, ficlString s) |
| { |
| ficlInteger accumulator = 0; |
| char isNegative = 0; |
| char isDouble = 0; |
| unsigned base = vm->base; |
| char *trace = FICL_STRING_GET_POINTER(s); |
| ficlUnsigned8 length = (ficlUnsigned8)FICL_STRING_GET_LENGTH(s); |
| unsigned c; |
| unsigned digit; |
| |
| if (length > 1) { |
| switch (*trace) { |
| case '-': |
| trace++; |
| length--; |
| isNegative = 1; |
| break; |
| case '+': |
| trace++; |
| length--; |
| isNegative = 0; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /* detect & remove trailing decimal */ |
| if ((length > 0) && (trace[length - 1] == '.')) { |
| isDouble = 1; |
| length--; |
| } |
| |
| if (length == 0) /* detect "+", "-", ".", "+." etc */ |
| return (0); /* false */ |
| |
| while ((length--) && ((c = *trace++) != '\0')) { |
| if (!isalnum(c)) |
| return (0); /* false */ |
| |
| digit = c - '0'; |
| |
| if (digit > 9) |
| digit = tolower(c) - 'a' + 10; |
| |
| if (digit >= base) |
| return (0); /* false */ |
| |
| accumulator = accumulator * base + digit; |
| } |
| |
| if (isNegative) |
| accumulator = -accumulator; |
| |
| ficlStackPushInteger(vm->dataStack, accumulator); |
| if (vm->state == FICL_VM_STATE_COMPILE) |
| ficlPrimitiveLiteralIm(vm); |
| |
| if (isDouble) { /* simple (required) DOUBLE support */ |
| if (isNegative) |
| ficlStackPushInteger(vm->dataStack, -1); |
| else |
| ficlStackPushInteger(vm->dataStack, 0); |
| if (vm->state == FICL_VM_STATE_COMPILE) |
| ficlPrimitiveLiteralIm(vm); |
| } |
| |
| return (1); /* true */ |
| } |
| |
| /* |
| * d i c t C h e c k |
| * Checks the dictionary for corruption and throws appropriate |
| * errors. |
| * Input: +n number of ADDRESS UNITS (not ficlCells) proposed to allot |
| * -n number of ADDRESS UNITS proposed to de-allot |
| * 0 just do a consistency check |
| */ |
| void |
| ficlVmDictionarySimpleCheck(ficlVm *vm, ficlDictionary *dictionary, int cells) |
| { |
| #if FICL_ROBUST >= 1 |
| if ((cells >= 0) && |
| (ficlDictionaryCellsAvailable(dictionary) * |
| (int)sizeof (ficlCell) < cells)) { |
| ficlVmThrowError(vm, "Error: dictionary full"); |
| } |
| |
| if ((cells <= 0) && |
| (ficlDictionaryCellsUsed(dictionary) * |
| (int)sizeof (ficlCell) < -cells)) { |
| ficlVmThrowError(vm, "Error: dictionary underflow"); |
| } |
| #else /* FICL_ROBUST >= 1 */ |
| FICL_IGNORE(vm); |
| FICL_IGNORE(dictionary); |
| FICL_IGNORE(cells); |
| #endif /* FICL_ROBUST >= 1 */ |
| } |
| |
| void |
| ficlVmDictionaryCheck(ficlVm *vm, ficlDictionary *dictionary, int cells) |
| { |
| #if FICL_ROBUST >= 1 |
| ficlVmDictionarySimpleCheck(vm, dictionary, cells); |
| |
| if (dictionary->wordlistCount > FICL_MAX_WORDLISTS) { |
| ficlDictionaryResetSearchOrder(dictionary); |
| ficlVmThrowError(vm, "Error: search order overflow"); |
| } else if (dictionary->wordlistCount < 0) { |
| ficlDictionaryResetSearchOrder(dictionary); |
| ficlVmThrowError(vm, "Error: search order underflow"); |
| } |
| #else /* FICL_ROBUST >= 1 */ |
| FICL_IGNORE(vm); |
| FICL_IGNORE(dictionary); |
| FICL_IGNORE(cells); |
| #endif /* FICL_ROBUST >= 1 */ |
| } |
| |
| void |
| ficlVmDictionaryAllot(ficlVm *vm, ficlDictionary *dictionary, int n) |
| { |
| FICL_VM_DICTIONARY_SIMPLE_CHECK(vm, dictionary, n); |
| FICL_IGNORE(vm); |
| ficlDictionaryAllot(dictionary, n); |
| } |
| |
| void |
| ficlVmDictionaryAllotCells(ficlVm *vm, ficlDictionary *dictionary, int cells) |
| { |
| FICL_VM_DICTIONARY_SIMPLE_CHECK(vm, dictionary, cells); |
| FICL_IGNORE(vm); |
| ficlDictionaryAllotCells(dictionary, cells); |
| } |
| |
| /* |
| * f i c l P a r s e W o r d |
| * From the standard, section 3.4 |
| * b) Search the dictionary name space (see 3.4.2). If a definition name |
| * matching the string is found: |
| * 1.if interpreting, perform the interpretation semantics of the definition |
| * (see 3.4.3.2), and continue at a); |
| * 2.if compiling, perform the compilation semantics of the definition |
| * (see 3.4.3.3), and continue at a). |
| * |
| * c) If a definition name matching the string is not found, attempt to |
| * convert the string to a number (see 3.4.1.3). If successful: |
| * 1.if interpreting, place the number on the data stack, and continue at a); |
| * 2.if compiling, FICL_VM_STATE_COMPILE code that when executed will place |
| * the number on the stack (see 6.1.1780 LITERAL), and continue at a); |
| * |
| * d) If unsuccessful, an ambiguous condition exists (see 3.4.4). |
| * |
| * (jws 4/01) Modified to be a ficlParseStep |
| */ |
| int |
| ficlVmParseWord(ficlVm *vm, ficlString name) |
| { |
| ficlDictionary *dictionary = ficlVmGetDictionary(vm); |
| ficlWord *tempFW; |
| |
| FICL_VM_DICTIONARY_CHECK(vm, dictionary, 0); |
| FICL_STACK_CHECK(vm->dataStack, 0, 0); |
| |
| #if FICL_WANT_LOCALS |
| if (vm->callback.system->localsCount > 0) { |
| tempFW = ficlSystemLookupLocal(vm->callback.system, name); |
| } else |
| #endif |
| tempFW = ficlDictionaryLookup(dictionary, name); |
| |
| if (vm->state == FICL_VM_STATE_INTERPRET) { |
| if (tempFW != NULL) { |
| if (ficlWordIsCompileOnly(tempFW)) { |
| ficlVmThrowError(vm, |
| "Error: FICL_VM_STATE_COMPILE only!"); |
| } |
| |
| ficlVmExecuteWord(vm, tempFW); |
| return (1); /* true */ |
| } |
| } else { /* (vm->state == FICL_VM_STATE_COMPILE) */ |
| if (tempFW != NULL) { |
| if (ficlWordIsImmediate(tempFW)) { |
| ficlVmExecuteWord(vm, tempFW); |
| } else { |
| ficlCell c; |
| c.p = tempFW; |
| if (tempFW->flags & FICL_WORD_INSTRUCTION) |
| ficlDictionaryAppendUnsigned(dictionary, |
| (ficlInteger)tempFW->code); |
| else |
| ficlDictionaryAppendCell(dictionary, c); |
| } |
| return (1); /* true */ |
| } |
| } |
| |
| return (0); /* false */ |
| } |