/*******************************************************************1** d i c t . c2** Forth Inspired Command Language - dictionary methods3** Author: John Sadler ([email protected])4** Created: 19 July 19975** $Id: dict.c,v 1.14 2001/12/05 07:21:34 jsadler Exp $6*******************************************************************/7/*8** This file implements the dictionary -- FICL's model of9** memory management. All FICL words are stored in the10** dictionary. A word is a named chunk of data with its11** associated code. FICL treats all words the same, even12** precompiled ones, so your words become first-class13** extensions of the language. You can even define new14** control structures.15**16** 29 jun 1998 (sadler) added variable sized hash table support17*/18/*19** Copyright (c) 1997-2001 John Sadler ([email protected])20** All rights reserved.21**22** Get the latest Ficl release at http://ficl.sourceforge.net23**24** I am interested in hearing from anyone who uses ficl. If you have25** a problem, a success story, a defect, an enhancement request, or26** if you would like to contribute to the ficl release, please27** contact me by email at the address above.28**29** L I C E N S E and D I S C L A I M E R30**31** Redistribution and use in source and binary forms, with or without32** modification, are permitted provided that the following conditions33** are met:34** 1. Redistributions of source code must retain the above copyright35** notice, this list of conditions and the following disclaimer.36** 2. Redistributions in binary form must reproduce the above copyright37** notice, this list of conditions and the following disclaimer in the38** documentation and/or other materials provided with the distribution.39**40** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND41** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE42** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE43** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE44** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL45** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS46** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)47** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT48** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY49** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF50** SUCH DAMAGE.51*/525354#ifdef TESTMAIN55#include <stdio.h>56#include <ctype.h>57#else58#include <stand.h>59#endif60#include <string.h>61#include "ficl.h"6263/* Dictionary on-demand resizing control variables */64CELL dictThreshold;65CELL dictIncrease;666768static char *dictCopyName(FICL_DICT *pDict, STRINGINFO si);6970/**************************************************************************71d i c t A b o r t D e f i n i t i o n72** Abort a definition in process: reclaim its memory and unlink it73** from the dictionary list. Assumes that there is a smudged74** definition in process...otherwise does nothing.75** NOTE: this function is not smart enough to unlink a word that76** has been successfully defined (ie linked into a hash). It77** only works for defs in process. If the def has been unsmudged,78** nothing happens.79**************************************************************************/80void dictAbortDefinition(FICL_DICT *pDict)81{82FICL_WORD *pFW;83ficlLockDictionary(TRUE);84pFW = pDict->smudge;8586if (pFW->flags & FW_SMUDGE)87pDict->here = (CELL *)pFW->name;8889ficlLockDictionary(FALSE);90return;91}929394/**************************************************************************95a l i g n P t r96** Aligns the given pointer to FICL_ALIGN address units.97** Returns the aligned pointer value.98**************************************************************************/99void *alignPtr(void *ptr)100{101#if FICL_ALIGN > 0102char *cp;103CELL c;104cp = (char *)ptr + FICL_ALIGN_ADD;105c.p = (void *)cp;106c.u = c.u & (~FICL_ALIGN_ADD);107ptr = (CELL *)c.p;108#endif109return ptr;110}111112113/**************************************************************************114d i c t A l i g n115** Align the dictionary's free space pointer116**************************************************************************/117void dictAlign(FICL_DICT *pDict)118{119pDict->here = alignPtr(pDict->here);120}121122123/**************************************************************************124d i c t A l l o t125** Allocate or remove n chars of dictionary space, with126** checks for underrun and overrun127**************************************************************************/128int dictAllot(FICL_DICT *pDict, int n)129{130char *cp = (char *)pDict->here;131#if FICL_ROBUST132if (n > 0)133{134if ((unsigned)n <= dictCellsAvail(pDict) * sizeof (CELL))135cp += n;136else137return 1; /* dict is full */138}139else140{141n = -n;142if ((unsigned)n <= dictCellsUsed(pDict) * sizeof (CELL))143cp -= n;144else /* prevent underflow */145cp -= dictCellsUsed(pDict) * sizeof (CELL);146}147#else148cp += n;149#endif150pDict->here = PTRtoCELL cp;151return 0;152}153154155/**************************************************************************156d i c t A l l o t C e l l s157** Reserve space for the requested number of cells in the158** dictionary. If nCells < 0 , removes space from the dictionary.159**************************************************************************/160int dictAllotCells(FICL_DICT *pDict, int nCells)161{162#if FICL_ROBUST163if (nCells > 0)164{165if (nCells <= dictCellsAvail(pDict))166pDict->here += nCells;167else168return 1; /* dict is full */169}170else171{172nCells = -nCells;173if (nCells <= dictCellsUsed(pDict))174pDict->here -= nCells;175else /* prevent underflow */176pDict->here -= dictCellsUsed(pDict);177}178#else179pDict->here += nCells;180#endif181return 0;182}183184185/**************************************************************************186d i c t A p p e n d C e l l187** Append the specified cell to the dictionary188**************************************************************************/189void dictAppendCell(FICL_DICT *pDict, CELL c)190{191*pDict->here++ = c;192return;193}194195196/**************************************************************************197d i c t A p p e n d C h a r198** Append the specified char to the dictionary199**************************************************************************/200void dictAppendChar(FICL_DICT *pDict, char c)201{202char *cp = (char *)pDict->here;203*cp++ = c;204pDict->here = PTRtoCELL cp;205return;206}207208209/**************************************************************************210d i c t A p p e n d W o r d211** Create a new word in the dictionary with the specified212** name, code, and flags. Name must be NULL-terminated.213**************************************************************************/214FICL_WORD *dictAppendWord(FICL_DICT *pDict,215char *name,216FICL_CODE pCode,217UNS8 flags)218{219STRINGINFO si;220SI_SETLEN(si, strlen(name));221SI_SETPTR(si, name);222return dictAppendWord2(pDict, si, pCode, flags);223}224225226/**************************************************************************227d i c t A p p e n d W o r d 2228** Create a new word in the dictionary with the specified229** STRINGINFO, code, and flags. Does not require a NULL-terminated230** name.231**************************************************************************/232FICL_WORD *dictAppendWord2(FICL_DICT *pDict,233STRINGINFO si,234FICL_CODE pCode,235UNS8 flags)236{237FICL_COUNT len = (FICL_COUNT)SI_COUNT(si);238char *pName;239FICL_WORD *pFW;240241ficlLockDictionary(TRUE);242243/*244** NOTE: dictCopyName advances "here" as a side-effect.245** It must execute before pFW is initialized.246*/247pName = dictCopyName(pDict, si);248pFW = (FICL_WORD *)pDict->here;249pDict->smudge = pFW;250pFW->hash = hashHashCode(si);251pFW->code = pCode;252pFW->flags = (UNS8)(flags | FW_SMUDGE);253pFW->nName = (char)len;254pFW->name = pName;255/*256** Point "here" to first cell of new word's param area...257*/258pDict->here = pFW->param;259260if (!(flags & FW_SMUDGE))261dictUnsmudge(pDict);262263ficlLockDictionary(FALSE);264return pFW;265}266267268/**************************************************************************269d i c t A p p e n d U N S270** Append the specified FICL_UNS to the dictionary271**************************************************************************/272void dictAppendUNS(FICL_DICT *pDict, FICL_UNS u)273{274*pDict->here++ = LVALUEtoCELL(u);275return;276}277278279/**************************************************************************280d i c t C e l l s A v a i l281** Returns the number of empty cells left in the dictionary282**************************************************************************/283int dictCellsAvail(FICL_DICT *pDict)284{285return pDict->size - dictCellsUsed(pDict);286}287288289/**************************************************************************290d i c t C e l l s U s e d291** Returns the number of cells consumed in the dicionary292**************************************************************************/293int dictCellsUsed(FICL_DICT *pDict)294{295return pDict->here - pDict->dict;296}297298299/**************************************************************************300d i c t C h e c k301** Checks the dictionary for corruption and throws appropriate302** errors.303** Input: +n number of ADDRESS UNITS (not Cells) proposed to allot304** -n number of ADDRESS UNITS proposed to de-allot305** 0 just do a consistency check306**************************************************************************/307void dictCheck(FICL_DICT *pDict, FICL_VM *pVM, int n)308{309if ((n >= 0) && (dictCellsAvail(pDict) * (int)sizeof(CELL) < n))310{311vmThrowErr(pVM, "Error: dictionary full");312}313314if ((n <= 0) && (dictCellsUsed(pDict) * (int)sizeof(CELL) < -n))315{316vmThrowErr(pVM, "Error: dictionary underflow");317}318319if (pDict->nLists > FICL_DEFAULT_VOCS)320{321dictResetSearchOrder(pDict);322vmThrowErr(pVM, "Error: search order overflow");323}324else if (pDict->nLists < 0)325{326dictResetSearchOrder(pDict);327vmThrowErr(pVM, "Error: search order underflow");328}329330return;331}332333334/**************************************************************************335d i c t C o p y N a m e336** Copy up to nFICLNAME characters of the name specified by si into337** the dictionary starting at "here", then NULL-terminate the name,338** point "here" to the next available byte, and return the address of339** the beginning of the name. Used by dictAppendWord.340** N O T E S :341** 1. "here" is guaranteed to be aligned after this operation.342** 2. If the string has zero length, align and return "here"343**************************************************************************/344static char *dictCopyName(FICL_DICT *pDict, STRINGINFO si)345{346char *oldCP = (char *)pDict->here;347char *cp = oldCP;348char *name = SI_PTR(si);349int i = SI_COUNT(si);350351if (i == 0)352{353dictAlign(pDict);354return (char *)pDict->here;355}356357if (i > nFICLNAME)358i = nFICLNAME;359360for (; i > 0; --i)361{362*cp++ = *name++;363}364365*cp++ = '\0';366367pDict->here = PTRtoCELL cp;368dictAlign(pDict);369return oldCP;370}371372373/**************************************************************************374d i c t C r e a t e375** Create and initialize a dictionary with the specified number376** of cells capacity, and no hashing (hash size == 1).377**************************************************************************/378FICL_DICT *dictCreate(unsigned nCells)379{380return dictCreateHashed(nCells, 1);381}382383384FICL_DICT *dictCreateHashed(unsigned nCells, unsigned nHash)385{386FICL_DICT *pDict;387size_t nAlloc;388389nAlloc = sizeof (FICL_HASH) + nCells * sizeof (CELL)390+ (nHash - 1) * sizeof (FICL_WORD *);391392pDict = ficlMalloc(sizeof (FICL_DICT));393assert(pDict);394memset(pDict, 0, sizeof (FICL_DICT));395pDict->dict = ficlMalloc(nAlloc);396assert(pDict->dict);397398pDict->size = nCells;399dictEmpty(pDict, nHash);400return pDict;401}402403404/**************************************************************************405d i c t C r e a t e W o r d l i s t406** Create and initialize an anonymous wordlist407**************************************************************************/408FICL_HASH *dictCreateWordlist(FICL_DICT *dp, int nBuckets)409{410FICL_HASH *pHash;411412dictAlign(dp);413pHash = (FICL_HASH *)dp->here;414dictAllot(dp, sizeof (FICL_HASH)415+ (nBuckets-1) * sizeof (FICL_WORD *));416417pHash->size = nBuckets;418hashReset(pHash);419return pHash;420}421422423/**************************************************************************424d i c t D e l e t e425** Free all memory allocated for the given dictionary426**************************************************************************/427void dictDelete(FICL_DICT *pDict)428{429assert(pDict);430ficlFree(pDict);431return;432}433434435/**************************************************************************436d i c t E m p t y437** Empty the dictionary, reset its hash table, and reset its search order.438** Clears and (re-)creates the hash table with the size specified by nHash.439**************************************************************************/440void dictEmpty(FICL_DICT *pDict, unsigned nHash)441{442FICL_HASH *pHash;443444pDict->here = pDict->dict;445446dictAlign(pDict);447pHash = (FICL_HASH *)pDict->here;448dictAllot(pDict,449sizeof (FICL_HASH) + (nHash - 1) * sizeof (FICL_WORD *));450451pHash->size = nHash;452hashReset(pHash);453454pDict->pForthWords = pHash;455pDict->smudge = NULL;456dictResetSearchOrder(pDict);457return;458}459460461/**************************************************************************462d i c t H a s h S u m m a r y463** Calculate a figure of merit for the dictionary hash table based464** on the average search depth for all the words in the dictionary,465** assuming uniform distribution of target keys. The figure of merit466** is the ratio of the total search depth for all keys in the table467** versus a theoretical optimum that would be achieved if the keys468** were distributed into the table as evenly as possible.469** The figure would be worse if the hash table used an open470** addressing scheme (i.e. collisions resolved by searching the471** table for an empty slot) for a given size table.472**************************************************************************/473#if FICL_WANT_FLOAT474void dictHashSummary(FICL_VM *pVM)475{476FICL_DICT *dp = vmGetDict(pVM);477FICL_HASH *pFHash;478FICL_WORD **pHash;479unsigned size;480FICL_WORD *pFW;481unsigned i;482int nMax = 0;483int nWords = 0;484int nFilled;485double avg = 0.0;486double best;487int nAvg, nRem, nDepth;488489dictCheck(dp, pVM, 0);490491pFHash = dp->pSearch[dp->nLists - 1];492pHash = pFHash->table;493size = pFHash->size;494nFilled = size;495496for (i = 0; i < size; i++)497{498int n = 0;499pFW = pHash[i];500501while (pFW)502{503++n;504++nWords;505pFW = pFW->link;506}507508avg += (double)(n * (n+1)) / 2.0;509510if (n > nMax)511nMax = n;512if (n == 0)513--nFilled;514}515516/* Calc actual avg search depth for this hash */517avg = avg / nWords;518519/* Calc best possible performance with this size hash */520nAvg = nWords / size;521nRem = nWords % size;522nDepth = size * (nAvg * (nAvg+1))/2 + (nAvg+1)*nRem;523best = (double)nDepth/nWords;524525sprintf(pVM->pad,526"%d bins, %2.0f%% filled, Depth: Max=%d, Avg=%2.1f, Best=%2.1f, Score: %2.0f%%",527size,528(double)nFilled * 100.0 / size, nMax,529avg,530best,531100.0 * best / avg);532533ficlTextOut(pVM, pVM->pad, 1);534535return;536}537#endif538539/**************************************************************************540d i c t I n c l u d e s541** Returns TRUE iff the given pointer is within the address range of542** the dictionary.543**************************************************************************/544int dictIncludes(FICL_DICT *pDict, void *p)545{546return ((p >= (void *) &pDict->dict)547&& (p < (void *)(&pDict->dict + pDict->size))548);549}550551/**************************************************************************552d i c t L o o k u p553** Find the FICL_WORD that matches the given name and length.554** If found, returns the word's address. Otherwise returns NULL.555** Uses the search order list to search multiple wordlists.556**************************************************************************/557FICL_WORD *dictLookup(FICL_DICT *pDict, STRINGINFO si)558{559FICL_WORD *pFW = NULL;560FICL_HASH *pHash;561int i;562UNS16 hashCode = hashHashCode(si);563564assert(pDict);565566ficlLockDictionary(1);567568for (i = (int)pDict->nLists - 1; (i >= 0) && (!pFW); --i)569{570pHash = pDict->pSearch[i];571pFW = hashLookup(pHash, si, hashCode);572}573574ficlLockDictionary(0);575return pFW;576}577578579/**************************************************************************580f i c l L o o k u p L o c581** Same as dictLookup, but looks in system locals dictionary first...582** Assumes locals dictionary has only one wordlist...583**************************************************************************/584#if FICL_WANT_LOCALS585FICL_WORD *ficlLookupLoc(FICL_SYSTEM *pSys, STRINGINFO si)586{587FICL_WORD *pFW = NULL;588FICL_DICT *pDict = pSys->dp;589FICL_HASH *pHash = ficlGetLoc(pSys)->pForthWords;590int i;591UNS16 hashCode = hashHashCode(si);592593assert(pHash);594assert(pDict);595596ficlLockDictionary(1);597/*598** check the locals dict first...599*/600pFW = hashLookup(pHash, si, hashCode);601602/*603** If no joy, (!pFW) --------------------------v604** iterate over the search list in the main dict605*/606for (i = (int)pDict->nLists - 1; (i >= 0) && (!pFW); --i)607{608pHash = pDict->pSearch[i];609pFW = hashLookup(pHash, si, hashCode);610}611612ficlLockDictionary(0);613return pFW;614}615#endif616617618/**************************************************************************619d i c t R e s e t S e a r c h O r d e r620** Initialize the dictionary search order list to sane state621**************************************************************************/622void dictResetSearchOrder(FICL_DICT *pDict)623{624assert(pDict);625pDict->pCompile = pDict->pForthWords;626pDict->nLists = 1;627pDict->pSearch[0] = pDict->pForthWords;628return;629}630631632/**************************************************************************633d i c t S e t F l a g s634** Changes the flags field of the most recently defined word:635** Set all bits that are ones in the set parameter, clear all bits636** that are ones in the clr parameter. Clear wins in case the same bit637** is set in both parameters.638**************************************************************************/639void dictSetFlags(FICL_DICT *pDict, UNS8 set, UNS8 clr)640{641assert(pDict->smudge);642pDict->smudge->flags |= set;643pDict->smudge->flags &= ~clr;644return;645}646647648/**************************************************************************649d i c t S e t I m m e d i a t e650** Set the most recently defined word as IMMEDIATE651**************************************************************************/652void dictSetImmediate(FICL_DICT *pDict)653{654assert(pDict->smudge);655pDict->smudge->flags |= FW_IMMEDIATE;656return;657}658659660/**************************************************************************661d i c t U n s m u d g e662** Completes the definition of a word by linking it663** into the main list664**************************************************************************/665void dictUnsmudge(FICL_DICT *pDict)666{667FICL_WORD *pFW = pDict->smudge;668FICL_HASH *pHash = pDict->pCompile;669670assert(pHash);671assert(pFW);672/*673** :noname words never get linked into the list...674*/675if (pFW->nName > 0)676hashInsertWord(pHash, pFW);677pFW->flags &= ~(FW_SMUDGE);678return;679}680681682/**************************************************************************683d i c t W h e r e684** Returns the value of the HERE pointer -- the address685** of the next free cell in the dictionary686**************************************************************************/687CELL *dictWhere(FICL_DICT *pDict)688{689return pDict->here;690}691692693/**************************************************************************694h a s h F o r g e t695** Unlink all words in the hash that have addresses greater than or696** equal to the address supplied. Implementation factor for FORGET697** and MARKER.698**************************************************************************/699void hashForget(FICL_HASH *pHash, void *where)700{701FICL_WORD *pWord;702unsigned i;703704assert(pHash);705assert(where);706707for (i = 0; i < pHash->size; i++)708{709pWord = pHash->table[i];710711while ((void *)pWord >= where)712{713pWord = pWord->link;714}715716pHash->table[i] = pWord;717}718719return;720}721722723/**************************************************************************724h a s h H a s h C o d e725**726** Generate a 16 bit hashcode from a character string using a rolling727** shift and add stolen from PJ Weinberger of Bell Labs fame. Case folds728** the name before hashing it...729** N O T E : If string has zero length, returns zero.730**************************************************************************/731UNS16 hashHashCode(STRINGINFO si)732{733/* hashPJW */734UNS8 *cp;735UNS16 code = (UNS16)si.count;736UNS16 shift = 0;737738if (si.count == 0)739return 0;740741/* changed to run without errors under Purify -- lch */742for (cp = (UNS8 *)si.cp; si.count && *cp; cp++, si.count--)743{744code = (UNS16)((code << 4) + tolower(*cp));745shift = (UNS16)(code & 0xf000);746if (shift)747{748code ^= (UNS16)(shift >> 8);749code ^= (UNS16)shift;750}751}752753return (UNS16)code;754}755756757758759/**************************************************************************760h a s h I n s e r t W o r d761** Put a word into the hash table using the word's hashcode as762** an index (modulo the table size).763**************************************************************************/764void hashInsertWord(FICL_HASH *pHash, FICL_WORD *pFW)765{766FICL_WORD **pList;767768assert(pHash);769assert(pFW);770771if (pHash->size == 1)772{773pList = pHash->table;774}775else776{777pList = pHash->table + (pFW->hash % pHash->size);778}779780pFW->link = *pList;781*pList = pFW;782return;783}784785786/**************************************************************************787h a s h L o o k u p788** Find a name in the hash table given the hashcode and text of the name.789** Returns the address of the corresponding FICL_WORD if found,790** otherwise NULL.791** Note: outer loop on link field supports inheritance in wordlists.792** It's not part of ANS Forth - ficl only. hashReset creates wordlists793** with NULL link fields.794**************************************************************************/795FICL_WORD *hashLookup(FICL_HASH *pHash, STRINGINFO si, UNS16 hashCode)796{797FICL_UNS nCmp = si.count;798FICL_WORD *pFW;799UNS16 hashIdx;800801if (nCmp > nFICLNAME)802nCmp = nFICLNAME;803804for (; pHash != NULL; pHash = pHash->link)805{806if (pHash->size > 1)807hashIdx = (UNS16)(hashCode % pHash->size);808else /* avoid the modulo op for single threaded lists */809hashIdx = 0;810811for (pFW = pHash->table[hashIdx]; pFW; pFW = pFW->link)812{813if ( (pFW->nName == si.count)814&& (!strincmp(si.cp, pFW->name, nCmp)) )815return pFW;816#if FICL_ROBUST817assert(pFW != pFW->link);818#endif819}820}821822return NULL;823}824825826/**************************************************************************827h a s h R e s e t828** Initialize a FICL_HASH to empty state.829**************************************************************************/830void hashReset(FICL_HASH *pHash)831{832unsigned i;833834assert(pHash);835836for (i = 0; i < pHash->size; i++)837{838pHash->table[i] = NULL;839}840841pHash->link = NULL;842pHash->name = NULL;843return;844}845846/**************************************************************************847d i c t C h e c k T h r e s h o l d848** Verify if an increase in the dictionary size is warranted, and do it if849** so.850**************************************************************************/851852void dictCheckThreshold(FICL_DICT* dp)853{854if( dictCellsAvail(dp) < dictThreshold.u ) {855dp->dict = ficlMalloc( dictIncrease.u * sizeof (CELL) );856assert(dp->dict);857dp->here = dp->dict;858dp->size = dictIncrease.u;859dictAlign(dp);860}861}862863864865