muforth/interpret.c

/*
 * This file is part of muFORTH: http://pages.nimblemachines.com/muforth
 *
 * Copyright (c) 2002-2008 David Frech. All rights reserved, and all wrongs
 * reversed. (See the file COPYRIGHT for details.)
 */

/* Interpreter and compiler */

#include "muforth.h"

#include <ctype.h>

/* for debug */
#include <sys/uio.h>
#include <unistd.h>

#if defined (DEBUG_STACK) || defined(DEBUG_TOKEN)
#include <stdio.h>
#endif

struct imode        /* interpreter mode */
{
    xtk eat;        /* consume one token */
    xtk prompt;     /* display a mode-specific prompt */
};

static struct text source;
static char *first;         /* goes from source.start to source.end */
struct string parsed;       /* for errors */

/* XXX: Gross hack alert! */
char *ate_the_stack;
char *isnt_defined;

/*
 * This is a horrendous hack. gcc 3.3 is smart enough to let me do what
 * I want using initializers, but 2.95 complains. So I have to run a bit
 * of code that compiles some strings into the dictionary, and sets a
 * few globals to point to them. It's really ugly.
 */
static void make_constant_strings()
{
    ate_the_stack = to_counted_string("ate the stack");
    isnt_defined  = to_counted_string("isn't defined");
}

/*
 * This isn't exactly ANS-kosher, since traditionally >IN contained an
 * offset within the input text that went from 0 to length-1; here it goes
 * from source.start to source.end-1, but the important effect is that we
 * can do this:
 *
 * >in @ <parse> >in ! <re-parse>
 */
void mu_to_in()
{
    PUSH(&first);
}

static void mu_return_token(char *last, int trailing)
{
    /* Get address and length of the token */
    parsed.data = first;
    parsed.length = last - first;

    /* Account for characters processed, return token */
    first = last + trailing;

    NIP(-1);    /* make room for result */
    ST1 = (cell) parsed.data;
    TOP = parsed.length;

#ifdef DEBUG_TOKEN
    fprintf(stderr, "%.*s\n", parsed.length, parsed.data);
#endif
}

void mu_token()  /* -- start len */
{
    char *last;

    DUP;   /* we'll be setting TOP when we're done */

    /* Skip leading whitespace */
    for (; first < source.end && isspace(*first); first++)
        ;

    /*
     * Scan for trailing whitespace and consume it, unless we run out of
     * input text first.
     */
    for (last = first; last < source.end; last++)
        if (isspace(*last))
        {
            /* found trailing whitespace; consume it */
            mu_return_token(last, 1);
            return;
        }

    /* ran out of text; don't consume trailing */
    mu_return_token(last, 0);
}

void mu_parse()  /* delim -- start len */
{
    char *last;

    /* The first character of unseen input is the first character of token. */

    /*
     * Scan for trailing delimiter and consume it, unless we run out of
     * input text first.
     */
    for (last = first; last < source.end; last++)
        if (TOP == *last)
        {
            /* found trailing delimiter; consume it */
            mu_return_token(last, 1);
            return;
        }

    /* ran out of text; don't consume trailing */
    mu_return_token(last, 0);
}

/*
: complain   error"  is not defined"  -;
: huh?   if ^ then complain  ;   ( useful after find or token' )
defer not-defined  now complain is not-defined
*/

void mu_complain()
{
    PUSH(isnt_defined);
    mu_throw();
}

void mu_huh_q()
{
    if (POP) return;
    mu_complain();
}

void mu_execute() { EXECUTE; }

/* The interpreter's "consume" function. */
void _mu__lbracket()
{
    mu_push_forth_chain();
    mu_find();
    if (POP)
    {
        EXECUTE;
        return;
    }
    mu_complain();
}

/* The compiler's "consume" function. */
void _mu__rbracket()
{
    mu_push_compiler_chain();
    mu_find();
    if (POP)
    {
        EXECUTE;
        return;
    }
    mu_push_forth_chain();
    mu_find();
    if (POP)
    {
        mu_compile_comma();
        return;
    }
    mu_complain();
}

