/** 
    Implements dictionary and wordlists.
  
    Copyright (C) Tektronix, Inc. 1998 - 2001. All rights reserved.
  
    @see     GNU LGPL
    @author  Tektronix CTE              @(#) %derived_by: guidod %
    @version %version: bln_mpt1!5.55 %
      (%date_modified: Wed Sep 11 13:17:53 2002 %)
 */
/*@{*/
#if defined(__version_control__) && defined(__GNUC__)
static char* id __attribute__((unused)) = 
"@(#) $Id: %full_filespec:  dict-sub.c~bln_mpt1!5.55:csrc:bln_12xx!1 % $";
#endif

#define _P4_SOURCE 1

#include <pfe/pfe-base.h>
#include <pfe/def-xtra.h>

#include <string.h>
#include <ctype.h>
#include <stddef.h> /*offsetof*/
#include <stdlib.h>

#include <pfe/def-comp.h>
#include <pfe/term-sub.h>
#include <pfe/lined.h>
#include <pfe/_missing.h>
#include <pfe/logging.h>

#define UPPERMAX 32
#define UPPERCOPY(_upper_, _nm_, _l_) \
        if ((_l_) < UPPERMAX)         \
        { memcpy ((_upper_), (_nm_), (_l_)); p4_upper ((_upper_), (_l_)); }  \
        else                          \
        { *(int*)(_upper_) = 0; }


/*
   A vocabulary is organized as a mixture between hash-table and
   linked list. (This is a practice you can observe in several
   systems.) It works like this: Given a name, first a hash-code is
   generated. This hash-code selects one of several linked lists
   called threads. The hooks to these threads are stored in a table.
  
   The body of a WORDLIST is essentially such a table of pointers to
   threads, while in FIG-Forth it was just a pointer to the one and
   only linked list a VOCABULARY consists of in FIG-Forth.
 */
