/** 
   --  Subroutines for the Internal Forth-System
   
    Copyright (C) Tektronix, Inc. 1998 - 2001. All rights reserved.
  
    @see     GNU LGPL
    @author  Tektronix CTE            @(#) %derived_by: guidod %
    @version %version: bln_mpt1!1.48 %
      (%date_modified: Wed Jul 24 11:37:14 2002 %)
 */
/*@{*/
#if defined(__version_control__) && defined(__GNUC__)
static char* id __attribute__((unused)) = 
"@(#) $Id: %full_filespec:  engine-sub.c~bln_mpt1!1.48:csrc:bln_12xx!1 % $";
#endif

#define _P4_SOURCE 1

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

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <setjmp.h>

#ifdef _K12_SOURCE
#include <pfe/main-k12.h>
#endif

#include <pfe/option-ext.h>
#include <pfe/double-sub.h>
#include <pfe/debug-ext.h>
#include <pfe/block-sub.h>
#include <pfe/file-sub.h>
#include <pfe/term-sub.h>
#include <pfe/_missing.h>
#include <pfe/exception-sub.h>
#include <pfe/chainlist-ext.h>

#include <pfe/logging.h>

FCode(p4_noop)
{
    /* well, nothing... */
}
/* ********************************************************************** 
   inner and outer interpreter						  
 */
#ifndef PFE_SBR_CALL_THREADING
/* jumpbuf is a jmp_buf enanced by space to preserve variables that
   may be stored in cpu register, would be the Forth VM
 */
typedef struct	
{
    jmp_buf jmp;
# ifdef P4_REGRP
    p4xcode **rp;
# endif
# ifdef P4_REGSP
    p4cell *sp;
# endif
# ifdef P4_REGLP
    p4cell *lp;
# endif
# ifdef P4_REGFP
    double *fp;
# endif
}
jumpbuf;
/**
   longjmp via (jumpbuf*) following inline
   - purpose: stop the inner interpreter
 */
FCode_XE (p4_jump)	
{   FX_USE_CODE_ADDR 
{
    jumpbuf *buf = (jumpbuf *) *IP;

# ifdef P4_REGRP		/* save global register variables */
    buf->rp = RP;
# endif
# ifdef P4_REGSP
    buf->sp = SP;
# endif
# ifdef P4_REGLP
    buf->lp = LP;
# endif
# ifndef P4_NO_FP
# ifdef P4_REGFP
    buf->fp = FP;
# endif
# endif
    longjmp (buf->jmp, 1);
    /*FX_USE_CODE_EXIT;*/
}
}
#endif /* ! SBR_THREADING */
/**
   Run a forth word from within C-code
   - this is the inner interpreter
 */