void mu_nope() {}    /* very useful NO-OP */
void mu_zzz()  {}    /* a convenient GDB breakpoint */

/*
 * Remember that the second part of a struct imode is a pointer to code to
 * print a mode-specific prompt. The muforth kernel lacks decent I/O
 * facilities. Until these are defined in startup.mu4, the prompts are
 * noops.
 */

static struct imode forth_interpreter  = { XTK(_mu__lbracket), XTK(mu_nope) };
static struct imode forth_compiler     = { XTK(_mu__rbracket), XTK(mu_nope) };

static struct imode *state = &forth_interpreter;


static void consume()
{
    execute_xtk(state->eat);      /* call the current consume function */
}

void mu_push_state()
{
    PUSH(&state);
}

void mu_compiler_lbracket()
{
    state = &forth_interpreter;
}

void mu_minus_rbracket()
{
    state = &forth_compiler;
}

void mu_push_parsed()
{
    DUP; NIP(-1);
    ST1 = (cell) parsed.data;
    TOP = parsed.length;
}

static void mu_qstack()
{
    if (SP > S0)
    {
        PUSH(ate_the_stack);
        mu_throw();
    }
#ifdef DEBUG_STACK
    /* print stack */
    {
        cell *p;

        printf("  [ ");

        for (p = S0; p > SP; )
            printf("%x ", *--p);

        printf("] %x\n", TOP);
    }
#endif
}

void mu_interpret()
{
    source.start = (char *)ST1;
    source.end =   (char *)ST1 + TOP;
    DROP(2);

    first = source.start;

    for (;;)
    {
        mu_token();
        if (TOP == 0) break;
        consume();
        mu_qstack();
    }
    DROP(2);
}

void mu_evaluate()
{
    struct text saved_source;
    char *saved_first;

    saved_source = source;
    saved_first = first;

    PUSH(XTK(mu_interpret));
    mu_catch();
    source = saved_source;
    first = saved_first;
    mu_throw();
}

void mu_load_file()    /* c-string-name */
{
    int fd;

    mu_push_r_slash_o();
    mu_open_file();
    fd = TOP;
    mu_mmap_file();
    PUSH(XTK(mu_evaluate));
    mu_catch();
    close(fd);
    mu_throw();
}

void init_interpret()   /* called at init time */
{
    make_constant_strings();    /* XXX: Hack! */
}

/*
: consume   state @  @execute ;

: _[   ( interpret one token)
      .forth. find  if execute ^ then  number ;

: _]   ( compile one token)
   .compiler. find  if  execute  ^ then
      .forth. find  if  compile, ^ then    number, ;

-:  compiler -"find if  assembler -"find if  number, exit  then
        compile, exit  then  execute ;

-:  assembler -"find if  outside -"find if  target -"find  if
      ( not found)  number exit  then
      ( target)  @  \o >DATA  ( target pfa contents)  exit  then  then
      execute  ;

-:  outside -"find  if  target -"find  if  tnumber drop  exit  then
      @ ( target pfa)  remote exit  then  execute  ;

-:  inside -"find  if   target -"find  if  tnumber, exit  then
      @ ( target pfa)  \o ,  exit  then   execute  ;

-:  definer -"find  if  compiler -"find  if  ( execute if either of these)
    outside -"find  if     forth -"find  if  number, exit  then  then
        compile, exit  then  then   execute  ;

( stack underflow)
: ?stack   depth 0< if  error"  ate the stack"  then  ;

: interpret ( a u)
   source 2!  0 >in !
   begin  token  dup  while  consume  ?stack  repeat  2drop  ;

: evaluate  ( a u)
   source 2@ 2push  >in @ push  lit interpret catch
   pop >in !  2pop source 2!  throw  ;

compiler definitions
: -;     \ [  ;             ( for words that don't end with ^)
: ^   lit unnest compile,  ;   ( right now this doesn't do anything fancy)
: ;      \ ^  \ -;  ;
forth definitions
*/