pforth: allow header and code size to be controlled
[pforth] / csrc / pf_core.c
/* @(#) pf_core.c 98/01/28 1.5 */
/***************************************************************
** Forth based on 'C'
**
** This file has the main entry points to the pForth library.
**
** Author: Phil Burk
** Copyright 1994 3DO, Phil Burk, Larry Polansky, David Rosenboom
**
** The pForth software code is dedicated to the public domain,
** and any third party may reproduce, distribute and modify
** the pForth software code or any derivative works thereof
** without any compensation or license. The pForth software
** code is provided on an "as is" basis without any warranty
** of any kind, including, without limitation, the implied
** warranties of merchantability and fitness for a particular
** purpose and their equivalents under the laws of any jurisdiction.
**
****************************************************************
** 940502 PLB Creation.
** 940505 PLB More macros.
** 940509 PLB Moved all stack handling into inner interpreter.
** Added Create, Colon, Semicolon, HNumberQ, etc.
** 940510 PLB Got inner interpreter working with secondaries.
** Added (LITERAL). Compiles colon definitions.
** 940511 PLB Added conditionals, LITERAL, CREATE DOES>
** 940512 PLB Added DO LOOP DEFER, fixed R>
** 940520 PLB Added INCLUDE
** 940521 PLB Added NUMBER?
** 940930 PLB Outer Interpreter now uses deferred NUMBER?
** 941005 PLB Added ANSI locals, LEAVE, modularised
** 950320 RDG Added underflow checking for FP stack
** 970702 PLB Added STACK_SAFETY to FP stack size.
***************************************************************/
#include "pf_all.h"
/***************************************************************
** Global Data
***************************************************************/
char gScratch[TIB_SIZE];
pfTaskData_t *gCurrentTask = NULL;
pfDictionary_t *gCurrentDictionary;
cell_t gNumPrimitives;
ExecToken gLocalCompiler_XT; /* custom compiler for local variables */
ExecToken gNumberQ_XT; /* XT of NUMBER? */
ExecToken gQuitP_XT; /* XT of (QUIT) */
ExecToken gAcceptP_XT; /* XT of ACCEPT */
/* Depth of data stack when colon called. */
cell_t gDepthAtColon;
/* Global Forth variables. */
cell_t gVarContext; /* Points to last name field. */
cell_t gVarState; /* 1 if compiling. */
cell_t gVarBase; /* Numeric Base. */
cell_t gVarEcho; /* Echo input. */
cell_t gVarTraceLevel; /* Trace Level for Inner Interpreter. */
cell_t gVarTraceStack; /* Dump Stack each time if true. */
cell_t gVarTraceFlags; /* Enable various internal debug messages. */
cell_t gVarQuiet; /* Suppress unnecessary messages, OK, etc. */
cell_t gVarReturnCode; /* Returned to caller of Forth, eg. UNIX shell. */
/* data for INCLUDE that allows multiple nested files. */
IncludeFrame gIncludeStack[MAX_INCLUDE_DEPTH];
cell_t gIncludeIndex;
static void pfResetForthTask( void );
static void pfInit( void );
static void pfTerm( void );
#define DEFAULT_RETURN_DEPTH (512)
#define DEFAULT_USER_DEPTH (512)
#ifndef PF_DEFAULT_HEADER_SIZE
#define DEFAULT_HEADER_SIZE (120000)
#endif
#ifndef PF_DEFAULT_CODE_SIZE
#define PF_DEFAULT_CODE_SIZE (300000)
#endif
/* Initialize globals in a function to simplify loading on
* embedded systems which may not support initialization of data section.
*/
static void pfInit( void )
{
/* all zero */
gCurrentTask = NULL;
gCurrentDictionary = NULL;
gNumPrimitives = 0;
gLocalCompiler_XT = 0;
gVarContext = (cell_t)NULL; /* Points to last name field. */
gVarState = 0; /* 1 if compiling. */
gVarEcho = 0; /* Echo input. */
gVarTraceLevel = 0; /* Trace Level for Inner Interpreter. */
gVarTraceFlags = 0; /* Enable various internal debug messages. */
gVarReturnCode = 0; /* Returned to caller of Forth, eg. UNIX shell. */
gIncludeIndex = 0;
/* non-zero */
gVarBase = 10; /* Numeric Base. */
gDepthAtColon = DEPTH_AT_COLON_INVALID;
gVarTraceStack = 1;
pfInitMemoryAllocator();
ioInit();
}
static void pfTerm( void )
{
ioTerm();
}
/***************************************************************
** Task Management
***************************************************************/
void pfDeleteTask( PForthTask task )
{
pfTaskData_t *cftd = (pfTaskData_t *)task;
FREE_VAR( cftd->td_ReturnLimit );
FREE_VAR( cftd->td_StackLimit );
pfFreeMem( cftd );
}
/* Allocate some extra cells to protect against mild stack underflows. */
#define STACK_SAFETY (8)
PForthTask pfCreateTask( cell_t UserStackDepth, cell_t ReturnStackDepth )
{
pfTaskData_t *cftd;
cftd = ( pfTaskData_t * ) pfAllocMem( sizeof( pfTaskData_t ) );
if( !cftd ) goto nomem;
pfSetMemory( cftd, 0, sizeof( pfTaskData_t ));
/* Allocate User Stack */
cftd->td_StackLimit = (cell_t *) pfAllocMem((ucell_t)(sizeof(cell_t) *
(UserStackDepth + STACK_SAFETY)));
if( !cftd->td_StackLimit ) goto nomem;
cftd->td_StackBase = cftd->td_StackLimit + UserStackDepth;
cftd->td_StackPtr = cftd->td_StackBase;
/* Allocate Return Stack */
cftd->td_ReturnLimit = (cell_t *) pfAllocMem((ucell_t)(sizeof(cell_t) * ReturnStackDepth) );
if( !cftd->td_ReturnLimit ) goto nomem;
cftd->td_ReturnBase = cftd->td_ReturnLimit + ReturnStackDepth;
cftd->td_ReturnPtr = cftd->td_ReturnBase;
/* Allocate Float Stack */
#ifdef PF_SUPPORT_FP
/* Allocate room for as many Floats as we do regular data. */
cftd->td_FloatStackLimit = (PF_FLOAT *) pfAllocMem((ucell_t)(sizeof(PF_FLOAT) *
(UserStackDepth + STACK_SAFETY)));
if( !cftd->td_FloatStackLimit ) goto nomem;
cftd->td_FloatStackBase = cftd->td_FloatStackLimit + UserStackDepth;
cftd->td_FloatStackPtr = cftd->td_FloatStackBase;
#endif
cftd->td_InputStream = PF_STDIN;
cftd->td_SourcePtr = &cftd->td_TIB[0];
cftd->td_SourceNum = 0;
return (PForthTask) cftd;
nomem:
ERR("CreateTaskContext: insufficient memory.\n");
if( cftd ) pfDeleteTask( (PForthTask) cftd );
return NULL;
}
/***************************************************************
** Dictionary Management
***************************************************************/
ThrowCode pfExecIfDefined( const char *CString )
{
ThrowCode result = 0;
if( NAME_BASE != (cell_t)NULL)
{
ExecToken XT;
if( ffFindC( CString, &XT ) )
{
result = pfCatch( XT );
}
}
return result;
}
/***************************************************************
** Delete a dictionary created by pfCreateDictionary()
*/
void pfDeleteDictionary( PForthDictionary dictionary )
{
pfDictionary_t *dic = (pfDictionary_t *) dictionary;
if( !dic ) return;
if( dic->dic_Flags & PF_DICF_ALLOCATED_SEGMENTS )
{
FREE_VAR( dic->dic_HeaderBaseUnaligned );
FREE_VAR( dic->dic_CodeBaseUnaligned );
}
pfFreeMem( dic );
}
/***************************************************************
** Create a complete dictionary.
** The dictionary consists of two parts, the header with the names,
** and the code portion.
** Delete using pfDeleteDictionary().
** Return pointer to dictionary management structure.
*/
PForthDictionary pfCreateDictionary( cell_t HeaderSize, cell_t CodeSize )
{
/* Allocate memory for initial dictionary. */
pfDictionary_t *dic;
dic = ( pfDictionary_t * ) pfAllocMem( sizeof( pfDictionary_t ) );
if( !dic ) goto nomem;
pfSetMemory( dic, 0, sizeof( pfDictionary_t ));
dic->dic_Flags |= PF_DICF_ALLOCATED_SEGMENTS;
/* Align dictionary segments to preserve alignment of floats across hosts.
* Thank you Helmut Proelss for pointing out that this needs to be cast
* to (ucell_t) on 16 bit systems.
*/
#define DIC_ALIGNMENT_SIZE ((ucell_t)(0x10))
#define DIC_ALIGN(addr) ((((ucell_t)(addr)) + DIC_ALIGNMENT_SIZE - 1) & ~(DIC_ALIGNMENT_SIZE - 1))
/* Allocate memory for header. */
if( HeaderSize > 0 )
{
dic->dic_HeaderBaseUnaligned = (ucell_t) pfAllocMem( (ucell_t) HeaderSize + DIC_ALIGNMENT_SIZE );
if( !dic->dic_HeaderBaseUnaligned ) goto nomem;
/* Align header base. */
dic->dic_HeaderBase = DIC_ALIGN(dic->dic_HeaderBaseUnaligned);
pfSetMemory( (char *) dic->dic_HeaderBase, 0xA5, (ucell_t) HeaderSize);
dic->dic_HeaderLimit = dic->dic_HeaderBase + HeaderSize;
dic->dic_HeaderPtr = dic->dic_HeaderBase;
}
else
{
dic->dic_HeaderBase = 0;
}
/* Allocate memory for code. */
dic->dic_CodeBaseUnaligned = (ucell_t) pfAllocMem( (ucell_t) CodeSize + DIC_ALIGNMENT_SIZE );
if( !dic->dic_CodeBaseUnaligned ) goto nomem;
dic->dic_CodeBase = DIC_ALIGN(dic->dic_CodeBaseUnaligned);
pfSetMemory( (char *) dic->dic_CodeBase, 0x5A, (ucell_t) CodeSize);
dic->dic_CodeLimit = dic->dic_CodeBase + CodeSize;
dic->dic_CodePtr.Byte = ((uint8_t *) (dic->dic_CodeBase + QUADUP(NUM_PRIMITIVES)));
return (PForthDictionary) dic;
nomem:
pfDeleteDictionary( dic );
return NULL;
}
/***************************************************************
** Used by Quit and other routines to restore system.
***************************************************************/
static void pfResetForthTask( void )
{
/* Go back to terminal input. */
gCurrentTask->td_InputStream = PF_STDIN;
/* Reset stacks. */
gCurrentTask->td_StackPtr = gCurrentTask->td_StackBase;
gCurrentTask->td_ReturnPtr = gCurrentTask->td_ReturnBase;
#ifdef PF_SUPPORT_FP /* Reset Floating Point stack too! */
gCurrentTask->td_FloatStackPtr = gCurrentTask->td_FloatStackBase;
#endif
/* Advance >IN to end of input. */
gCurrentTask->td_IN = gCurrentTask->td_SourceNum;
gVarState = 0;
}
/***************************************************************
** Set current task context.
***************************************************************/
void pfSetCurrentTask( PForthTask task )
{
gCurrentTask = (pfTaskData_t *) task;
}
/***************************************************************
** Set Quiet Flag.
***************************************************************/
void pfSetQuiet( cell_t IfQuiet )
{
gVarQuiet = (cell_t) IfQuiet;
}
/***************************************************************
** Query message status.
***************************************************************/
cell_t pfQueryQuiet( void )
{
return gVarQuiet;
}
/***************************************************************
** Top level interpreter.
***************************************************************/
ThrowCode pfQuit( void )
{
ThrowCode exception;
int go = 1;
while(go)
{
exception = ffOuterInterpreterLoop();
if( exception == 0 )
{
exception = ffOK();
}
switch( exception )
{
case 0:
break;
case THROW_BYE:
go = 0;
break;
case THROW_ABORT:
default:
ffDotS();
pfReportThrow( exception );
pfHandleIncludeError();
pfResetForthTask();
break;
}
}
return gVarReturnCode;
}
/***************************************************************
** Include file based on 'C' name.
***************************************************************/
cell_t pfIncludeFile( const char *FileName )
{
FileStream *fid;
cell_t Result;
char buffer[32];
cell_t numChars, len;
/* Open file. */
fid = sdOpenFile( FileName, "r" );
if( fid == NULL )
{
ERR("pfIncludeFile could not open ");
ERR(FileName);
EMIT_CR;
return -1;
}
/* Create a dictionary word named ::::FileName for FILE? */
pfCopyMemory( &buffer[0], "::::", 4);
len = (cell_t) pfCStringLength(FileName);
numChars = ( len > (32-4-1) ) ? (32-4-1) : len;
pfCopyMemory( &buffer[4], &FileName[len-numChars], numChars+1 );
CreateDicEntryC( ID_NOOP, buffer, 0 );
Result = ffIncludeFile( fid ); /* Also close the file. */
/* Create a dictionary word named ;;;; for FILE? */
CreateDicEntryC( ID_NOOP, ";;;;", 0 );
return Result;
}
/***************************************************************
** Output 'C' string message.
** Use sdTerminalOut which works before initializing gCurrentTask.
***************************************************************/
void pfDebugMessage( const char *CString )
{
#if 0
while( *CString )
{
char c = *CString++;
if( c == '\n' )
{
sdTerminalOut( 0x0D );
sdTerminalOut( 0x0A );
pfDebugMessage( "DBG: " );
}
else
{
sdTerminalOut( c );
}
}
#else
(void)CString;
#endif
}
/***************************************************************
** Print a decimal number to debug output.
*/
void pfDebugPrintDecimalNumber( int n )
{
pfDebugMessage( ConvertNumberToText( n, 10, TRUE, 1 ) );
}
/***************************************************************
** Output 'C' string message.
** This is provided to help avoid the use of printf() and other I/O
** which may not be present on a small embedded system.
** Uses ioType & ioEmit so requires that gCurrentTask has been initialized.
***************************************************************/
void pfMessage( const char *CString )
{
ioType( CString, (cell_t) pfCStringLength(CString) );
}
/**************************************************************************
** Main entry point for pForth.
*/
ThrowCode pfDoForth( const char *DicFileName, const char *SourceName, cell_t IfInit )
{
pfTaskData_t *cftd;
pfDictionary_t *dic = NULL;
ThrowCode Result = 0;
ExecToken EntryPoint = 0;
#ifdef PF_USER_INIT
Result = PF_USER_INIT;
if( Result < 0 ) goto error1;
#endif
pfInit();
/* Allocate Task structure. */
pfDebugMessage("pfDoForth: call pfCreateTask()\n");
cftd = pfCreateTask( DEFAULT_USER_DEPTH, DEFAULT_RETURN_DEPTH );
if( cftd )
{
pfSetCurrentTask( cftd );
if( !gVarQuiet )
{
MSG( "PForth V"PFORTH_VERSION );
if( IsHostLittleEndian() ) MSG("-LE");
else MSG("-BE");
#if PF_BIG_ENDIAN_DIC
MSG("/BE");
#elif PF_LITTLE_ENDIAN_DIC
MSG("/LE");
#endif
if (sizeof(cell_t) == 8)
{
MSG("/64");
}
else if (sizeof(cell_t) == 4)
{
MSG("/32");
}
MSG( ", built "__DATE__" "__TIME__ );
}
/* Don't use MSG before task set. */
if( SourceName )
{
pfDebugMessage("SourceName = "); pfDebugMessage(SourceName); pfDebugMessage("\n");
}
#ifdef PF_NO_GLOBAL_INIT
if( LoadCustomFunctionTable() < 0 ) goto error2; /* Init custom 'C' call array. */
#endif
#if (!defined(PF_NO_INIT)) && (!defined(PF_NO_SHELL))
if( IfInit )
{
pfDebugMessage("Build dictionary from scratch.\n");
dic = pfBuildDictionary( PF_DEFAULT_HEADER_SIZE, PF_DEFAULT_CODE_SIZE );
}
else
#else
TOUCH(IfInit);
#endif /* !PF_NO_INIT && !PF_NO_SHELL*/
{
if( DicFileName )
{
pfDebugMessage("DicFileName = "); pfDebugMessage(DicFileName); pfDebugMessage("\n");
if( !gVarQuiet )
{
EMIT_CR;
}
dic = pfLoadDictionary( DicFileName, &EntryPoint );
}
else
{
if( !gVarQuiet )
{
MSG(" (static)");
EMIT_CR;
}
dic = pfLoadStaticDictionary();
}
}
if( dic == NULL ) goto error2;
if( !gVarQuiet )
{
EMIT_CR;
}
pfDebugMessage("pfDoForth: try AUTO.INIT\n");
Result = pfExecIfDefined("AUTO.INIT");
if( Result != 0 )
{
MSG("Error in AUTO.INIT");
goto error2;
}
if( EntryPoint != 0 )
{
Result = pfCatch( EntryPoint );
}
#ifndef PF_NO_SHELL
else
{
if( SourceName == NULL )
{
pfDebugMessage("pfDoForth: pfQuit\n");
Result = pfQuit();
}
else
{
if( !gVarQuiet )
{
MSG("Including: ");
MSG(SourceName);
MSG("\n");
}
Result = pfIncludeFile( SourceName );
}
}
#endif /* PF_NO_SHELL */
/* Clean up after running Forth. */
pfExecIfDefined("AUTO.TERM");
pfDeleteDictionary( dic );
pfDeleteTask( cftd );
}
pfTerm();
#ifdef PF_USER_TERM
PF_USER_TERM;
#endif
return Result;
error2:
MSG("pfDoForth: Error occured.\n");
pfDeleteTask( cftd );
/* Terminate so we restore normal shell tty mode. */
pfTerm();
#ifdef PF_USER_INIT
error1:
#endif
return -1;
}
#ifdef PF_UNIT_TEST
cell_t pfUnitTest( void )
{
cell_t numErrors = 0;
numErrors += pfUnitTestText();
return numErrors;
}
#endif