_export int
p4_wl_hash (const char *s, int l)
/* s string, l length of string, returns hash-code for that name */
{
#  if 0 /*GUD* original by DUZ */
    int n = *s++ - '@';

    while (--l > 0)
        n = n * 37 + *s++ - '@';        /* a maybe-stupid hash function :-) */
    return n & (THREADS - 1);   /* i.e. modulo threads */

#  else /*GUD* more simpler, avoiding multiply */
    register char c = *s;

    while(--l > 0)
    
{ c += *s++; c ^= l; }
return c & (THREADS - 1); # endif }
/*
   If we want to traverse a WORDLIST in it's entirety, we must follow
   all threads simultaneously. The following definition eases this by
   locating the thread with the hook pointing to the highest memory
   location, assuming that this thread contains the latest definition
   entered in the given WORDLIST. For usage refer to the definition of
   WORDS.
  
   When following a wordlist using topmost, a copy of the word list
   must be made. Everytime the topmost item was processed it must be
   replaced by its successor in the linked list.
 */
/* find the thread with the latest word in the given word list */ _export p4char ** p4_topmost (p4_Wordl *w)
{
    int n = THREADS;
    p4char **p, **s = w->thread;

    for (p = s++; --n; s++)
        if (*s > *p)
            p = s;
    return p;
}
/* return the NFA of the latest definition in the CURRENT WORDLIST */
_export p4char * 
p4_latest (void) 
{
    return *p4_topmost (CURRENT);
}
/* --------------------------------
   word list and forget 
 */
/** 
   create a word list in the dictionary 
 */
_export p4_Wordl *
p4_make_wordlist (p4char* nfa)
{
    p4_Wordl *w = (Wordl *) DP; /* allocate word list in HERE */
    P4_INC (DP, Wordl);
    
    ZERO (w->thread);           /* initialize all threads to empty */
    w->nfa = nfa;               /* set name for the wordlist (if any) */
    w->flag = WORDL_FLAG;       /* init flags from global flags */
    w->prev = VOC_LINK;         /* chain word list in VOC-LINK */
    VOC_LINK = w;
    w->id = w->prev ? (w->prev->id << 1) : 1;
    if (w->flag & WORDL_CURRENT)
        w->also = CURRENT;      /* if WORDL_CURRENT, search also this */
    else
        w->also = 0;
    return w;
}
_export p4_Wordl *
p4_find_wordlist (const char* nm, int nmlen)
{
    auto p4char upper[UPPERMAX];
    p4_Wordl* wl;
    char* nfa;
    UPPERCOPY(upper, nm, nmlen)

    /* a special, since FORTH has no vocabulary_RT anymore */
    if (nmlen == 5 && ! memcmp (nm, "FORTH", 5))
        return PFE.forth_wl;
    if (nmlen == 11 && ! memcmp (nm, "ENVIRONMENT", 11))
        return PFE.environ_wl;

    for (wl = VOC_LINK; wl ; wl = wl->prev)
    
{
        if (! wl->nfa) continue;
        nfa = wl->nfa;
        if (NFACNT(*nfa) != nmlen) continue;
        if (!memcmp (nfa+1, nm, nmlen) || !memcmp (nfa+1, upper, nmlen))
            return wl;
    }
return 0; }
/** ((FORGET)) 
   remove words from dictionary, free dictionary space, this is the
   runtime helper of => (FORGET)
 */
FCode (p4_forget_dp)
{
    register p4_Wordl *wl;
    register p4char* new_dp = PFE.forget_dp;

    /* unchain words in all threads of all word lists: */
    for (wl = VOC_LINK; wl; wl = wl->prev)
    
{
        p4char **p = wl->thread;
        int i;
       
	if (0) if (wl->nfa) 
	    fprintf(stderr,"\"%.*s\"", NFACNT(*wl->nfa), wl->nfa+1);

        for (i = THREADS; --i >= 0; p++)
        
{  /* unchain words in thread: */
            while (*p >= new_dp) 
            
{
                if (PFE_IS_DESTROYER(*p))
                
{
                    P4_info2 (" destroy: \"%.*s\"", NFACNT(**p), *p+1);
                    p4_call (p4_name_from (*p));
                    new_dp = PFE.forget_dp; /* forget_dp is volatile */
                    /* and may have changed through recursive forget */
                }
*p = *p4_name_to_link (*p); }
}
}
/* unchain word lists: */ while (VOC_LINK && VOC_LINK >= (p4_Wordl *) new_dp)
{   
        
{   /* delete from search-order */   
            int i;
            for (i=0; i < PFE_set.wordlists; i++) 
            
{
                if (CONTEXT[i] == VOC_LINK) 
                
{
                    CONTEXT[i] = NULL;
                    if (! PFE.atexit_running)
                    
{
                        const p4char* nfa = VOC_LINK->nfa ? VOC_LINK->nfa 
                            : (const p4char*) "\1?";
                        P4_note3 ("deleted '%.*s' "
                                  "from context search-order [%i]", 
                                  NFACNT(*nfa), nfa+1, i);
                    }
}
if (PFE.dforder[i] == VOC_LINK)
{
                    PFE.dforder[i] = NULL;
                    if (! PFE.atexit_running)
                    
{
                        const p4char* nfa = VOC_LINK->nfa ? VOC_LINK->nfa 
                            : (const p4char*) "\1?";
                        P4_note3 ("deleted '%.*s' "
                                  "from default search-order [%i]", 
                                  NFACNT(*nfa), nfa+1, i);
                    }
}
}
}
VOC_LINK = VOC_LINK->prev; }
/* compact search-order */
{ register int i, j;
      for (i=0, j=0; i < PFE_set.wordlists; i++)
      
{
        if (CONTEXT[i]) CONTEXT[j++] = CONTEXT[i];
      }
while (j < PFE_set.wordlists) CONTEXT[j++] = NULL; for (i=0, j=0; i < PFE_set.wordlists; i++)
{
        if (PFE.dforder[i]) PFE.dforder[j++] = PFE.dforder[i];
      }
while (j < PFE_set.wordlists) PFE.dforder[j++] = NULL; }
/* free dictionary space: */ DP = (p4char *) new_dp; LAST = NULL; PFE.forget_dp = 0; if (CURRENT >= (p4_Wordl *) new_dp)
{
        if (CONTEXT[0]) CURRENT = PFE.forth_wl; /* initial CURRENT */
        if (! PFE.atexit_running)
            p4_throw (P4_ON_CURRENT_DELETED);  /* and still throw */
    }
}
/** (FORGET)
   forget anything above address
 */
_export void
p4_forget (char* above)
{
    if ((p4char*) above < FENCE)
        p4_throw (P4_ON_INVALID_FORGET);

    if (PFE.forget_dp) /* some p4_forget_dp already started */
    
{
        /* P4_info1 ("recursive forget %p", above); */
        if (PFE.forget_dp > above) 
        
{
            PFE.forget_dp = above; /* update p4_forget_dp argument */
        }
}
else
{ 
        /* P4_info1 ("forget start %p", above); */
        PFE.forget_dp = above; /* put new argument for p4_forget_dp */
        FX (p4_forget_dp);     /* forget execution start */
    }
}
FCode_RT (p4_destroyer_RT)
{   
    /* this code is a trampoline for ITC code not using an FFA flag.
     * we just expect the a prior p4_call in p4_forget has setup an
     * appropriate BODY pointer - either it goes through a p4WP or
     * indirectly through p4IP. All we have to do is, to fetch the
     * actual ccode from PFA[1] and branch down into the target code.
     */
#   if ! defined PFE_CALL_THREADING
    ((p4code*)(P4_WP_PFA))[1](FX_VOID);
#   elif ! defined PFE_SBR_CALL_THREADING /* not actually ever compiled */
    ((p4code*)(*IP))[1](FX_VOID); /* CTC, (*IP) would have the BODY-pointer */
#   else
    P4_fail (
        "you are not supposed to have a destroyer_RT in this configuration");
#   endif
}
P4RUNTIME1_RT(p4_destroyer);
/**
   create a destroyer word. Upon =>'FORGET' the code will will be
   run with the given argument. The structure is code/CFA and what/PFA.
 */
_export char*
p4_forget_word (const char *name, p4cell id, p4code ccode, p4cell what)
{
    char nm[255]; /* better as POCKET ? */
    sprintf (nm, name, id);

#  if defined PFE_WITH_FFA  && ! defined PFE_CALL_THREADING
    
{
	p4_header_comma (nm, strlen(nm), PFE.atexit_wl);
	*_FFA(LAST) |= (P4xIMMEDIATE|P4xONxDESTROY);
	FX_RCOMMA (ccode); /*cfa*/
	FX_VCOMMA (what);  /*pfa*/
    }
# elif ! defined PFE_CALL_THREADING
{
	p4_header_comma (nm, strlen(nm), PFE.atexit_wl);
	*_FFA(LAST) |= (P4xIMMEDIATE|P4xONxDESTROY);
	FX_RUNTIME1_RT (p4_destroyer);
	FX_VCOMMA (what); /*pfa*/
	FX_RCOMMA (ccode); /*pfa+1*/
    }
# else /* PFE_CALL_THREADING and up, p4_call uses INFO-block */
{
	p4Word* w = (p4xt) p4_DP;
	FX_RCOMMA ("~"); /* trampoline with body */
	FX_RCOMMA (ccode);
	p4_header_comma (nm, strlen(nm), PFE.atexit_wl);
	*_FFA(LAST) |= (P4xIMMEDIATE|P4xONxDESTROY);
	FX_PCOMMA (w);    /* cfa = word-comp-info */
	FX_VCOMMA (what); /* pfa */
    }
# endif return LAST; }
/* ------------------------------ 
   search a header 
 */
static char *
search_thread (const char *nm, int l, char *t, p4cell wl_flag )
{
    auto char upper[UPPERMAX];
    if (l > NFACNTMAX)
        return NULL;

# if P4_LOG /* additional sanity check */
    if (p4_LogMask & P4_LOG_DEBUG) /* if any debug level */
        if (t && !((char*)PFE.dict <= t && t <= (char*)PFE.dictlimit)) 
        
{ 
            P4_fail3 ("hashlink pointer invalid %p in search for '%.*s'", 
              t, l, nm);
        }
# endif if( LOWER_CASE && (wl_flag & WORDL_NOCASE) )
{   /* pfe 33.x will have this mode removed: */
        while (t)
        
{
            if ( !(*_FFA(t) & P4xSMUDGED) && NFACNT(*t) == l)
            
{
                if (!p4_strncmpi (nm, t+1, l))  break;
            }
t = *p4_name_to_link (t); }
if (t && (wl_flag & WORDL_UPPER_CASE) && memcmp (nm, t+1, l))
{   /* does not match case-sensitive */
            UPPERCOPY (upper, nm, l);
            if (memcmp (upper, t+1, l)) 
            
{  /* and neither the upper-case form */
#             if defined _K12_SOURCE && defined VXWORKS
                p4_outf ("(warning: input '%.*s' hits '%.*s': bad spelling?)", 
                          l, nm, l, t+1);
#             endif
                P4_warn4 ("oops, input '%.*s' hits '%.*s': bad spelling?", 
                          l, nm, l, t+1);
                P4_info4 ("- the input word '%.*s' might not match '%.*s'"
                          "in the future - please fix it now.", l, nm, l, t+1);
            }
}
}
else if( UPPER_CASE && (wl_flag & WORDL_UPPER_CASE) )
{   /* note: p4_match/p4_search_incomplete */
        UPPERCOPY (upper, nm, l);

        /* this thread does contain some upper-case defs 
           AND lower-case input shall match those definitions */
        while (t)
        
{
            if ( !(*_FFA(t) & P4xSMUDGED) && NFACNT(*t) == l)
            
{
                if (!memcmp (nm, t+1, l))  break;
                if (!memcmp (upper, t+1, l)) break;
            }
t = *p4_name_to_link (t); }
}
else
{
        /* input is case-sensitive OR vocabulary contains mixed-case defs */
        while (t)
        
{
            if ( !(*_FFA(t) & P4xSMUDGED) && NFACNT(*t) == l)
            
{
                if (!memcmp (nm, t+1, l))  break;
            }
t = *p4_name_to_link (t); }
}
return t; }
_export char *
p4_search_wordlist (const char *nm, int l, const p4_Wordl *w)
{
    if( w->flag & WORDL_NOHASH )
    
{ return search_thread (nm, l, w->thread[0], w->flag ); }
else
{ return search_thread (nm, l, w->thread[p4_wl_hash (nm, l)], w->flag ); }
}
/* search all word lists in the search order for name, return NFA 
   (we use the id speedup here - the first WLs have each a unique bitmask
    in the wl->id. Especially the FORTH wordlist can be present multiple
    time - even in being just search via wl->also. With w->id each is just
    searched once - atleast for each of the WLs that have gotten an id-bit
    which on a 32bit system are 32 WLs - enough for many system setups.
    It might be possible to use the old code even here (that walked the
    ORDER to see if the next WL is present in an earlier slot) but in a
    system with so many vocs it is quite improbable to find duplicates
    other than the basic vocs like FORTH in there anyway - so we use this
    one that might search a WL twice theoretically. Tell me of occasions
    where that is really a problem - in my setups it happens that the ORDER
    overflows much before getting duplicates other than the basic wordlists.
 */
_export char *
p4_find (const char *nm, int l)
{
    register Wordl **p;
    register Wordl *wordl;
    register char *w = NULL;
    register int n = p4_wl_hash (nm, l);
    register p4ucell searched = 0;
    
    for (p = CONTEXT; p <= &ONLY; p++)
    
{
        for (wordl = *p; wordl ; wordl=wordl->also)
	
{
	    if (searched&wordl->id)
		continue;
	    searched |= wordl->id;

            if( wordl->flag & WORDL_NOHASH )
                w = search_thread (nm, l, wordl->thread[0], wordl->flag );
            else
                w = search_thread (nm, l, wordl->thread[n], wordl->flag );

	    if (w) return w;
        }
}
return w; /*0*/ }
/**
   tick next word,  and
   return count byte pointer of name field (to detect immediacy)
 */
_export char *
p4_tick_nfa (void) 
{
    register char *p;

    p4_word_parseword (' '); *DP=0; /* PARSE-WORD-NOHERE */
    p = p4_find (PFE.word.ptr, PFE.word.len);
    if (! p)
        p4_throw (P4_ON_UNDEFINED);
    return p;
}
/**
   tick next word,  and return xt
 */
_export p4xt
p4_tick_cfa (void)
{
    return p4_name_from (p4_tick_nfa ());
}
#if 0
/**
   tick next word, store p4xt in xt, and
   return count byte pointer of name field (to detect immediacy)
 */
___export char *
p4_tick (p4xt *xt)
{
    register char *p = p4_tick_nfa ();
    *xt = p4_name_from (p);
    return p;
}
#endif
/* ---------------------------
   create a header 
 */
/* writes counted string into dictionary, returns address */
_export char *
p4_string_comma (const char *s, int len)
{
    char *p = (char *) DP;
    
    if (len >= (1 << CHAR_BIT))
        p4_throw (P4_ON_ARG_TYPE);
    *DP++ = len;                /* store count byte */
    while (--len >= 0)          /* store string */
        *DP++ = (p4char) *s++;
    FX (p4_align);
    return p;
}
/*
    char*                       |* use FX(p4_parse_comma_quote) !!! *|
    p4_parse_comma(char del)
   {
      p4_word_parse (del); |* PARSE-WORD-NOTHROW *|
      return p4_string_comma (PFE.word.ptr, (int) PFE.word.len);
   }
 */
/* ----------------------
   words with wildcards 
*/
/*
   Show words in word list matching pattern, and of one of the
   categories in string `categories'. NULL pointer or zero length
   string means all kinds of words.
 */
_export void
p4_wild_words (const p4_Wordl *wl, const char *pattern, const char *categories)
{
    p4char **t;
    /* Wordl wcopy = *wl;          // clobbered while following it */
    Wordl wcopy; memcpy (&wcopy, wl, sizeof(wcopy));

# ifndef WILD_TAB
# define WILD_TAB 26 /* traditional would be 20 (26*4=80), now 26*3=78 */
# endif

    FX (p4_cr);
    FX (p4_start_Q_cr);
    if (categories && *categories == '\0')
        categories = NULL;
    for (t = p4_topmost (&wcopy); *t; t = p4_topmost (&wcopy))
    
{
        char wbuf[NFACNTMAX+1];
        p4char *w = *t;
        p4char **s = p4_name_to_link (w);
        int l = NFACNT(*w++);
        p4_store_c_string (w, l, wbuf, sizeof wbuf);
        if (p4_match (pattern, wbuf, wl->flag & P4_UPPER_CASE_FLAGS))
        
{
	    char c = p4_category (*P4_TO_CODE(P4_LINK_FROM (s)));
            if (! categories || strchr (categories, c))
            
{
                if (p4_OUT+WILD_TAB - p4_OUT%WILD_TAB + 2 + l > p4_COLS ||
                    p4_OUT+WILD_TAB - p4_OUT%WILD_TAB + WILD_TAB*2/3 > p4_COLS)
                
{
                    if (p4_Q_cr ())
                        break;
                }
else
{
                    if (p4_OUT)
                        p4_tab (WILD_TAB);
                }
p4_outf ("%c %.*s ", c, l, w); }
}
*t = *s; }
}
#ifdef _use_pfe_old_style_search_incomplete
/* completion of word against dictionary */
static p4char *
search_incomplete (const char *nm, int l, Wordl *w)
/*
   traverses the entire given wordlist to find a matching word
   caution: clobbers *w. This is needed to be able to continue the search.
 */
{
    auto char upper[UPPERMAX];
    p4char **t, *s;

    if (w->flag & P4_UPPER_CASE_FLAGS)
    
{
        UPPERCOPY(upper, nm, l);
    }
for (t = p4_topmost (w); *t; t = p4_topmost (w))
{
        s = *t;
        *t = *p4_name_to_link (*t);

        if ( !(*_FFA(t) & P4xSMUDGED) && NFACNT(*s) >= l)
        
{
            if (NFACNT(*s) <= UPPERMAX) 
            
{ if (!memcmp (upper, s+1, l)) return s; }
if (! memcmp (nm, s+1, l)) return s; }
}
return NULL; }
/*
   Try to complete string in/len from dictionary.
   Store completion in out (asciiz), return number of possible completions.
   If display is true, display alternatives.
   (if (display && !len) { don't print 200 words, just the number })
 */
static int
p4_complete_word (const char *in, int len, char *out, int display)
{
    Wordl w, *wl, **p;
    char *s = NULL, *t = NULL;  
    int n = 0, m = 0, cnt = 0, searched_n = 0;
    Wordl* searched[32] = 
{0}
; for (p = CONTEXT; p <= &ONLY; p++)
{
        for (wl = *p; wl; wl = wl->also ) 
        
{
            for (n=0; n < searched_n; n++)
                if (wl == searched[n])
                    break;       /* continue at second outer for */
            if (wl == searched[n]) 
                continue;        /* must expressed like that in C*/
        
            if (searched_n < 32) 
                searched[searched_n++] = wl;
             
            for (w = *wl; (t = search_incomplete (in, len, &w)) != NULL; cnt++)
            
{
                if (display && len) 
                
{
                    FX (p4_space);
                    p4_type_on_line (t + 1, NFACNT(*t));
                }
if (! s)
{
                    s = t + 1;
                    m = NFACNT(*t);
                }
else
{
                    ++t;
                    for (n = 0; n < m; n++)
                        if (s[n] != t[n])
                            break;
                    m = n;
                }
}
}
}
if (cnt) p4_store_c_string (s, m, out, NFACNTMAX+1); if (display && !len)
{ p4_outf (" %i words ", cnt); }
return cnt; }
#else
static char *
search_thread_inco (const char *nm, int l, char *t, p4cell wl_flag )
{  /* compare with p4_search_thread */
    auto char upper[UPPERMAX];
    if (l > NFACNTMAX)
        return NULL;

    if( UPPER_CASE && (wl_flag & WORDL_UPPER_CASE) )
    
{   /* note: p4_match/p4_search_incomplete */
        UPPERCOPY (upper, nm, l);

        /* this thread does contain some upper-case defs 
           AND lower-case input shall match those definitions */
        while (t)
        
{
            if ( !(*_FFA(t) & P4xSMUDGED) && NFACNT(*t) >= l)
            
{
                if (!memcmp (nm, t+1, l))  break;
                if (!memcmp (upper, t+1, l)) break;
            }
t = *p4_name_to_link (t); }
}
else
{
        /* input is case-sensitive OR vocabulary contains mixed-case defs */
        while (t)
        
{
            if ( !(*_FFA(t) & P4xSMUDGED) && NFACNT(*t) >= l)
            
{
                if (!memcmp (nm, t+1, l))  break;
            }
t = *p4_name_to_link (t); }
}
return t; }
static char *
find_next_incomplete (const char *nm, int l, char* old)
{   /* compare with p4_find */
    register Wordl **p, **q;
    register Wordl *wordl;
    register char *w = NULL;
    register int n;
    register p4ucell searched = 0;
    
    for (p = CONTEXT; p <= &ONLY; p++)
    
{
        for (q = CONTEXT; q < p; q++)
        
{
            if (!*q) continue;
            if (*q == *p) goto continue_outer;
        }
for (wordl = *p; wordl ; wordl=wordl->also)
{
            /* we _must_ ensure that no world-thread is searched twice unlike
             * p4_find, so just skip any wordl that does not have an id for
             * the search_also case. Generally, we do not miss any word thus.
             */
            if (! wordl->id) 
                continue;  
	    if (searched & wordl->id)
		continue;
	    searched |= wordl->id;
            
            for (n = 0; n < THREADS; n++)
            
{
                w = wordl->thread[n];
                while (w)
                
{
                    w = search_thread_inco (nm, l, w, wordl->flag );

                    if (w) 
                    
{
                        if (! old)               /* search all wordl-threads */
                            return w;            /* until "old" is found     */
                        if (old == w)            /* then return the next nfa */
                            old = 0;             /* found after that one -   */
                        w = *p4_name_to_link(w); /* and if old-arg was null  */
                    }
/* it returns the first w */ }
/* while (w) */ }
}
continue_outer: continue; }
return w; /*0*/ }
/*
   Try to complete string in/len from dictionary.
   Store completion in out (asciiz), return number of possible completions.
   If display is true, display alternatives.
   (if (display && !len) { don't print 200 words, just the number })
 */
static int
p4_complete_word (const char *in, int len, char *out, int display)
{
    char *s = NULL, *t = NULL;  
    int n = 0, m = 0, cnt = 0;

    while ((t = find_next_incomplete(in, len, t)))
    
{
        cnt ++;
        if (display && len) 
        
{
            FX (p4_space);
            p4_type_on_line (t + 1, NFACNT(*t));
        }
if (! s)
{
            s = t + 1;
            m = NFACNT(*t);
        }
else
{
            for (n = 0; n < m; n++)
                if (s[n] != (t+1)[n])
                    break;
            m = n;
        }
}
if (cnt) p4_store_c_string (s, m, out, NFACNTMAX+1); if (display && !len)
{ p4_outf (" %i words ", cnt); }
return cnt; }
#endif
_export int
p4_complete_dictionary (char *in, char *out, int display)
{
    char *lw, buf[NFACNTMAX+1];
    int n;
    
    lw = strrchr (in, ' ');
    if (lw)
        lw++;
    else
        lw = in;
    memcpy (out, in, lw - in);
    n = p4_complete_word (lw, strlen (lw), buf, display);
    strcpy (&out[lw - in], buf);
    return n;
}
/* ------------------------------------------------------------------- */
/** ONLY ( -- )
   the only-vocabulary is special. Calling it will erase
   the search => ORDER of vocabularies and only allows
   to name some very basic vocabularies. Even => ALSO
   is not available.
 example:
   ONLY FORTH ALSO EXTENSIONS ALSO DEFINITIONS
 */
FCode (p4_only_RT)
{
    /* NO BODY_ADDR */
    memset(CONTEXT, 0, PFE_set.wordlists*sizeof(p4_Wordl*));
    CONTEXT[0] = CURRENT = ONLY;
}
P4RUNTIMES1_RT(p4_only, P4_ONLY_CODE1);
/** FORTH ( -- )
 : FORTH FORTH-WORDLIST CONTEXT ! ;
 */
FCode (p4_forth_RT)
{
    /* NO BODY_ADDR */
    CONTEXT[0] = PFE.forth_wl;
}
P4RUNTIMES1_RT(p4_forth, P4_ONLY_CODE1);
_export void
p4_preload_only (void)
{
    auto p4_Wordl only;                   /* scratch ONLY word list */
    
    DP = (p4char *) & PFE.dict[1];
  
    /* Load the ONLY word list to the scratch ONLY: */
    memset (&only, 0, sizeof only);
    /* # only.flag |= WORDL_NOHASH; */
    p4_header_comma ("ONLY", 4, &only ); FX_RUNTIME1_RT(p4_only);
    ONLY = p4_make_wordlist (LAST);
    /* # ONLY->flag |= WORDL_NOHASH; */
    COPY (ONLY->thread, only.thread);   /* Copy scratch ONLY to real ONLY */
    CURRENT = ONLY;

    /* FORTH -> [ANS] -> ONLY */
    p4_header_comma ("FORTH", 5, ONLY); FX_RUNTIME1_RT(p4_forth);
    PFE.forth_wl = p4_make_wordlist (LAST); 
    p4_header_comma ("[ANS]", 5, ONLY); FX_RUNTIME1(p4_vocabulary);
    FX_IMMEDIATE;
    PFE.forth_wl->also = p4_make_wordlist (LAST);
    PFE.forth_wl->also->also = ONLY;

    /* destroyers :: LOADED */
    p4_header_comma ("LOADED", 6, ONLY); FX_RUNTIME1(p4_vocabulary);
    PFE.atexit_wl = p4_make_wordlist (LAST); 
    PFE.atexit_wl->flag |= WORDL_NOHASH; /* see environment_dump in core.c */

    /* ENVIRONMENT -> LOADED */
    p4_header_comma ("ENVIRONMENT",11, ONLY); FX_RUNTIME1(p4_vocabulary);
    FX_IMMEDIATE;
    PFE.environ_wl = p4_make_wordlist (LAST);
    PFE.environ_wl->also = PFE.atexit_wl;
    PFE.environ_wl->flag |= WORDL_NOHASH;          /* for option-ext */
    PFE.environ_wl->thread[0] = PFE.set->opt.link; /* that goes here */
}
/*@}*/
/*
   Local variables:
   c-file-style: "stroustrup"
   End:
 */