_export void
p4_run_forth (p4xt xt)
{
#if defined PFE_SBR_CALL_THREADING
    p4_sbr_call (xt);
    return;
#else

    jumpbuf stop;

#  if ! defined PFE_CALL_THREADING
    static p4code jump_p = PFX (p4_jump);
    p4xcode list[3];
    list[0] = xt;
    list[1] = &jump_p;
    list[2] = (p4xcode) &stop;
#  else
    /* sbr-stub, xt-code, xt-data, sbr-stub, jump-code, jump-data */
    p4xcode list[6] /* = { 0,0,0,0,0,0 } */ ;
    *p4_compile_xcode(
	p4_compile_comma(
	    list,
	    xt),
	PFX(p4_jump)) = (p4xcode) &stop;
#  endif

    IP = list;
#  if !defined P4_WP_VIA_IP && !defined PFE_CALL_THREADING
    p4WP = *IP;
#  endif

    if (setjmp (stop.jmp))
    
{
#     ifdef P4_REGRP		/* restore global register variables */
        RP = stop.rp;		/* clobbered by longjmp() */
#     endif
#     ifdef P4_REGSP
        SP = stop.sp;
#     endif
#     ifdef P4_REGLP
        LP = stop.lp;
#     endif
#     ifndef P4_NO_FP
#     ifdef P4_REGFP
        FP = stop.fp;
#     endif
#     endif
        return;
    }
/* next_loop */ for (;;)
{
#    ifdef PFE_CALL_THREADING
#      define NVAR register p4code c;
#      define NEXT c = *IP++, (c)()
#    elif defined P4_WP_VIA_IP
#      define NVAR register p4xt w;
#      define NEXT w = *IP++, (*w) ()	/* ip is register but p4WP isn't */
#    else
#      define NVAR
#      define NEXT p4WP = *IP++, (*p4WP) ()	
				/* ip and p4WP are same: register or not */
#    endif

        NVAR;
#     ifdef UNROLL_NEXT		/* USER-CONFIG: if it helps */
        NEXT; NEXT; NEXT; NEXT;	/* do a little loop unrolling for speed */
        NEXT; NEXT; NEXT; NEXT;
#     else
        NEXT;			/* on some machines it doesn't do any good */
#     endif
    }
#endif /* ! PFE_SBR_CALL_THREADING */ }
/**
 */
_export void
p4_call (p4xt xt)
{
# if 0 && defined __target_os_sunos
    void *saved_ip;

    saved_ip = IP;
    printf ("%X/%X\n", IP, saved_ip);
    p4_run_forth (xt);
    printf ("%X/%X\n", IP, saved_ip);
    IP = saved_ip;
    printf ("%X/%X\n\n", IP, saved_ip);
# elif !defined PFE_SBR_CALL_THREADING
    p4xcode *saved_ip = IP;
    p4_run_forth (xt);
    IP = saved_ip;
# else
    p4_sbr_call (xt);
# endif
}
/**
   the NEXT call. Can be replaced by p4_debug_execute to
   trace the inner forth interpreter.
 */
_export void
p4_normal_execute (p4xt xt)
{
#  if defined PFE_SBR_CALL_THREADING /*FIXME: BODY / CODE ADDR needed? */
    (*p4_to_code(xt))();
#  else
    p4_call(xt);
#  endif
}
/**
   quick execute - unsafe and slow and simple
  
   use this routine for callbacks that might go through some forth
   colon-routines - that code shall not THROW or do some other nifty
   tricks with the return-stack or the inner interpreter. 
   Just simple things - use only for primitives or colon-routines,
   nothing curried with a DOES part in SBR-threading or sth. like that.
 */
_export void
p4_simple_execute (p4xt xt)
{
#  if defined PFE_SBR_CALL_THREADING /*FIXME: BODY / CODE ADDR needed? */
    (*p4_to_code(xt))();
#  elif defined PFE_CALL_THREADING 
    P4_REGIP_T ip = IP;
    P4_REGRP_T rp = RP; 
    p4xcode body = (p4xcode) P4_TO_BODY(xt);
    IP = &body; /* fake the body-field, just in case it is needed */
    (*p4_to_code(xt))();
    while (RP < rp) 
{ NVAR; NEXT; }
IP = ip; # else /* ITC: */ P4_REGIP_T ip = IP; P4_REGRP_T rp = RP; IP = & xt; do
{ NVAR; NEXT; }
while (RP < rp); IP = ip; # endif }
/* ================= INTERPRET =================== */
static p4ucell
FXCode (p4_interpret_find) /* hereclean */
{
    register p4char *s;
    register p4xt xt;

    /* WORD-string is at HERE and at PFE.word.ptr / PFE.word.len */
    s = p4_find (PFE.word.ptr, PFE.word.len);
    if (! s) return (p4cell) s; /* quick path, even alias null returncode */

    xt = p4_name_from (s);
    if (! STATE || *_FFA(s) & P4xIMMEDIATE)
    
{
	p4_call (xt);           /* execute it now */
	FX (p4_Q_stack);        /* check stack */
    }
else
{
	FX_COMPILE_COMMA (xt);  /* comma token */
    }
return 1; }
static p4ucell
FXCode (p4_interpret_number) /* hereclean */
{
    p4dcell d;

    /* WORD-string is at HERE and at PFE.word.ptr / PFE.word.len */
    if (! p4_number_question (PFE.word.ptr, PFE.word.len, &d))
	return 0; /* quick path */

    if (STATE)
    
{
	if (p4_DPL >= 0) 
	
{   
	    FX_COMPILE (p4_two_literal);
	    FX_COMMA_ (d.hi,'D');
            FX_COMMA_ (d.lo,'d');
	}
else
{
	    FX_COMPILE (p4_literal);
            FX_SCOMMA (d.lo);
	}
}
else
{
	*--SP = d.lo;
	if (p4_DPL >= 0) 
	    *--SP = d.hi;
    }
return 1; }
/**
   the => INTERPRET as called by the outer interpreter
 */
FCode (p4_interpret)
{
    register int i;

    /* HACK: until proper initialization bindings, we do init'
     * the interpret-vectors right in here. This *will* go away.
     */
    if (! PFE.interpret[3])
    
{
	/* PFE.interpret[6] = PFX (p4_interpret_dstrings); */
	/* PFE.interpret[5] = PFX (p4_interpret_locals); */
	PFE.interpret[4] = PFX (p4_interpret_find);
	PFE.interpret[3] = PFX (p4_interpret_number);
	/* PFE.interpret[2] = PFX (p4_interpret_float); */
	/* PFE.interpret[1] = PFX (p4_interpret_smart); */
    }
PFE.last_here = PFE.dp; for (;;)
{
    again:
        for (;;)
        
{
	    /* the parsed string is in PFE.word.ptr / PFE.word.len,
	     * and by setting the HERE-string to length null, THROW
	     * will not try to report it but instead it prints PFE.word.
	     */
	    p4_word_parseword (' '); /* PARSE-WORD-NOHERE */
            if (PFE.word.len) 
{ *DP = 0; break; }
switch (SOURCE_ID)
{
             default:
                 if (p4_next_line ())
                 
{
                     PFE.last_here = PFE.dp;
                     continue;
                 }
case 0: case -1: return; }
}
i = DIM (PFE.interpret); while ( i-- )
{
	    if (! PFE.interpret[i]) continue;
	    if (PFE.interpret[i] (FX_VOID)) goto again;
	}
p4_throw (P4_ON_UNDEFINED); }
}
/**
   => INTERPRET buffer
 */
_export void
p4_evaluate (char *p, int n)
{
#  if !defined P4_RP_IN_VM
    Iframe saved;
    p4_link_saved_input (&saved);
#  else
    RP = (p4xcode **) p4_save_input (RP);
#  endif
    SOURCE_ID = -1;
    BLK = 0;
    TIB = p;
    NUMBER_TIB = n;
    TO_IN = 0;
    FX (p4_interpret);
#  if defined P4_RP_IN_VM
    RP = (p4xcode **) p4_restore_input (RP);
#  else
    p4_unlink_saved_input (&saved);
#  endif
}
/**
 */
_export void
p4_include_file (p4_File *fid)
{
    if (fid == NULL || fid->f == NULL)
        p4_throws (P4_ON_FILE_NEX, fid->name, 0);
    else
    
{
#      if !defined P4_RP_IN_VM
	Iframe saved;
	p4_link_saved_input (&saved);
#      else
	RP = (p4xcode **) p4_save_input (RP);
#      endif
	SOURCE_ID = (p4cell) fid;
	BLK = 0;
	TO_IN = 0;
	FX (p4_interpret);
#      if defined P4_RP_IN_VM
	RP = (p4xcode **) p4_restore_input (RP);
#      else
	p4_unlink_saved_input (&saved);
#      endif
    }
}
/**
   called by INCLUDED and INCLUDE
 */
_export int
p4_included1 (const char *name, int len, int throws)
{
    File* f;
    char* fn;
    
    fn = p4_pocket_expanded_filename (name, len, 
				      P4_opt.incpaths, P4_opt.incext);
    f = p4_open_file (fn, strlen (fn), FMODE_RO);
    if (!f)
    
{  
        if (throws) 
        
{
            p4_throws (P4_ON_FILE_NEX, fn, 0); 
        }
else
{ 
            P4_fail2 ("- could not open '%s' (paths='%s')\n", 
              fn, P4_opt.incpaths); 
            return 0; 
        }
}
# ifdef _K12_SOURCE
{
        register struct k12_priv* k12p = P4_K12_PRIV(p4TH);
        k12p->state = K12_EMU_NOT_LOADED;
        /* before GetEvent, it goes _IDLE in term-k12.c FIXME: generalize!!*/
    }
# endif p4_include_file (f); p4_close_file (f); return 1; }
/**
   INCLUDED
 */
_export void
p4_included (const char* name, int len)
{
    p4_included1 (name, len, 1);
}
/*
 */
_export void
p4_unnest_input (p4_Iframe *p)
{
    while (PFE.saved_input && PFE.saved_input != p)
    
{
        switch (SOURCE_ID)
	
{
         case -1:
         case 0:
             break;
         default:
             p4_close_file (SOURCE_FILE);
	}
# if defined P4_RP_IN_VM RP = (p4xcode **) p4_restore_input (PFE.saved_input); # else p4_unlink_saved_input (PFE.saved_input); # endif }
}
/**
   walk the filedescriptors and close/free the fds. This function
   is usefully called from => ABORT - otherwise it may rip too
   many files in use.
 */
FCode (p4_closeall_files) 
{
    /*FIXME: look at p4_close_all_files, is it the same?? */
    File* f;

    /* see => p4_free_file_slot for an example */
    for (f = PFE.files; f < PFE.files_top; f++) 
        if (f->f != NULL)
        
{
            if (f->name && f->name[0] == '<')
                continue; /* stdIn, stdOut, stdErr, a.k.a. "<STDIN>" etc. */
            else
                p4_close_file(f);
        }
}
/* ********************************************************************** 
    QUIT, ABORT, INTERPRET
 */
/**
   a little helper that just emits "ok", called in outer interpreter,
   also useful on the command line to copy lines for re-execution
 */
FCode (p4_ok)
{
    if (!STATE)
    
{
        p4_outs ("ok");
        if (PFE.nr) 
{
            p4_outc ('-');
            p4_outc ('0' + PFE.nr % 10); 
        }
FX (p4_space); }
}
/*
   things => QUIT has to initialize 
 */
static void
quit_system (P4_VOID)
{
#  ifdef P4_RP_IN_VM
    CSP = (p4cell*) RP;         /* come_back marker */
    RP = p4_R0;			/* return stack to its bottom */
#  endif
    LP = NULL;			/* including all local variables */
    STATE = P4_FALSE;		/* interpreting now */
    PFE.cAtch = NULL;		/* and no exceptions caught */
    p4_debug_off ();		/* turn off debugger */
}
/*
   things => ABORT has to initialize
 */
static void
abort_system (P4_VOID)
{
    SP = p4_S0;				/* stacks */
    if (PFE.abort[2]) 
{ (PFE.abort[2]) (FX_VOID); }
/* -> floating */ if (PFE.abort[3])
{ (PFE.abort[3]) (FX_VOID); }
/* -> dstrings */ if (p4_RESET_ORDER)
{ FX (p4_reset_order); }
/* reset search order */ FX (p4_decimal); /* number i/o base */ FX (p4_standard_io); /* disable i/o redirection */ FX (p4_closeall_files); /* close open filedescriptors */ if (PFE.dictlimit - PFE_MINIMAL_UNUSED > PFE.dp) return; else
{
        P4_fail2 ("DICT OVER - reset HERE from %+i to %+i",
                  PFE.dp - PFE.dict, PFE.last_here - PFE.dict);

        PFE.dp = PFE.last_here;
    }
}
FCode (p4_paren_abort)
{
    abort_system (FX_VOID);
    quit_system (FX_VOID);
}
/** 
   the outer interpreter, in PFE the jumppoint for both => ABORT and => QUIT
 */
_export int
p4_interpret_loop (P4_VOID)
{
    register int err;
    switch (err = setjmp(PFE.loop))
    
{
     case  0:  /* newloop -> do abort*/
         /* initialize */
     case 'A': /* do abort */
         abort_system (FX_VOID);
         p4_redo_all_words (PFE.abort_wl);
         /* -> do quit */
     case 'Q': /* do quit */
         quit_system (FX_VOID);
     case 'S': /* schedule */
				/* normal interactive QUIT */
                                /* doing the QUERY-INTERPRET loop */
         p4_unnest_input (NULL);
         for (;;)
         
{
             p4_do_all_words (PFE.prompt_wl);
             FX (p4_ok);
             FX (p4_cr);	
             FX (p4_query);		
             FX (p4_interpret);		
             FX (p4_Q_stack);	
         }
case 'X': /* exit / bye */ /* -> PFE.atexit_cleanup (); */ return 0; }
return err; }
/* ********************************************************************** 
   Initialize dictionary, and system variables, include files		  
 */
/**
   setup all system variables and initialize the dictionary
   to reach a very clean status as if right after cold boot.
 */
static void
cold_system (void)
{
    SP = p4_S0;
#  ifndef P4_NO_FP
    FP = p4_F0;
#  endif
#  ifdef P4_RP_IN_VM
    RP = p4_R0;
#  endif
    TIB = PFE.tib;
    BASE = 10;
    p4_DPL = -1;
    PRECISION = p4_search_option_value("precision",9, 6, PFE.set);
    WORDL_FLAG = 0; /* implicitly enables HASHing */
    if (p4_search_option_value("source-any-case",15, PFE_set.find_any_case,
          PFE.set)) WORDL_FLAG |= WORDL_NOCASE;
    if (p4_search_option_value("source-upper-case",17, PFE_set.upper_case_on,
          PFE.set)) WORDL_FLAG |= WORDL_UPPER_CASE;
    LOWER_CASE_FN = p4_search_option_value("lower-case-filenames",20, 
      PFE_set.lower_case_fn, PFE.set);
    FLOAT_INPUT = P4_opt.float_input;
    PFE.local = (char (*)[32]) PFE.stack;

    memset (PFE.files_top - 3, 0, sizeof (File) * 3);

    PFE.stdIn = PFE.files_top - 3;
    PFE.stdIn->f = stdin;
    strcpy (PFE.stdIn->name, "<STDIN>");
    strcpy (PFE.stdIn->mdstr, "r");
    PFE.stdIn->mode = FMODE_RO;
    
    PFE.stdOut = PFE.files_top - 2;
    PFE.stdOut->f = stdout;
    strcpy (PFE.stdOut->name, "<STDOUT>");
    strcpy (PFE.stdOut->mdstr, "a");
    PFE.stdOut->mode = FMODE_WO;
    
    PFE.stdErr = PFE.files_top - 1;
    PFE.stdErr->f = stderr;
    strcpy (PFE.stdErr->name, "<STDERR>");
    strcpy (PFE.stdErr->mdstr, "a");
    PFE.stdErr->mode = FMODE_WO;
    
    REDEFINED_MSG = P4_FALSE;
    
    /* Wipe the dictionary: */
    memset (PFE.dict, 0, (PFE.dictlimit - PFE.dict));
    p4_preload_only ();
    if (! PFE.abort_wl)  PFE.abort_wl  = p4_new_wordlist (0);
    if (! PFE.prompt_wl) PFE.prompt_wl = p4_new_wordlist (0);
    FX (p4_only_RT);
    
{
        /* Defines the following default search order:
         * FORTH EXTENSIONS ONLY */
#ifndef MODULE0
#define MODULE0 extensions
#endif
        extern const p4Words P4WORDS (forth);
        extern const p4Words P4WORDS (MODULE0); 
        
#ifdef MODULE1
        extern const p4Words P4WORDS (MODULE1);
#endif
#ifdef MODULE2
        extern const p4Words P4WORDS (MODULE2);
#endif
#ifdef MODULE3
        extern const p4Words P4WORDS (MODULE3);
#endif

        p4_load_words (&P4WORDS (forth), ONLY, 0);
        p4_load_words (&P4WORDS (MODULE0), ONLY, 0);
        
#ifdef MODULE1
        p4_load_words (&P4WORDS (MODULE1), ONLY, 0);
#endif
#ifdef MODULE2
        p4_load_words (&P4WORDS (MODULE2), ONLY, 0);
#endif
#ifdef MODULE3
        p4_load_words (&P4WORDS (MODULE3), ONLY, 0);
#endif
	/* should be replaced by p4_load_words someday... fixme: */
        if (PFE.set->loadlist[0]) 
            p4_load_words (PFE.set->loadlist[0], ONLY, 0);
        if (PFE.set->loadlist[1]) 
            p4_load_words (PFE.set->loadlist[1], ONLY, 0);
        if (PFE.set->loadlist[2]) 
            p4_load_words (PFE.set->loadlist[2], ONLY, 0);
        if (PFE.set->loadlist[3]) 
            p4_load_words (PFE.set->loadlist[3], ONLY, 0);
    }
/* last step of bootup default search-order is FORTH DEFINITIONS a.k.a. FORTH-WORDLIST CONTEXT ! DEFINITIONS */ CURRENT = CONTEXT[0] = PFE.forth_wl; /* points to FORTH vocabulary */ FX (p4_default_order); REDEFINED_MSG = P4_TRUE; }
/**
   setup all system variables and initialize the dictionary
 */
_export void
p4_boot_system (void)
{
    if (PFE.nr) 
{ printf (" CPU%i ", PFE.nr); }
/* Action of COLD ABORT and QUIT, but don't enter the interactive QUIT */ RESET_ORDER = P4_TRUE; cold_system (); abort_system (); quit_system (); REDEFINED_MSG = P4_FALSE;
{ 
	register const char* file;
#       ifndef PFE_BLOCK_FILE /* USER-CONFIG: --block-file=<mapped-file> */
#       define PFE_BLOCK_FILE PFE_DEFAULT_BLKFILE 
#       endif

	if ((file = p4_search_option_string (
	    "block-file", 10, PFE_BLOCK_FILE, PFE.set)))
	
{
	    if (! p4_set_blockfile (p4_open_blockfile(file, strlen (file)))
		&& strcmp (file, PFE_DEFAULT_BLKFILE) != 0)
	    
{
		P4_fatal1 ("Can't find block file %s", file);
		PFE.exitcode = 4;
		p4_longjmp_exit ();
	    }
}
# ifndef PFE_BOOT_FILE /* USER-CONFIG: --boot-file=<included-file> */ # define PFE_BOOT_FILE 0 # endif if ((file = p4_search_option_string ( "boot-file", 9, PFE_BOOT_FILE, PFE.set)))
{
	    p4_included1 (file, strlen (file), 0);
	}
}
/* read_help_index (PFE_PKGHELPDIR, "index"); */ /* According to the ANS Forth description, the order after BOOT must * include the FORTH-WORDLIST, and the CURRENT definition-wordlist * must be the FORTH-WORDLIST. Here we assume that the various LOADs * before have kept atleast one occurence of FORTH-WORDLIST in the * search-order but we explicitly set the CURRENT definition-wordlist * Then we do DEFAULT-ORDER so it can pop up in a RESET-ORDER on ABORT * BEWARE: a bootscript can arrange the items in the search-order but * it can not arrange to set the CURRENT definitions-wordlist as well. * Note that ONLY is always searched, so one can always get back at FORTH * OTOH, in main-sub, the first include-file is loaded after boot_system * so it can arrange for a different the DEFAULT-ORDER incl. CURRENT. */ CURRENT = PFE.forth_wl; FX (p4_default_order); FENCE = DP; LAST = NULL; REDEFINED_MSG = P4_TRUE; }
/*@}*/