| /* |
| * t o o l s . c |
| * Forth Inspired Command Language - programming tools |
| * Author: John Sadler (john_sadler@alum.mit.edu) |
| * Created: 20 June 2000 |
| * $Id: tools.c,v 1.12 2010/08/12 13:57:22 asau Exp $ |
| */ |
| /* |
| * 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. |
| */ |
| |
| /* |
| * NOTES: |
| * SEE needs information about the addresses of functions that |
| * are the CFAs of colon definitions, constants, variables, DOES> |
| * words, and so on. It gets this information from a table and supporting |
| * functions in words.c. |
| * fiColonParen fiDoDoes createParen fiVariableParen fiUserParen fiConstantParen |
| * |
| * Step and break debugger for Ficl |
| * debug ( xt -- ) Start debugging an xt |
| * Set a breakpoint |
| * Specify breakpoint default action |
| */ |
| |
| #include "ficl.h" |
| |
| extern void exit(int); |
| |
| static void ficlPrimitiveStepIn(ficlVm *vm); |
| static void ficlPrimitiveStepOver(ficlVm *vm); |
| static void ficlPrimitiveStepBreak(ficlVm *vm); |
| |
| void |
| ficlCallbackAssert(ficlCallback *callback, int expression, |
| char *expressionString, char *filename, int line) |
| { |
| #if FICL_ROBUST >= 1 |
| if (!expression) { |
| static char buffer[256]; |
| sprintf(buffer, "ASSERTION FAILED at %s:%d: \"%s\"\n", |
| filename, line, expressionString); |
| ficlCallbackTextOut(callback, buffer); |
| exit(-1); |
| } |
| #else /* FICL_ROBUST >= 1 */ |
| FICL_IGNORE(callback); |
| FICL_IGNORE(expression); |
| FICL_IGNORE(expressionString); |
| FICL_IGNORE(filename); |
| FICL_IGNORE(line); |
| #endif /* FICL_ROBUST >= 1 */ |
| } |
| |
| /* |
| * v m S e t B r e a k |
| * Set a breakpoint at the current value of IP by |
| * storing that address in a BREAKPOINT record |
| */ |
| static void |
| ficlVmSetBreak(ficlVm *vm, ficlBreakpoint *pBP) |
| { |
| ficlWord *pStep = ficlSystemLookup(vm->callback.system, "step-break"); |
| FICL_VM_ASSERT(vm, pStep); |
| |
| pBP->address = vm->ip; |
| pBP->oldXT = *vm->ip; |
| *vm->ip = pStep; |
| } |
| |
| /* |
| * d e b u g P r o m p t |
| */ |
| static void |
| ficlDebugPrompt(ficlVm *vm, int debug) |
| { |
| if (debug) |
| setenv("prompt", "dbg> ", 1); |
| else |
| setenv("prompt", "${interpret}", 1); |
| } |
| |
| #if 0 |
| static int |
| isPrimitive(ficlWord *word) |
| { |
| ficlWordKind wk = ficlWordClassify(word); |
| return ((wk != COLON) && (wk != DOES)); |
| } |
| #endif |
| |
| /* |
| * d i c t H a s h S u m m a r y |
| * Calculate a figure of merit for the dictionary hash table based |
| * on the average search depth for all the words in the dictionary, |
| * assuming uniform distribution of target keys. The figure of merit |
| * is the ratio of the total search depth for all keys in the table |
| * versus a theoretical optimum that would be achieved if the keys |
| * were distributed into the table as evenly as possible. |
| * The figure would be worse if the hash table used an open |
| * addressing scheme (i.e. collisions resolved by searching the |
| * table for an empty slot) for a given size table. |
| */ |
| #if FICL_WANT_FLOAT |
| void |
| ficlPrimitiveHashSummary(ficlVm *vm) |
| { |
| ficlDictionary *dictionary = ficlVmGetDictionary(vm); |
| ficlHash *pFHash; |
| ficlWord **hash; |
| unsigned size; |
| ficlWord *word; |
| unsigned i; |
| int nMax = 0; |
| int nWords = 0; |
| int nFilled; |
| double avg = 0.0; |
| double best; |
| int nAvg, nRem, nDepth; |
| |
| FICL_VM_DICTIONARY_CHECK(vm, dictionary, 0); |
| |
| pFHash = dictionary->wordlists[dictionary->wordlistCount - 1]; |
| hash = pFHash->table; |
| size = pFHash->size; |
| nFilled = size; |
| |
| for (i = 0; i < size; i++) { |
| int n = 0; |
| word = hash[i]; |
| |
| while (word) { |
| ++n; |
| ++nWords; |
| word = word->link; |
| } |
| |
| avg += (double)(n * (n+1)) / 2.0; |
| |
| if (n > nMax) |
| nMax = n; |
| if (n == 0) |
| --nFilled; |
| } |
| |
| /* Calc actual avg search depth for this hash */ |
| avg = avg / nWords; |
| |
| /* Calc best possible performance with this size hash */ |
| nAvg = nWords / size; |
| nRem = nWords % size; |
| nDepth = size * (nAvg * (nAvg+1))/2 + (nAvg+1)*nRem; |
| best = (double)nDepth/nWords; |
| |
| sprintf(vm->pad, "%d bins, %2.0f%% filled, Depth: " |
| "Max=%d, Avg=%2.1f, Best=%2.1f, Score: %2.0f%%\n", |
| size, (double)nFilled * 100.0 / size, nMax, |
| avg, best, 100.0 * best / avg); |
| |
| ficlVmTextOut(vm, vm->pad); |
| } |
| #endif |
| |
| /* |
| * Here's the outer part of the decompiler. It's |
| * just a big nested conditional that checks the |
| * CFA of the word to decompile for each kind of |
| * known word-builder code, and tries to do |
| * something appropriate. If the CFA is not recognized, |
| * just indicate that it is a primitive. |
| */ |
| static void |
| ficlPrimitiveSeeXT(ficlVm *vm) |
| { |
| ficlWord *word; |
| ficlWordKind kind; |
| |
| word = (ficlWord *)ficlStackPopPointer(vm->dataStack); |
| kind = ficlWordClassify(word); |
| |
| switch (kind) { |
| case FICL_WORDKIND_COLON: |
| sprintf(vm->pad, ": %.*s\n", word->length, word->name); |
| ficlVmTextOut(vm, vm->pad); |
| ficlDictionarySee(ficlVmGetDictionary(vm), word, |
| &(vm->callback)); |
| break; |
| case FICL_WORDKIND_DOES: |
| ficlVmTextOut(vm, "does>\n"); |
| ficlDictionarySee(ficlVmGetDictionary(vm), |
| (ficlWord *)word->param->p, &(vm->callback)); |
| break; |
| case FICL_WORDKIND_CREATE: |
| ficlVmTextOut(vm, "create\n"); |
| break; |
| case FICL_WORDKIND_VARIABLE: |
| sprintf(vm->pad, "variable = %ld (%#lx)\n", |
| (long)word->param->i, (long unsigned)word->param->u); |
| ficlVmTextOut(vm, vm->pad); |
| break; |
| #if FICL_WANT_USER |
| case FICL_WORDKIND_USER: |
| sprintf(vm->pad, "user variable %ld (%#lx)\n", |
| (long)word->param->i, (long unsigned)word->param->u); |
| ficlVmTextOut(vm, vm->pad); |
| break; |
| #endif |
| case FICL_WORDKIND_CONSTANT: |
| sprintf(vm->pad, "constant = %ld (%#lx)\n", |
| (long)word->param->i, (long unsigned)word->param->u); |
| ficlVmTextOut(vm, vm->pad); |
| break; |
| case FICL_WORDKIND_2CONSTANT: |
| sprintf(vm->pad, "constant = %ld %ld (%#lx %#lx)\n", |
| (long)word->param[1].i, (long)word->param->i, |
| (long unsigned)word->param[1].u, |
| (long unsigned)word->param->u); |
| ficlVmTextOut(vm, vm->pad); |
| break; |
| |
| default: |
| sprintf(vm->pad, "%.*s is a primitive\n", word->length, |
| word->name); |
| ficlVmTextOut(vm, vm->pad); |
| break; |
| } |
| |
| if (word->flags & FICL_WORD_IMMEDIATE) { |
| ficlVmTextOut(vm, "immediate\n"); |
| } |
| |
| if (word->flags & FICL_WORD_COMPILE_ONLY) { |
| ficlVmTextOut(vm, "compile-only\n"); |
| } |
| } |
| |
| static void |
| ficlPrimitiveSee(ficlVm *vm) |
| { |
| ficlPrimitiveTick(vm); |
| ficlPrimitiveSeeXT(vm); |
| } |
| |
| /* |
| * f i c l D e b u g X T |
| * debug ( xt -- ) |
| * Given an xt of a colon definition or a word defined by DOES>, set the |
| * VM up to debug the word: push IP, set the xt as the next thing to execute, |
| * set a breakpoint at its first instruction, and run to the breakpoint. |
| * Note: the semantics of this word are equivalent to "step in" |
| */ |
| static void |
| ficlPrimitiveDebugXT(ficlVm *vm) |
| { |
| ficlWord *xt = ficlStackPopPointer(vm->dataStack); |
| ficlWordKind wk = ficlWordClassify(xt); |
| |
| ficlStackPushPointer(vm->dataStack, xt); |
| ficlPrimitiveSeeXT(vm); |
| |
| switch (wk) { |
| case FICL_WORDKIND_COLON: |
| case FICL_WORDKIND_DOES: |
| /* |
| * Run the colon code and set a breakpoint at the next |
| * instruction |
| */ |
| ficlVmExecuteWord(vm, xt); |
| ficlVmSetBreak(vm, &(vm->callback.system->breakpoint)); |
| break; |
| default: |
| ficlVmExecuteWord(vm, xt); |
| break; |
| } |
| } |
| |
| /* |
| * s t e p I n |
| * Ficl |
| * Execute the next instruction, stepping into it if it's a colon definition |
| * or a does> word. This is the easy kind of step. |
| */ |
| static void |
| ficlPrimitiveStepIn(ficlVm *vm) |
| { |
| /* |
| * Do one step of the inner loop |
| */ |
| ficlVmExecuteWord(vm, *vm->ip++); |
| |
| /* |
| * Now set a breakpoint at the next instruction |
| */ |
| ficlVmSetBreak(vm, &(vm->callback.system->breakpoint)); |
| } |
| |
| /* |
| * s t e p O v e r |
| * Ficl |
| * Execute the next instruction atomically. This requires some insight into |
| * the memory layout of compiled code. Set a breakpoint at the next instruction |
| * in this word, and run until we hit it |
| */ |
| static void |
| ficlPrimitiveStepOver(ficlVm *vm) |
| { |
| ficlWord *word; |
| ficlWordKind kind; |
| ficlWord *pStep = ficlSystemLookup(vm->callback.system, "step-break"); |
| FICL_VM_ASSERT(vm, pStep); |
| |
| word = *vm->ip; |
| kind = ficlWordClassify(word); |
| |
| switch (kind) { |
| case FICL_WORDKIND_COLON: |
| case FICL_WORDKIND_DOES: |
| /* |
| * assume that the next ficlCell holds an instruction |
| * set a breakpoint there and return to the inner interpreter |
| */ |
| vm->callback.system->breakpoint.address = vm->ip + 1; |
| vm->callback.system->breakpoint.oldXT = vm->ip[1]; |
| vm->ip[1] = pStep; |
| break; |
| default: |
| ficlPrimitiveStepIn(vm); |
| break; |
| } |
| } |
| |
| /* |
| * s t e p - b r e a k |
| * Ficl |
| * Handles breakpoints for stepped execution. |
| * Upon entry, breakpoint contains the address and replaced instruction |
| * of the current breakpoint. |
| * Clear the breakpoint |
| * Get a command from the console. |
| * i (step in) - execute the current instruction and set a new breakpoint |
| * at the IP |
| * o (step over) - execute the current instruction to completion and set |
| * a new breakpoint at the IP |
| * g (go) - execute the current instruction and exit |
| * q (quit) - abort current word |
| * b (toggle breakpoint) |
| */ |
| |
| extern char *ficlDictionaryInstructionNames[]; |
| |
| static void |
| ficlPrimitiveStepBreak(ficlVm *vm) |
| { |
| ficlString command; |
| ficlWord *word; |
| ficlWord *pOnStep; |
| int debug = 1; |
| |
| if (!vm->restart) { |
| FICL_VM_ASSERT(vm, vm->callback.system->breakpoint.address); |
| FICL_VM_ASSERT(vm, vm->callback.system->breakpoint.oldXT); |
| |
| /* |
| * Clear the breakpoint that caused me to run |
| * Restore the original instruction at the breakpoint, |
| * and restore the IP |
| */ |
| vm->ip = (ficlIp)(vm->callback.system->breakpoint.address); |
| *vm->ip = vm->callback.system->breakpoint.oldXT; |
| |
| /* |
| * If there's an onStep, do it |
| */ |
| pOnStep = ficlSystemLookup(vm->callback.system, "on-step"); |
| if (pOnStep) |
| ficlVmExecuteXT(vm, pOnStep); |
| |
| /* |
| * Print the name of the next instruction |
| */ |
| word = vm->callback.system->breakpoint.oldXT; |
| |
| if ((((ficlInstruction)word) > ficlInstructionInvalid) && |
| (((ficlInstruction)word) < ficlInstructionLast)) |
| sprintf(vm->pad, "next: %s (instruction %ld)\n", |
| ficlDictionaryInstructionNames[(long)word], |
| (long)word); |
| else { |
| sprintf(vm->pad, "next: %s\n", word->name); |
| if (strcmp(word->name, "interpret") == 0) |
| debug = 0; |
| } |
| |
| ficlVmTextOut(vm, vm->pad); |
| ficlDebugPrompt(vm, debug); |
| } else { |
| vm->restart = 0; |
| } |
| |
| command = ficlVmGetWord(vm); |
| |
| switch (command.text[0]) { |
| case 'i': |
| ficlPrimitiveStepIn(vm); |
| break; |
| |
| case 'o': |
| ficlPrimitiveStepOver(vm); |
| break; |
| |
| case 'g': |
| break; |
| |
| case 'l': { |
| ficlWord *xt; |
| xt = ficlDictionaryFindEnclosingWord( |
| ficlVmGetDictionary(vm), (ficlCell *)(vm->ip)); |
| if (xt) { |
| ficlStackPushPointer(vm->dataStack, xt); |
| ficlPrimitiveSeeXT(vm); |
| } else { |
| ficlVmTextOut(vm, "sorry - can't do that\n"); |
| } |
| ficlVmThrow(vm, FICL_VM_STATUS_RESTART); |
| break; |
| } |
| |
| case 'q': |
| ficlDebugPrompt(vm, 0); |
| ficlVmThrow(vm, FICL_VM_STATUS_ABORT); |
| break; |
| case 'x': { |
| /* |
| * Take whatever's left in the TIB and feed it to a |
| * subordinate ficlVmExecuteString |
| */ |
| int returnValue; |
| ficlString s; |
| ficlWord *oldRunningWord = vm->runningWord; |
| |
| FICL_STRING_SET_POINTER(s, |
| vm->tib.text + vm->tib.index); |
| FICL_STRING_SET_LENGTH(s, |
| vm->tib.end - FICL_STRING_GET_POINTER(s)); |
| |
| returnValue = ficlVmExecuteString(vm, s); |
| |
| if (returnValue == FICL_VM_STATUS_OUT_OF_TEXT) { |
| returnValue = FICL_VM_STATUS_RESTART; |
| vm->runningWord = oldRunningWord; |
| ficlVmTextOut(vm, "\n"); |
| } |
| if (returnValue == FICL_VM_STATUS_ERROR_EXIT) |
| ficlDebugPrompt(vm, 0); |
| |
| ficlVmThrow(vm, returnValue); |
| break; |
| } |
| |
| default: |
| ficlVmTextOut(vm, |
| "i -- step In\n" |
| "o -- step Over\n" |
| "g -- Go (execute to completion)\n" |
| "l -- List source code\n" |
| "q -- Quit (stop debugging and abort)\n" |
| "x -- eXecute the rest of the line " |
| "as Ficl words\n"); |
| ficlDebugPrompt(vm, 1); |
| ficlVmThrow(vm, FICL_VM_STATUS_RESTART); |
| break; |
| } |
| |
| ficlDebugPrompt(vm, 0); |
| } |
| |
| /* |
| * b y e |
| * TOOLS |
| * Signal the system to shut down - this causes ficlExec to return |
| * VM_USEREXIT. The rest is up to you. |
| */ |
| static void |
| ficlPrimitiveBye(ficlVm *vm) |
| { |
| ficlVmThrow(vm, FICL_VM_STATUS_USER_EXIT); |
| } |
| |
| /* |
| * d i s p l a y S t a c k |
| * TOOLS |
| * Display the parameter stack (code for ".s") |
| */ |
| |
| struct stackContext |
| { |
| ficlVm *vm; |
| ficlDictionary *dictionary; |
| int count; |
| }; |
| |
| static ficlInteger |
| ficlStackDisplayCallback(void *c, ficlCell *cell) |
| { |
| struct stackContext *context = (struct stackContext *)c; |
| char buffer[80]; |
| |
| #ifdef _LP64 |
| snprintf(buffer, sizeof (buffer), "[0x%016lx %3d]: %20ld (0x%016lx)\n", |
| (unsigned long)cell, context->count++, (long)cell->i, |
| (unsigned long)cell->u); |
| #else |
| snprintf(buffer, sizeof (buffer), "[0x%08x %3d]: %12d (0x%08x)\n", |
| (unsigned)cell, context->count++, cell->i, cell->u); |
| #endif |
| |
| ficlVmTextOut(context->vm, buffer); |
| return (FICL_TRUE); |
| } |
| |
| void |
| ficlStackDisplay(ficlStack *stack, ficlStackWalkFunction callback, |
| void *context) |
| { |
| ficlVm *vm = stack->vm; |
| char buffer[128]; |
| struct stackContext myContext; |
| |
| FICL_STACK_CHECK(stack, 0, 0); |
| |
| #ifdef _LP64 |
| sprintf(buffer, "[%s stack has %d entries, top at 0x%016lx]\n", |
| stack->name, ficlStackDepth(stack), (unsigned long)stack->top); |
| #else |
| sprintf(buffer, "[%s stack has %d entries, top at 0x%08x]\n", |
| stack->name, ficlStackDepth(stack), (unsigned)stack->top); |
| #endif |
| ficlVmTextOut(vm, buffer); |
| |
| if (callback == NULL) { |
| myContext.vm = vm; |
| myContext.count = 0; |
| context = &myContext; |
| callback = ficlStackDisplayCallback; |
| } |
| ficlStackWalk(stack, callback, context, FICL_FALSE); |
| |
| #ifdef _LP64 |
| sprintf(buffer, "[%s stack base at 0x%016lx]\n", stack->name, |
| (unsigned long)stack->base); |
| #else |
| sprintf(buffer, "[%s stack base at 0x%08x]\n", stack->name, |
| (unsigned)stack->base); |
| #endif |
| ficlVmTextOut(vm, buffer); |
| } |
| |
| void |
| ficlVmDisplayDataStack(ficlVm *vm) |
| { |
| ficlStackDisplay(vm->dataStack, NULL, NULL); |
| } |
| |
| static ficlInteger |
| ficlStackDisplaySimpleCallback(void *c, ficlCell *cell) |
| { |
| struct stackContext *context = (struct stackContext *)c; |
| char buffer[32]; |
| |
| sprintf(buffer, "%s%ld", context->count ? " " : "", (long)cell->i); |
| context->count++; |
| ficlVmTextOut(context->vm, buffer); |
| return (FICL_TRUE); |
| } |
| |
| void |
| ficlVmDisplayDataStackSimple(ficlVm *vm) |
| { |
| ficlStack *stack = vm->dataStack; |
| char buffer[32]; |
| struct stackContext context; |
| |
| FICL_STACK_CHECK(stack, 0, 0); |
| |
| sprintf(buffer, "[%d] ", ficlStackDepth(stack)); |
| ficlVmTextOut(vm, buffer); |
| |
| context.vm = vm; |
| context.count = 0; |
| ficlStackWalk(stack, ficlStackDisplaySimpleCallback, &context, |
| FICL_TRUE); |
| } |
| |
| static ficlInteger |
| ficlReturnStackDisplayCallback(void *c, ficlCell *cell) |
| { |
| struct stackContext *context = (struct stackContext *)c; |
| char buffer[128]; |
| |
| #ifdef _LP64 |
| sprintf(buffer, "[0x%016lx %3d] %20ld (0x%016lx)", (unsigned long)cell, |
| context->count++, cell->i, cell->u); |
| #else |
| sprintf(buffer, "[0x%08x %3d] %12d (0x%08x)", (unsigned)cell, |
| context->count++, cell->i, cell->u); |
| #endif |
| |
| /* |
| * Attempt to find the word that contains the return |
| * stack address (as if it is part of a colon definition). |
| * If this works, also print the name of the word. |
| */ |
| if (ficlDictionaryIncludes(context->dictionary, cell->p)) { |
| ficlWord *word; |
| word = ficlDictionaryFindEnclosingWord(context->dictionary, |
| cell->p); |
| if (word) { |
| int offset = (ficlCell *)cell->p - &word->param[0]; |
| sprintf(buffer + strlen(buffer), ", %s + %d ", |
| word->name, offset); |
| } |
| } |
| strcat(buffer, "\n"); |
| ficlVmTextOut(context->vm, buffer); |
| return (FICL_TRUE); |
| } |
| |
| void |
| ficlVmDisplayReturnStack(ficlVm *vm) |
| { |
| struct stackContext context; |
| context.vm = vm; |
| context.count = 0; |
| context.dictionary = ficlVmGetDictionary(vm); |
| ficlStackDisplay(vm->returnStack, ficlReturnStackDisplayCallback, |
| &context); |
| } |
| |
| /* |
| * f o r g e t - w i d |
| */ |
| static void |
| ficlPrimitiveForgetWid(ficlVm *vm) |
| { |
| ficlDictionary *dictionary = ficlVmGetDictionary(vm); |
| ficlHash *hash; |
| |
| hash = (ficlHash *)ficlStackPopPointer(vm->dataStack); |
| ficlHashForget(hash, dictionary->here); |
| } |
| |
| /* |
| * f o r g e t |
| * TOOLS EXT ( "<spaces>name" -- ) |
| * Skip leading space delimiters. Parse name delimited by a space. |
| * Find name, then delete name from the dictionary along with all |
| * words added to the dictionary after name. An ambiguous |
| * condition exists if name cannot be found. |
| * |
| * If the Search-Order word set is present, FORGET searches the |
| * compilation word list. An ambiguous condition exists if the |
| * compilation word list is deleted. |
| */ |
| static void |
| ficlPrimitiveForget(ficlVm *vm) |
| { |
| void *where; |
| ficlDictionary *dictionary = ficlVmGetDictionary(vm); |
| ficlHash *hash = dictionary->compilationWordlist; |
| |
| ficlPrimitiveTick(vm); |
| where = ((ficlWord *)ficlStackPopPointer(vm->dataStack))->name; |
| ficlHashForget(hash, where); |
| dictionary->here = FICL_POINTER_TO_CELL(where); |
| } |
| |
| /* |
| * w o r d s |
| */ |
| #define nCOLWIDTH 8 |
| |
| static void |
| ficlPrimitiveWords(ficlVm *vm) |
| { |
| ficlDictionary *dictionary = ficlVmGetDictionary(vm); |
| ficlHash *hash = dictionary->wordlists[dictionary->wordlistCount - 1]; |
| ficlWord *wp; |
| int nChars = 0; |
| int len; |
| unsigned i; |
| int nWords = 0; |
| char *cp; |
| char *pPad; |
| int columns; |
| |
| cp = getenv("COLUMNS"); |
| /* |
| * using strtol for now. TODO: refactor number conversion from |
| * ficlPrimitiveToNumber() and use it instead. |
| */ |
| if (cp == NULL) |
| columns = 80; |
| else |
| columns = strtol(cp, NULL, 0); |
| |
| /* |
| * the pad is fixed size area, it's better to allocate |
| * dedicated buffer space to deal with custom terminal sizes. |
| */ |
| pPad = malloc(columns + 1); |
| if (pPad == NULL) |
| ficlVmThrowError(vm, "Error: out of memory"); |
| |
| pager_open(); |
| for (i = 0; i < hash->size; i++) { |
| for (wp = hash->table[i]; wp != NULL; wp = wp->link, nWords++) { |
| if (wp->length == 0) /* ignore :noname defs */ |
| continue; |
| |
| /* prevent line wrap due to long words */ |
| if (nChars + wp->length >= columns) { |
| pPad[nChars++] = '\n'; |
| pPad[nChars] = '\0'; |
| nChars = 0; |
| if (pager_output(pPad)) |
| goto pager_done; |
| } |
| |
| cp = wp->name; |
| nChars += sprintf(pPad + nChars, "%s", cp); |
| |
| if (nChars > columns - 10) { |
| pPad[nChars++] = '\n'; |
| pPad[nChars] = '\0'; |
| nChars = 0; |
| if (pager_output(pPad)) |
| goto pager_done; |
| } else { |
| len = nCOLWIDTH - nChars % nCOLWIDTH; |
| while (len-- > 0) |
| pPad[nChars++] = ' '; |
| } |
| |
| if (nChars > columns - 10) { |
| pPad[nChars++] = '\n'; |
| pPad[nChars] = '\0'; |
| nChars = 0; |
| if (pager_output(pPad)) |
| goto pager_done; |
| } |
| } |
| } |
| |
| if (nChars > 0) { |
| pPad[nChars++] = '\n'; |
| pPad[nChars] = '\0'; |
| nChars = 0; |
| ficlVmTextOut(vm, pPad); |
| } |
| |
| sprintf(pPad, "Dictionary: %d words, %ld cells used of %u total\n", |
| nWords, (long)(dictionary->here - dictionary->base), |
| dictionary->size); |
| pager_output(pPad); |
| |
| pager_done: |
| free(pPad); |
| pager_close(); |
| } |
| |
| /* |
| * l i s t E n v |
| * Print symbols defined in the environment |
| */ |
| static void |
| ficlPrimitiveListEnv(ficlVm *vm) |
| { |
| ficlDictionary *dictionary = vm->callback.system->environment; |
| ficlHash *hash = dictionary->forthWordlist; |
| ficlWord *word; |
| unsigned i; |
| int counter = 0; |
| |
| pager_open(); |
| for (i = 0; i < hash->size; i++) { |
| for (word = hash->table[i]; word != NULL; |
| word = word->link, counter++) { |
| sprintf(vm->pad, "%s\n", word->name); |
| if (pager_output(vm->pad)) |
| goto pager_done; |
| } |
| } |
| |
| sprintf(vm->pad, "Environment: %d words, %ld cells used of %u total\n", |
| counter, (long)(dictionary->here - dictionary->base), |
| dictionary->size); |
| pager_output(vm->pad); |
| |
| pager_done: |
| pager_close(); |
| } |
| |
| /* |
| * This word lists the parse steps in order |
| */ |
| void |
| ficlPrimitiveParseStepList(ficlVm *vm) |
| { |
| int i; |
| ficlSystem *system = vm->callback.system; |
| FICL_VM_ASSERT(vm, system); |
| |
| ficlVmTextOut(vm, "Parse steps:\n"); |
| ficlVmTextOut(vm, "lookup\n"); |
| |
| for (i = 0; i < FICL_MAX_PARSE_STEPS; i++) { |
| if (system->parseList[i] != NULL) { |
| ficlVmTextOut(vm, system->parseList[i]->name); |
| ficlVmTextOut(vm, "\n"); |
| } else |
| break; |
| } |
| } |
| |
| /* |
| * e n v C o n s t a n t |
| * Ficl interface to ficlSystemSetEnvironment and ficlSetEnvD - allow Ficl |
| * code to set environment constants... |
| */ |
| static void |
| ficlPrimitiveEnvConstant(ficlVm *vm) |
| { |
| unsigned value; |
| FICL_STACK_CHECK(vm->dataStack, 1, 0); |
| |
| ficlVmGetWordToPad(vm); |
| value = ficlStackPopUnsigned(vm->dataStack); |
| ficlDictionarySetConstant(ficlSystemGetEnvironment(vm->callback.system), |
| vm->pad, (ficlUnsigned)value); |
| } |
| |
| static void |
| ficlPrimitiveEnv2Constant(ficlVm *vm) |
| { |
| ficl2Integer value; |
| |
| FICL_STACK_CHECK(vm->dataStack, 2, 0); |
| |
| ficlVmGetWordToPad(vm); |
| value = ficlStackPop2Integer(vm->dataStack); |
| ficlDictionarySet2Constant( |
| ficlSystemGetEnvironment(vm->callback.system), vm->pad, value); |
| } |
| |
| |
| /* |
| * f i c l C o m p i l e T o o l s |
| * Builds wordset for debugger and TOOLS optional word set |
| */ |
| void |
| ficlSystemCompileTools(ficlSystem *system) |
| { |
| ficlDictionary *dictionary = ficlSystemGetDictionary(system); |
| ficlDictionary *environment = ficlSystemGetEnvironment(system); |
| |
| FICL_SYSTEM_ASSERT(system, dictionary); |
| FICL_SYSTEM_ASSERT(system, environment); |
| |
| |
| /* |
| * TOOLS and TOOLS EXT |
| */ |
| ficlDictionarySetPrimitive(dictionary, ".s", ficlVmDisplayDataStack, |
| FICL_WORD_DEFAULT); |
| ficlDictionarySetPrimitive(dictionary, ".s-simple", |
| ficlVmDisplayDataStackSimple, FICL_WORD_DEFAULT); |
| ficlDictionarySetPrimitive(dictionary, "bye", ficlPrimitiveBye, |
| FICL_WORD_DEFAULT); |
| ficlDictionarySetPrimitive(dictionary, "forget", ficlPrimitiveForget, |
| FICL_WORD_DEFAULT); |
| ficlDictionarySetPrimitive(dictionary, "see", ficlPrimitiveSee, |
| FICL_WORD_DEFAULT); |
| ficlDictionarySetPrimitive(dictionary, "words", ficlPrimitiveWords, |
| FICL_WORD_DEFAULT); |
| |
| /* |
| * Set TOOLS environment query values |
| */ |
| ficlDictionarySetConstant(environment, "tools", FICL_TRUE); |
| ficlDictionarySetConstant(environment, "tools-ext", FICL_FALSE); |
| |
| /* |
| * Ficl extras |
| */ |
| ficlDictionarySetPrimitive(dictionary, "r.s", ficlVmDisplayReturnStack, |
| FICL_WORD_DEFAULT); |
| ficlDictionarySetPrimitive(dictionary, ".env", ficlPrimitiveListEnv, |
| FICL_WORD_DEFAULT); |
| ficlDictionarySetPrimitive(dictionary, "env-constant", |
| ficlPrimitiveEnvConstant, FICL_WORD_DEFAULT); |
| ficlDictionarySetPrimitive(dictionary, "env-2constant", |
| ficlPrimitiveEnv2Constant, FICL_WORD_DEFAULT); |
| ficlDictionarySetPrimitive(dictionary, "debug-xt", ficlPrimitiveDebugXT, |
| FICL_WORD_DEFAULT); |
| ficlDictionarySetPrimitive(dictionary, "parse-order", |
| ficlPrimitiveParseStepList, FICL_WORD_DEFAULT); |
| ficlDictionarySetPrimitive(dictionary, "step-break", |
| ficlPrimitiveStepBreak, FICL_WORD_DEFAULT); |
| ficlDictionarySetPrimitive(dictionary, "forget-wid", |
| ficlPrimitiveForgetWid, FICL_WORD_DEFAULT); |
| ficlDictionarySetPrimitive(dictionary, "see-xt", ficlPrimitiveSeeXT, |
| FICL_WORD_DEFAULT); |
| |
| #if FICL_WANT_FLOAT |
| ficlDictionarySetPrimitive(dictionary, ".hash", |
| ficlPrimitiveHashSummary, FICL_WORD_DEFAULT); |
| #endif |
| } |