|
|
|
#include "forth_internal.h"
|
|
|
|
|
|
|
|
static enum fh_error w_colon(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
ENSURE_STATE(FH_STATE_INTERPRET);
|
|
|
|
|
|
|
|
char *wordname = NULL;
|
|
|
|
size_t namelen = 0;
|
|
|
|
fh_input_consume_spaces(fh);
|
|
|
|
TRY(fh_input_read_word(fh, &wordname, &namelen));
|
|
|
|
|
|
|
|
fh_setstate(fh, FH_STATE_COMPILE, 0);
|
|
|
|
|
|
|
|
uint32_t ptr;
|
|
|
|
fh_align(fh);
|
|
|
|
TRY(fh_heap_reserve(fh, DICTWORD_SIZE, &ptr));
|
|
|
|
|
|
|
|
LOG("New word \"%.*s\", head at 0x%08x, exec at 0x%08x", (int) namelen, wordname, ptr, fh->here);
|
|
|
|
|
|
|
|
struct fh_word_s *new_word = fh_word_at(fh, ptr);
|
|
|
|
if (!new_word) { return FH_ERR_INTERNAL; }
|
|
|
|
new_word->previous = fh->dict_last;
|
|
|
|
new_word->param = fh->here;
|
|
|
|
new_word->handler = w_user_word;
|
|
|
|
strncpy(new_word->name, wordname, namelen);
|
|
|
|
new_word->name[namelen] = 0;
|
|
|
|
new_word->flags = WORDFLAG_WORD | WORDFLAG_HIDDEN;
|
|
|
|
|
|
|
|
fh->dict_last = ptr;
|
|
|
|
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error rt_marker(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
LOG("Restore dict to landmark sate \"%s\"", w->name);
|
|
|
|
fh->dict_last = w->param;
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error rt_defer(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
uint32_t defered = w->param;
|
|
|
|
if (defered == MAGICADDR_UNRESOLVED) {
|
|
|
|
LOGE("Exec DEFER name without assigned xt!");
|
|
|
|
return FH_ERR_BAD_DEFER;
|
|
|
|
}
|
|
|
|
|
|
|
|
return fh_handle_word(fh, defered);
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_marker(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
ENSURE_STATE(FH_STATE_INTERPRET);
|
|
|
|
|
|
|
|
char *wordname = NULL;
|
|
|
|
size_t namelen = 0;
|
|
|
|
fh_input_consume_spaces(fh);
|
|
|
|
TRY(fh_input_read_word(fh, &wordname, &namelen));
|
|
|
|
LOG("Marker name: %.*s", (int) namelen, wordname);
|
|
|
|
|
|
|
|
uint32_t ptr;
|
|
|
|
TRY(fh_heap_reserve(fh, DICTWORD_SIZE, &ptr));
|
|
|
|
|
|
|
|
struct fh_word_s *new_word = fh_word_at(fh, ptr);
|
|
|
|
if (!new_word) { return FH_ERR_INTERNAL; }
|
|
|
|
new_word->previous = fh->dict_last;
|
|
|
|
new_word->param = fh->dict_last;
|
|
|
|
new_word->handler = rt_marker;
|
|
|
|
strncpy(new_word->name, wordname, namelen);
|
|
|
|
new_word->name[namelen] = 0;
|
|
|
|
new_word->flags = WORDFLAG_WORD;
|
|
|
|
|
|
|
|
fh->dict_last = ptr;
|
|
|
|
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_colon_noname(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
ENSURE_STATE(FH_STATE_INTERPRET);
|
|
|
|
|
|
|
|
LOG("Starting noname compilation");
|
|
|
|
|
|
|
|
fh_setstate(fh, FH_STATE_COMPILE, 0);
|
|
|
|
|
|
|
|
uint32_t ptr;
|
|
|
|
fh_align(fh);
|
|
|
|
TRY(fh_heap_reserve(fh, DICTWORD_SIZE, &ptr));
|
|
|
|
|
|
|
|
struct fh_word_s *new_word = fh_word_at(fh, ptr);
|
|
|
|
if (!new_word) { return FH_ERR_INTERNAL; }
|
|
|
|
//new_word->previous = MAGICADDR_DICTFIRST;
|
|
|
|
new_word->previous = fh->dict_last;
|
|
|
|
new_word->param = fh->here;
|
|
|
|
new_word->handler = w_user_word;
|
|
|
|
new_word->name[0] = 0; // no name, really
|
|
|
|
new_word->flags = WORDFLAG_WORD | WORDFLAG_HIDDEN;
|
|
|
|
|
|
|
|
TRY(ds_push(fh, ptr)); // TODO maybe should do this at semicolon?
|
|
|
|
|
|
|
|
fh->dict_last = ptr;
|
|
|
|
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_does(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
|
|
|
|
if (fh->executing_compiled) {
|
|
|
|
struct fh_word_s *last_word = fh_word_at(fh, fh->dict_last);
|
|
|
|
if (!last_word) { return FH_ERR_INTERNAL; }
|
|
|
|
last_word->param = fh->execptr + INSTR_SIZE;
|
|
|
|
last_word->handler = w_user_word;
|
|
|
|
last_word->flags = WORDFLAG_WORD | WORDFLAG_CREATED;
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fh->state == FH_STATE_COMPILE) {
|
|
|
|
TRY(fh_put_instr(fh, FH_INSTR_WORD, (void *) w - (void *) &fh->heap[0])); // call the DOES word
|
|
|
|
TRY(fh_put_instr(fh, FH_INSTR_ENDWORD, 1)); // synthetic exit so we dont also run the "postponed" DOES> content
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
fh_setstate(fh, FH_STATE_COMPILE, 0);
|
|
|
|
|
|
|
|
struct fh_word_s *last_word = fh_word_at(fh, fh->dict_last);
|
|
|
|
if (!last_word) { return FH_ERR_INTERNAL; }
|
|
|
|
|
|
|
|
last_word->handler = w_user_word;
|
|
|
|
last_word->param = fh->here;
|
|
|
|
last_word->flags = WORDFLAG_WORD | WORDFLAG_CREATED;
|
|
|
|
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_forget(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
ENSURE_STATE(FH_STATE_INTERPRET);
|
|
|
|
|
|
|
|
char *wordname = NULL;
|
|
|
|
size_t namelen = 0;
|
|
|
|
fh_input_consume_spaces(fh);
|
|
|
|
TRY(fh_input_read_word(fh, &wordname, &namelen));
|
|
|
|
LOG("Name to forget: %.*s", (int) namelen, wordname);
|
|
|
|
|
|
|
|
uint32_t addr;
|
|
|
|
TRY(fh_find_word(fh, wordname, namelen, &addr));
|
|
|
|
|
|
|
|
struct fh_word_s *removedword = fh_word_at(fh, addr);
|
|
|
|
if (!removedword) { return FH_ERR_INTERNAL; }
|
|
|
|
fh->dict_last = removedword->previous;
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_postpone(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
ENSURE_STATE(FH_STATE_COMPILE);
|
|
|
|
|
|
|
|
char *wordname;
|
|
|
|
size_t namelen = 0;
|
|
|
|
fh_input_consume_spaces(fh);
|
|
|
|
TRY(fh_input_read_word(fh, &wordname, &namelen));
|
|
|
|
TRY(fh_postpone_word(fh, wordname, namelen));
|
|
|
|
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error rt_read_value(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
enum fh_error rv;
|
|
|
|
TRY(ds_push(fh, w->param));
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error rt_read_varaddr(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
enum fh_error rv;
|
|
|
|
uint32_t addr = (void *) &w->param - (void *) &fh->heap[0]; // this is ugly
|
|
|
|
TRY(ds_push(fh, addr));
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error wp_variable(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
|
|
|
|
bool is_value = w->param == 1;
|
|
|
|
bool is_const = w->param == 2;
|
|
|
|
|
|
|
|
if (is_const && fh->state == FH_STATE_COMPILE) {
|
|
|
|
uint32_t wordaddr = (void *) w - (void *) &fh->heap[0]; // this is ugly
|
|
|
|
TRY(fh_put_instr(fh, FH_INSTR_WORD, wordaddr));
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
ENSURE_STATE(FH_STATE_INTERPRET);
|
|
|
|
|
|
|
|
char *wordname;
|
|
|
|
size_t namelen = 0;
|
|
|
|
fh_input_consume_spaces(fh);
|
|
|
|
TRY(fh_input_read_word(fh, &wordname, &namelen));
|
|
|
|
|
|
|
|
uint32_t ptr;
|
|
|
|
uint32_t value = 0;
|
|
|
|
|
|
|
|
if (is_value || is_const) {
|
|
|
|
TRY(ds_pop(fh, &value));
|
|
|
|
}
|
|
|
|
|
|
|
|
fh_align(fh);
|
|
|
|
TRY(fh_heap_reserve(fh, DICTWORD_SIZE, &ptr));
|
|
|
|
|
|
|
|
struct fh_word_s *new_word = fh_word_at(fh, ptr);
|
|
|
|
if (!new_word) { return FH_ERR_INTERNAL; }
|
|
|
|
new_word->previous = fh->dict_last;
|
|
|
|
new_word->param = value;
|
|
|
|
new_word->handler = (is_value || is_const) ? rt_read_value : rt_read_varaddr;
|
|
|
|
strncpy(new_word->name, wordname, namelen);
|
|
|
|
new_word->name[namelen] = 0;
|
|
|
|
new_word->flags = (is_const ? WORDFLAG_CONSTANT : WORDFLAG_VARIABLE) | WORDFLAG_BUILTIN;
|
|
|
|
|
|
|
|
if (is_const) {
|
|
|
|
LOG("Create CONSTANT %s = %d (0x%08x)", new_word->name, value, value);
|
|
|
|
} else if (is_value) {
|
|
|
|
LOG("Create VALUE %s = %d (0x%08x)", new_word->name, value, value);
|
|
|
|
} else {
|
|
|
|
LOG("Create VARIABLE %s at 0x%08x", new_word->name, ptr); // param field is the first
|
|
|
|
}
|
|
|
|
|
|
|
|
fh->dict_last = ptr;
|
|
|
|
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_to(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
if (fh->state == FH_STATE_INTERPRET) {
|
|
|
|
uint32_t value;
|
|
|
|
TRY(ds_pop(fh, &value));
|
|
|
|
|
|
|
|
char *wordname;
|
|
|
|
size_t namelen = 0;
|
|
|
|
uint32_t waddr;
|
|
|
|
fh_input_consume_spaces(fh);
|
|
|
|
TRY(fh_input_read_word(fh, &wordname, &namelen));
|
|
|
|
TRY(fh_find_word(fh, wordname, namelen, &waddr));
|
|
|
|
|
|
|
|
struct fh_word_s *ww = fh_word_at(fh, waddr);
|
|
|
|
if (!ww) { return FH_ERR_INTERNAL; }
|
|
|
|
|
|
|
|
if (ww->flags & WORDFLAG_WORD) {
|
|
|
|
LOGE("Cannot assign to dictionary word param field!");
|
|
|
|
return FH_ERR_NOT_APPLICABLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ww->flags & WORDFLAG_CONSTANT) {
|
|
|
|
LOGE("Cannot assign to constant!");
|
|
|
|
return FH_ERR_NOT_APPLICABLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
ww->param = value;
|
|
|
|
} else if (fh->state == FH_STATE_COMPILE) {
|
|
|
|
// immediate
|
|
|
|
char *wordname;
|
|
|
|
size_t namelen = 0;
|
|
|
|
uint32_t waddr;
|
|
|
|
fh_input_consume_spaces(fh);
|
|
|
|
TRY(fh_input_read_word(fh, &wordname, &namelen));
|
|
|
|
TRY(fh_find_word(fh, wordname, namelen, &waddr));
|
|
|
|
|
|
|
|
TRY(fh_put_instr(fh, FH_INSTR_TO, waddr));
|
|
|
|
}
|
|
|
|
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_leftbracket(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
ENSURE_STATE(FH_STATE_COMPILE);
|
|
|
|
fh_setstate(fh, FH_STATE_INTERPRET, 0);
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_rightbracket(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
ENSURE_STATE(FH_STATE_INTERPRET);
|
|
|
|
fh_setstate(fh, FH_STATE_COMPILE, 0);
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_source(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
TRY(ds_push(fh, fh->inputaddr));
|
|
|
|
TRY(ds_push(fh, fh->inputlen));
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_literal(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
ENSURE_STATE(FH_STATE_COMPILE);
|
|
|
|
|
|
|
|
uint32_t val;
|
|
|
|
TRY(ds_pop(fh, &val));
|
|
|
|
TRY(fh_put_instr(fh, FH_INSTR_NUMBER, val));
|
|
|
|
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_semicolon(struct fh_thread_s *fh, const struct fh_word_s *w0)
|
|
|
|
{
|
|
|
|
(void) w0;
|
|
|
|
enum fh_error rv;
|
|
|
|
|
|
|
|
ENSURE_STATE(FH_STATE_COMPILE);
|
|
|
|
TRY(fh_put_instr(fh, FH_INSTR_ENDWORD, 0));
|
|
|
|
|
|
|
|
/* Return to interpret state */
|
|
|
|
fh_setstate(fh, FH_STATE_INTERPRET, 0);
|
|
|
|
|
|
|
|
// XXX if there was another definition previously and it was used in some other compiled function,
|
|
|
|
// that old implementation will still be called.
|
|
|
|
|
|
|
|
// unhide the entry, if hidden (colon does this to make the word unresolvable before it's finished)
|
|
|
|
struct fh_word_s *ww = fh_word_at(fh, fh->dict_last);
|
|
|
|
if (ww && (ww->flags & WORDFLAG_WORD) && ww->name[0] != 0) {
|
|
|
|
ww->flags &= ~WORDFLAG_HIDDEN;
|
|
|
|
}
|
|
|
|
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_compile_comma(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
|
|
|
|
uint32_t xt;
|
|
|
|
TRY(ds_pop(fh, &xt));
|
|
|
|
TRY(fh_put_instr(fh, FH_INSTR_WORD, xt));
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_immediate(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
if (fh->dict_last == 0) {
|
|
|
|
LOGE("Dict is empty, cannot modify previous word!");
|
|
|
|
return FH_ERR_INVALID_STATE;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct fh_word_s *word = fh_word_at(fh, fh->dict_last);
|
|
|
|
if (!word) { return FH_ERR_INTERNAL; }
|
|
|
|
word->flags |= WORDFLAG_IMMEDIATE;
|
|
|
|
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_backslash(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
fh_setsubstate(fh, FH_SUBSTATE_LINE_COMMENT);
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_paren(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
fh_setsubstate(fh, FH_SUBSTATE_PAREN_COMMENT);
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error wp_char(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
bool bracketed = w->param;
|
|
|
|
if (bracketed) ENSURE_STATE(FH_STATE_COMPILE);
|
|
|
|
|
|
|
|
char *wordname = NULL;
|
|
|
|
size_t namelen = 0;
|
|
|
|
fh_input_consume_spaces(fh);
|
|
|
|
TRY(fh_input_read_word(fh, &wordname, &namelen));
|
|
|
|
|
|
|
|
char c = (char) *wordname;
|
|
|
|
if (bracketed) {
|
|
|
|
TRY(fh_put_instr(fh, FH_INSTR_NUMBER, c));
|
|
|
|
} else {
|
|
|
|
TRY(ds_push(fh, c));
|
|
|
|
}
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_depth(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
TRY(ds_push(fh, fh->data_stack_top));
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_unused(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
TRY(ds_push(fh, HEAP_SIZE - fh->here));
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_to_in(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
TRY(ds_push(fh, MAGICADDR_INPTR));
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_to_body(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
|
|
|
|
uint32_t xt;
|
|
|
|
TRY(ds_pop(fh, &xt)); // xt is now a dict entry (hopefully)
|
|
|
|
TRY(ds_push(fh, xt + DICTWORD_SIZE));
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool chartest_equals_or_end(char c, void *param)
|
|
|
|
{
|
|
|
|
char cc = (char) *(uint32_t *) param;
|
|
|
|
return c == cc || c == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static enum fh_error w_word(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
uint32_t ch;
|
|
|
|
TRY(ds_pop(fh, &ch));
|
|
|
|
if (ch > 0xFF) {
|
|
|
|
LOGE("Char out of ASCII bounds!");
|
|
|
|
return FH_ERR_NOT_APPLICABLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
fh_input_consume_matching(fh, chartest_equals_or_end, &ch);
|
|
|
|
|
|
|
|
char *out;
|
|
|
|
size_t len;
|
|
|
|
fh_input_read_delimited(fh, &out, &len, chartest_equals_or_end, &ch);
|
|
|
|
if (len >= WORDBUF_SIZE) {
|
|
|
|
LOGE("WORD parsed string too long");
|
|
|
|
return FH_ERR_NAME_TOO_LONG;
|
|
|
|
}
|
|
|
|
|
|
|
|
fh_store_char(fh, WORDBUF_ADDR, (char) len);
|
|
|
|
fh_heap_copyptr(fh, WORDBUF_ADDR + 1, out, len);
|
|
|
|
|
|
|
|
LOG("Word found: \"%.*s\"", (int) len, out);
|
|
|
|
|
|
|
|
TRY(ds_push(fh, WORDBUF_ADDR));
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_count(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
uint32_t caddr;
|
|
|
|
TRY(ds_pop(fh, &caddr));
|
|
|
|
|
|
|
|
uint8_t len;
|
|
|
|
TRY(fh_fetch_char(fh, caddr, (char *) &len));
|
|
|
|
TRY(ds_push(fh, caddr + 1));
|
|
|
|
TRY(ds_push(fh, len));
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_create(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
ENSURE_STATE(FH_STATE_INTERPRET);
|
|
|
|
|
|
|
|
char *wordname;
|
|
|
|
size_t namelen = 0;
|
|
|
|
fh_input_consume_spaces(fh);
|
|
|
|
TRY(fh_input_read_word(fh, &wordname, &namelen));
|
|
|
|
|
|
|
|
uint32_t ptr;
|
|
|
|
|
|
|
|
TRY(fh_heap_reserve(fh, DICTWORD_SIZE, &ptr));
|
|
|
|
|
|
|
|
struct fh_word_s *new_word = fh_word_at(fh, ptr);
|
|
|
|
if (!new_word) { return FH_ERR_INTERNAL; }
|
|
|
|
new_word->previous = fh->dict_last;
|
|
|
|
new_word->param = fh->here;
|
|
|
|
new_word->handler = rt_read_value;
|
|
|
|
strncpy(new_word->name, wordname, namelen);
|
|
|
|
new_word->name[namelen] = 0;
|
|
|
|
new_word->flags = WORDFLAG_BUILTIN | WORDFLAG_CREATED;
|
|
|
|
|
|
|
|
fh->dict_last = ptr;
|
|
|
|
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_defer(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
ENSURE_STATE(FH_STATE_INTERPRET);
|
|
|
|
|
|
|
|
char *wordname;
|
|
|
|
size_t namelen = 0;
|
|
|
|
fh_input_consume_spaces(fh);
|
|
|
|
TRY(fh_input_read_word(fh, &wordname, &namelen));
|
|
|
|
|
|
|
|
uint32_t ptr;
|
|
|
|
|
|
|
|
fh_align(fh);
|
|
|
|
TRY(fh_heap_reserve(fh, DICTWORD_SIZE, &ptr));
|
|
|
|
|
|
|
|
struct fh_word_s *new_word = fh_word_at(fh, ptr);
|
|
|
|
if (!new_word) { return FH_ERR_INTERNAL; }
|
|
|
|
new_word->previous = fh->dict_last;
|
|
|
|
new_word->param = MAGICADDR_UNRESOLVED;
|
|
|
|
new_word->handler = rt_defer;
|
|
|
|
strncpy(new_word->name, wordname, namelen);
|
|
|
|
new_word->name[namelen] = 0;
|
|
|
|
new_word->flags = WORDFLAG_BUILTIN | WORDFLAG_DEFER;
|
|
|
|
|
|
|
|
fh->dict_last = ptr;
|
|
|
|
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_defer_store(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
|
|
|
|
uint32_t xt1, xt2;
|
|
|
|
TRY(ds_pop(fh, &xt1));
|
|
|
|
TRY(ds_pop(fh, &xt2));
|
|
|
|
|
|
|
|
struct fh_word_s *ww = fh_word_at(fh, xt1);
|
|
|
|
if (0 == (ww->flags & WORDFLAG_DEFER)) {
|
|
|
|
LOGE("%s is not DEFER!", ww->name);
|
|
|
|
return FH_ERR_BAD_DEFER;
|
|
|
|
}
|
|
|
|
|
|
|
|
ww->param = xt2;
|
|
|
|
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_defer_fetch(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
|
|
|
|
uint32_t xt1;
|
|
|
|
TRY(ds_pop(fh, &xt1));
|
|
|
|
|
|
|
|
struct fh_word_s *ww = fh_word_at(fh, xt1);
|
|
|
|
if (0 == (ww->flags & WORDFLAG_DEFER)) {
|
|
|
|
LOGE("%s is not DEFER!", ww->name);
|
|
|
|
return FH_ERR_BAD_DEFER;
|
|
|
|
}
|
|
|
|
|
|
|
|
TRY(ds_push(fh, ww->param));
|
|
|
|
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_is(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
|
|
|
|
char *wordname;
|
|
|
|
size_t namelen = 0;
|
|
|
|
fh_input_consume_spaces(fh);
|
|
|
|
TRY(fh_input_read_word(fh, &wordname, &namelen));
|
|
|
|
|
|
|
|
uint32_t addr;
|
|
|
|
if (FH_OK != fh_find_word(fh, wordname, namelen, &addr)) {
|
|
|
|
LOGE("' %.*s word not found!", (int) namelen, wordname);
|
|
|
|
return FH_ERR_UNKNOWN_WORD;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct fh_word_s *ww = fh_word_at(fh, addr);
|
|
|
|
if (0 == (ww->flags & WORDFLAG_DEFER)) {
|
|
|
|
LOGE("%s is not DEFER!", ww->name);
|
|
|
|
return FH_ERR_BAD_DEFER;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fh->state == FH_STATE_COMPILE) {
|
|
|
|
TRY(fh_put_instr(fh, FH_INSTR_ISDEFER, addr));
|
|
|
|
} else {
|
|
|
|
TRY(ds_pop(fh, &ww->param));
|
|
|
|
}
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_action_of(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
|
|
|
|
char *wordname;
|
|
|
|
size_t namelen = 0;
|
|
|
|
fh_input_consume_spaces(fh);
|
|
|
|
TRY(fh_input_read_word(fh, &wordname, &namelen));
|
|
|
|
|
|
|
|
uint32_t addr;
|
|
|
|
if (FH_OK != fh_find_word(fh, wordname, namelen, &addr)) {
|
|
|
|
LOGE("' %.*s word not found!", (int) namelen, wordname);
|
|
|
|
return FH_ERR_UNKNOWN_WORD;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct fh_word_s *ww = fh_word_at(fh, addr);
|
|
|
|
if (0 == (ww->flags & WORDFLAG_DEFER)) {
|
|
|
|
LOGE("%s is not DEFER!", ww->name);
|
|
|
|
return FH_ERR_BAD_DEFER;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fh->state == FH_STATE_COMPILE) {
|
|
|
|
TRY(fh_put_instr(fh, FH_INSTR_ACTIONOF, addr));
|
|
|
|
} else {
|
|
|
|
TRY(ds_push(fh, ww->param));
|
|
|
|
}
|
|
|
|
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_find(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
ENSURE_STATE(FH_STATE_INTERPRET);
|
|
|
|
|
|
|
|
uint32_t caddr;
|
|
|
|
TRY(ds_pop(fh, &caddr));
|
|
|
|
uint8_t len;
|
|
|
|
TRY(fh_fetch_char(fh, caddr, (char *) &len));
|
|
|
|
|
|
|
|
uint32_t addr;
|
|
|
|
|
|
|
|
if (FH_OK != fh_find_word(fh, fh_str_at(fh, caddr + 1), len, &addr)) {
|
|
|
|
/* not found */
|
|
|
|
TRY(ds_push(fh, caddr));
|
|
|
|
TRY(ds_push(fh, 0));
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct fh_word_s *word = fh_word_at(fh, addr);
|
|
|
|
if (!word) { return FH_ERR_INTERNAL; }
|
|
|
|
|
|
|
|
TRY(ds_push(fh, addr));
|
|
|
|
TRY(ds_push(fh, (word->flags & WORDFLAG_IMMEDIATE) ? 1 : -1));
|
|
|
|
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error wp_tick(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
bool bracketed = w->param;
|
|
|
|
if (bracketed) ENSURE_STATE(FH_STATE_COMPILE);
|
|
|
|
|
|
|
|
char *wordname;
|
|
|
|
size_t namelen = 0;
|
|
|
|
fh_input_consume_spaces(fh);
|
|
|
|
TRY(fh_input_read_word(fh, &wordname, &namelen));
|
|
|
|
|
|
|
|
uint32_t addr;
|
|
|
|
if (FH_OK != fh_find_word(fh, wordname, namelen, &addr)) {
|
|
|
|
LOGE("' %.*s word not found!", (int) namelen, wordname);
|
|
|
|
return FH_ERR_UNKNOWN_WORD;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bracketed) {
|
|
|
|
TRY(fh_put_instr(fh, FH_INSTR_NUMBER, addr));
|
|
|
|
} else {
|
|
|
|
TRY(ds_push(fh, addr));
|
|
|
|
}
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_execute(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
|
|
|
|
uint32_t addr;
|
|
|
|
TRY(ds_pop(fh, &addr));
|
|
|
|
|
|
|
|
if (addr != WORDALIGNED(addr)) {
|
|
|
|
LOGE("Invalid execution token, address must be aligned!");
|
|
|
|
return FH_ERR_ILLEGAL_FETCH;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct fh_word_s *word = fh_word_at(fh, addr);
|
|
|
|
if (!word) {
|
|
|
|
LOGE("Execute with bad addr");
|
|
|
|
return FH_ERR_NOT_APPLICABLE;
|
|
|
|
}
|
|
|
|
if (!word->handler) {
|
|
|
|
LOGE("Execute word with no handler");
|
|
|
|
return FH_ERR_NOT_APPLICABLE;
|
|
|
|
}
|
|
|
|
TRY(word->handler(fh, word));
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_evaluate(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
uint32_t addr, count;
|
|
|
|
TRY(ds_pop_addr_len(fh, &addr, &count));
|
|
|
|
return fh_runtime_start(fh, fh_create_input_from_string(fh_str_at(fh, addr), count, fh->input->cwd));
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_save_input(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
fh->input->save_input(fh, fh->input);
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_restore_input(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
fh->input->restore_input(fh, fh->input);
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_included(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
uint32_t addr, count;
|
|
|
|
TRY(ds_pop_addr_len(fh, &addr, &count));
|
|
|
|
|
|
|
|
if (count > 99) {
|
|
|
|
LOGE("Filename too long for INCLUDED");
|
|
|
|
return FH_ERR_NOT_APPLICABLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
char tmp[100];
|
|
|
|
strncpy(tmp, fh_str_at(fh, addr), count);
|
|
|
|
tmp[count] = 0;
|
|
|
|
|
|
|
|
fh_runtime_start(fh, fh_create_input_from_filename(tmp));
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_include(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
|
|
|
|
char *wordname;
|
|
|
|
size_t namelen = 0;
|
|
|
|
fh_input_consume_spaces(fh);
|
|
|
|
TRY(fh_input_read_word(fh, &wordname, &namelen));
|
|
|
|
|
|
|
|
if (namelen > 99) {
|
|
|
|
LOGE("Filename too long for INCLUDED");
|
|
|
|
return FH_ERR_NOT_APPLICABLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
char tmp[100];
|
|
|
|
strncpy(tmp, wordname, namelen);
|
|
|
|
tmp[namelen] = 0;
|
|
|
|
|
|
|
|
fh_runtime_start(fh, fh_create_input_from_filename(tmp));
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_env_query(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
|
|
|
|
uint32_t len;
|
|
|
|
TRY(ds_pop(fh, &len));
|
|
|
|
uint32_t addr;
|
|
|
|
TRY(ds_pop(fh, &addr));
|
|
|
|
const char *str = fh_str_at(fh, addr);
|
|
|
|
if (!str) {
|
|
|
|
LOGE("Bad string addr for env query!");
|
|
|
|
return FH_ERR_NOT_APPLICABLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG("Test environment \"%.*s\"", len, str);
|
|
|
|
|
|
|
|
if (EQ(str, "/COUNTED-STRING", len)) {
|
|
|
|
TRY(ds_push(fh, 255));
|
|
|
|
TRY(ds_push(fh, 1));
|
|
|
|
} else if (EQ(str, "/HOLD", len)) {
|
|
|
|
TRY(ds_push(fh, WORDBUF_SIZE));
|
|
|
|
TRY(ds_push(fh, 1));
|
|
|
|
} else if (EQ(str, "/PAD", len)) {
|
|
|
|
TRY(ds_push(fh, MIN_PAD_SIZE));
|
|
|
|
TRY(ds_push(fh, 1));
|
|
|
|
} else if (EQ(str, "ADDRESS-UNIT-BITS", len)) {
|
|
|
|
TRY(ds_push(fh, 8));
|
|
|
|
TRY(ds_push(fh, 1));
|
|
|
|
} else if (EQ(str, "FLOORED", len)) {
|
|
|
|
TRY(ds_push(fh, TOBOOL(1)));
|
|
|
|
TRY(ds_push(fh, 1));
|
|
|
|
} else if (EQ(str, "MAX-CHAR", len)) {
|
|
|
|
TRY(ds_push(fh, 255));
|
|
|
|
TRY(ds_push(fh, 1));
|
|
|
|
} else if (EQ(str, "MAX-D", len)) {
|
|
|
|
TRY(ds_push_dw(fh, 0x7FFFFFFFFFFFFFFFULL));
|
|
|
|
} else if (EQ(str, "MAX-UD", len)) {
|
|
|
|
TRY(ds_push_dw(fh, 0xFFFFFFFFFFFFFFFFULL));
|
|
|
|
} else if (EQ(str, "MAX-N", len)) {
|
|
|
|
TRY(ds_push(fh, 0x7FFFFFFFULL));
|
|
|
|
TRY(ds_push(fh, 1));
|
|
|
|
} else if (EQ(str, "MAX-U", len)) {
|
|
|
|
TRY(ds_push(fh, 0xFFFFFFFFULL));
|
|
|
|
TRY(ds_push(fh, 1));
|
|
|
|
} else if (EQ(str, "RETURN-STACK-CELLS", len)) {
|
|
|
|
TRY(ds_push(fh, RETURN_STACK_DEPTH));
|
|
|
|
TRY(ds_push(fh, 1));
|
|
|
|
} else if (EQ(str, "STACK-CELLS", len)) {
|
|
|
|
TRY(ds_push(fh, DATA_STACK_DEPTH));
|
|
|
|
TRY(ds_push(fh, 1));
|
|
|
|
} else if (EQ(str, "CORE", len)) {
|
|
|
|
TRY(ds_push(fh, TOBOOL(1)));
|
|
|
|
TRY(ds_push(fh, 1));
|
|
|
|
} else {
|
|
|
|
TRY(ds_push(fh, 0));
|
|
|
|
}
|
|
|
|
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_parse(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
uint32_t ch;
|
|
|
|
TRY(ds_pop(fh, &ch));
|
|
|
|
if (ch > 0xFF) {
|
|
|
|
LOGE("Char out of ASCII bounds!");
|
|
|
|
return FH_ERR_NOT_APPLICABLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *out;
|
|
|
|
size_t len;
|
|
|
|
fh_input_read_delimited(fh, &out, &len, chartest_equals_or_end, &ch);
|
|
|
|
if (len >= WORDBUF_SIZE) {
|
|
|
|
LOGE("PARSE parsed string too long: %d", len);
|
|
|
|
return FH_ERR_NAME_TOO_LONG;
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG("Parsed %d", len);
|
|
|
|
|
|
|
|
TRY(ds_push(fh, (void*) out - (void*) &fh->heap[0]));
|
|
|
|
TRY(ds_push(fh, len));
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_parse_name(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
|
|
|
|
fh_input_consume_spaces(fh);
|
|
|
|
|
|
|
|
char * ww;
|
|
|
|
size_t len;
|
|
|
|
TRY(fh_input_read_word(fh, &ww, &len));
|
|
|
|
|
|
|
|
LOG("Parsed %.*s", len, ww);
|
|
|
|
|
|
|
|
TRY(ds_push(fh, (void*) ww - (void*) &fh->heap[0]));
|
|
|
|
TRY(ds_push(fh, len));
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum fh_error w_refill(struct fh_thread_s *fh, const struct fh_word_s *w)
|
|
|
|
{
|
|
|
|
(void) w;
|
|
|
|
enum fh_error rv;
|
|
|
|
bool suc = fh->input->refill_input_buffer(fh, fh->input);
|
|
|
|
if (fh->input->source_id == -1) suc = false; // string always says it can't refill
|
|
|
|
TRY(ds_push(fh, TOBOOL(suc)));
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
const struct name_and_handler fh_builtins_meta[] = {
|
|
|
|
{"depth", w_depth, 0, 0},
|
|
|
|
{"unused", w_unused, 0, 0},
|
|
|
|
{">in", w_to_in, 0, 0},
|
|
|
|
{">body", w_to_body, 0, 0},
|
|
|
|
{":", w_colon, 0, 0},
|
|
|
|
{":noname", w_colon_noname, 0, 0},
|
|
|
|
{"does>", w_does, 1, 0},
|
|
|
|
{";", w_semicolon, 1, 0},
|
|
|
|
{"forget", w_forget, 1, 0},
|
|
|
|
{"\\", w_backslash, 1, 0}, // line comment
|
|
|
|
{"(", w_paren, 1, 0}, // enclosed comment
|
|
|
|
{"immediate", w_immediate, 0, 0},
|
|
|
|
{"postpone", w_postpone, 1, 0},
|
|
|
|
{"[", w_leftbracket, 1, 0},
|
|
|
|
{"]", w_rightbracket, 1, 0},
|
|
|
|
{"source", w_source, 0, 0},
|
|
|
|
{"literal", w_literal, 1, 0},
|
|
|
|
{"char", wp_char, 0, 0},
|
|
|
|
{"[char]", wp_char, 1, 1},
|
|
|
|
{"to", w_to, 1, 0},
|
|
|
|
{"variable", wp_variable, 1, 0},
|
|
|
|
{"value", wp_variable, 1, 1},
|
|
|
|
{"constant", wp_variable, 1, 2},
|
|
|
|
{"word", w_word, 0, 0},
|
|
|
|
{"parse", w_parse, 0, 0},
|
|
|
|
{"parse-name", w_parse_name, 0, 0},
|
|
|
|
{"count", w_count, 0, 0},
|
|
|
|
{"create", w_create, 0, 0},
|
|
|
|
{"defer", w_defer, 0, 0},
|
|
|
|
{"defer!", w_defer_store, 0, 0},
|
|
|
|
{"defer@", w_defer_fetch, 0, 0},
|
|
|
|
{"action-of", w_action_of, 1, 0}, // imm because it has special compile behavior
|
|
|
|
{"is", w_is, 1, 0},
|
|
|
|
{"find", w_find, 0, 0},
|
|
|
|
{"'", wp_tick, 1, 0},
|
|
|
|
{"[']", wp_tick, 1, 1},
|
|
|
|
{"execute", w_execute, 0, 0},
|
|
|
|
{"save-input", w_save_input, 0, 0},
|
|
|
|
{"restore-input",w_restore_input, 0, 0},
|
|
|
|
{"environment?", w_env_query, 0, 0},
|
|
|
|
{"marker", w_marker, 0, 0},
|
|
|
|
{"compile,", w_compile_comma, 0, 0},
|
|
|
|
{"evaluate", w_evaluate, 0, 0},
|
|
|
|
{"included", w_included, 0, 0},
|
|
|
|
{"include", w_include, 0, 0},
|
|
|
|
{"refill", w_refill, 0, 0},
|
|
|
|
{ /* end marker */ }
|
|
|
|
};
|