|
|
|
#include <string.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
|
|
|
|
#include "fh_error.h"
|
|
|
|
#include "fh_runtime.h"
|
|
|
|
#include "fh_builtins.h"
|
|
|
|
#include "fh_stack.h"
|
|
|
|
#include "fh_mem.h"
|
|
|
|
#include "fh_globals.h"
|
|
|
|
#include "fh_print.h"
|
|
|
|
|
|
|
|
struct fh_global_s fh_globals = {};
|
|
|
|
|
|
|
|
/** State names */
|
|
|
|
static const char *statenames[FH_STATE_MAX] = {
|
|
|
|
[FH_STATE_INTERPRET] = "INTERPRET",
|
|
|
|
[FH_STATE_COMPILE] = "COMPILE",
|
|
|
|
[FH_STATE_SHUTDOWN] = "SHUTDOWN",
|
|
|
|
};
|
|
|
|
|
|
|
|
/** Sub-state names */
|
|
|
|
static const char *substatenames[FH_SUBSTATE_MAX] = {
|
|
|
|
[FH_SUBSTATE_NONE] = "NONE",
|
|
|
|
[FH_SUBSTATE_COLONNAME] = "COLONNAME",
|
|
|
|
[FH_SUBSTATE_SQUOTE] = "SQUOTE",
|
|
|
|
[FH_SUBSTATE_DOTQUOTE] = "DOTQUOTE",
|
|
|
|
[FH_SUBSTATE_PARENCOMMENT] = "PARENCOMMENT",
|
|
|
|
[FH_SUBSTATE_LINECOMMENT] = "LINECOMMENT",
|
|
|
|
};
|
|
|
|
|
|
|
|
/** Add a word to the dictionary. */
|
|
|
|
enum fh_error fh_add_word(const struct fh_word_s *w, struct fh_thread_s *fh)
|
|
|
|
{
|
|
|
|
if (fh->dict_top == DICT_SIZE) {
|
|
|
|
return FH_ERR_DICT_FULL;
|
|
|
|
}
|
|
|
|
memcpy(&fh->dict[fh->dict_top++], w, sizeof(struct fh_word_s));
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Log current runtime state */
|
|
|
|
static void showstate(const struct fh_thread_s *fh)
|
|
|
|
{
|
|
|
|
if (fh->substate == 0) {
|
|
|
|
LOG("state = %s", statenames[fh->state]);
|
|
|
|
} else {
|
|
|
|
LOG("state = %s.%s", statenames[fh->state], substatenames[fh->substate]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Set runtime state and sub-state */
|
|
|
|
void fh_setstate(struct fh_thread_s *fh, enum fh_state state, enum fh_substate substate)
|
|
|
|
{
|
|
|
|
fh->state = state;
|
|
|
|
fh->substate = substate;
|
|
|
|
showstate(fh);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Set runtime sub-state (state is unchanged) */
|
|
|
|
void fh_setsubstate(struct fh_thread_s *fh, enum fh_substate substate)
|
|
|
|
{
|
|
|
|
fh->substate = substate;
|
|
|
|
showstate(fh);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Execute a user word */
|
|
|
|
enum fh_error w_user_word(struct fh_thread_s *fh, const struct fh_word_s *w0)
|
|
|
|
{
|
|
|
|
enum fh_error rv;
|
|
|
|
const struct fh_word_s *w;
|
|
|
|
const struct fh_word_s *w2;
|
|
|
|
uint32_t wn;
|
|
|
|
|
|
|
|
w = w0;
|
|
|
|
call:
|
|
|
|
if (!w) { return FH_ERR_INTERNAL; }
|
|
|
|
|
|
|
|
LOG("Run user word: %s", w->name);
|
|
|
|
|
|
|
|
TRY(rs_push(fh, fh->execptr));
|
|
|
|
fh->execptr = w->start;
|
|
|
|
|
|
|
|
instr:;
|
|
|
|
if (fh->state == FH_STATE_QUIT) {
|
|
|
|
/* abort or quit was called, return to interactive mode */
|
|
|
|
fh_setstate(fh, FH_STATE_INTERPRET, FH_SUBSTATE_NONE);
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
// make sure it's aligned
|
|
|
|
fh->execptr = WORDALIGNED(fh->execptr);
|
|
|
|
const struct fh_instruction_s *instr = (const struct fh_instruction_s *) &fh->compile[fh->execptr];
|
|
|
|
fh->execptr += sizeof(struct fh_instruction_s);
|
|
|
|
|
|
|
|
uint32_t strl;
|
|
|
|
uint32_t addr = 0;
|
|
|
|
switch (instr->kind) {
|
|
|
|
case FH_INSTR_NUMBER:
|
|
|
|
TRY(ds_push(fh, instr->data));
|
|
|
|
goto instr;
|
|
|
|
|
|
|
|
case FH_INSTR_WORD:
|
|
|
|
wn = instr->data;
|
|
|
|
switch (wn) {
|
|
|
|
/* special case for strings stored in compile memory */
|
|
|
|
case CPLWORD_ALLOCSTR:
|
|
|
|
case CPLWORD_TYPESTR:
|
|
|
|
strl = *((uint32_t *) &fh->compile[fh->execptr]);
|
|
|
|
LOG("strl %d", strl);
|
|
|
|
fh->execptr += 4; // advance past the length
|
|
|
|
if (wn == CPLWORD_ALLOCSTR) {
|
|
|
|
TRY(fh_heap_reserve(fh, strl, &addr));
|
|
|
|
fh_heap_copy_from_compile(fh, addr, fh->execptr, strl);
|
|
|
|
LOG("Exec: alloc-str \"%.*s\"", strl, &fh->heap[addr]);
|
|
|
|
TRY(ds_push(fh, addr));
|
|
|
|
TRY(ds_push(fh, strl));
|
|
|
|
fh->execptr += strl;
|
|
|
|
} else {
|
|
|
|
FHPRINT("%.*s", (int) strl, &fh->compile[fh->execptr]);
|
|
|
|
LOG("Exec: type-str \"%.*s\"", strl, &fh->compile[fh->execptr]);
|
|
|
|
}
|
|
|
|
goto instr;
|
|
|
|
|
|
|
|
case CPLWORD_ENDWORD:
|
|
|
|
LOG("Exec: word-end (RETURN)");
|
|
|
|
TRY(rs_pop(fh, &fh->execptr));
|
|
|
|
if (fh->execptr == MAGICADDR_INTERACTIVE) {
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
goto instr;
|
|
|
|
|
|
|
|
default:
|
|
|
|
w2 = &fh->dict[instr->data];
|
|
|
|
if (w2->builtin) {
|
|
|
|
LOG("Exec: builtin-word %s", w2->name);
|
|
|
|
w2->handler(fh, w2);
|
|
|
|
goto instr;
|
|
|
|
} else {
|
|
|
|
LOG("Exec: user-word %s (CALL)", w2->name);
|
|
|
|
w = &fh->dict[instr->data];
|
|
|
|
goto call;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
end:
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Initialize a runtime */
|
|
|
|
enum fh_error fh_init(struct fh_thread_s *fh)
|
|
|
|
{
|
|
|
|
enum fh_error rv;
|
|
|
|
|
|
|
|
/* Make sure we have a clean state */
|
|
|
|
memset(fh, 0, sizeof(struct fh_thread_s));
|
|
|
|
|
|
|
|
TRY(register_builtin_words(fh));
|
|
|
|
|
|
|
|
fh->execptr = MAGICADDR_INTERACTIVE;
|
|
|
|
fh->base = 10;
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Process a quoted string read from input */
|
|
|
|
static enum fh_error fh_handle_quoted_string(
|
|
|
|
struct fh_thread_s *fh,
|
|
|
|
const char *start,
|
|
|
|
size_t len
|
|
|
|
)
|
|
|
|
{
|
|
|
|
enum fh_error rv;
|
|
|
|
uint32_t addr = 0;
|
|
|
|
struct fh_instruction_s instr;
|
|
|
|
|
|
|
|
if (fh->state == FH_STATE_INTERPRET) {
|
|
|
|
switch (fh->substate) {
|
|
|
|
case FH_SUBSTATE_SQUOTE:
|
|
|
|
TRY(fh_heap_put(fh, start, len));
|
|
|
|
TRY(ds_push(fh, addr));
|
|
|
|
TRY(ds_push(fh, len));
|
|
|
|
break;
|
|
|
|
case FH_SUBSTATE_DOTQUOTE:
|
|
|
|
FHPRINT("%.*s", (int) len, start);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
LOGE("Bad substate in interpret mode: %s", substatenames[fh->substate]);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
LOG("Compile a string");
|
|
|
|
/* compile */
|
|
|
|
if (fh->substate == FH_SUBSTATE_SQUOTE) {
|
|
|
|
instr_init(&instr, FH_INSTR_WORD, CPLWORD_ALLOCSTR);
|
|
|
|
} else {
|
|
|
|
instr_init(&instr, FH_INSTR_WORD, CPLWORD_TYPESTR);
|
|
|
|
}
|
|
|
|
uint32_t len32 = len;
|
|
|
|
/* string is encoded as a special compiler command, the size,
|
|
|
|
* and then the string, all 4-byte aligned. */
|
|
|
|
TRY(fh_compile_put(fh, &instr, INSTR_SIZE));
|
|
|
|
|
|
|
|
TRY(fh_compile_reserve(fh, len + 4, &addr));
|
|
|
|
fh_compile_write(fh, addr, &len32, 4);
|
|
|
|
fh_compile_write(fh, addr + 4, start, len);
|
|
|
|
}
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Process a word read from input */
|
|
|
|
static enum fh_error fh_handle_word(
|
|
|
|
struct fh_thread_s *fh,
|
|
|
|
const char *start,
|
|
|
|
size_t len
|
|
|
|
)
|
|
|
|
{
|
|
|
|
if (len >= MAX_NAME_LEN) {
|
|
|
|
return FH_ERR_NAME_TOO_LONG;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* First, try if it's a known word */
|
|
|
|
// TODO we could use binary search if the dict was ordered
|
|
|
|
struct fh_word_s *w = &fh->dict[0];
|
|
|
|
struct fh_instruction_s instr;
|
|
|
|
uint32_t cnt = 0;
|
|
|
|
enum fh_error rv;
|
|
|
|
while (w->handler) {
|
|
|
|
if (0 == strncasecmp(start, w->name, len) && w->name[len] == 0) {
|
|
|
|
// word found!
|
|
|
|
if (fh->state == FH_STATE_COMPILE && !w->immediate) {
|
|
|
|
LOG("Compile word call: %s", w->name);
|
|
|
|
instr_init(&instr, FH_INSTR_WORD, cnt);
|
|
|
|
TRY(fh_compile_put(fh, &instr, INSTR_SIZE));
|
|
|
|
} else {
|
|
|
|
/* interpret */
|
|
|
|
LOG("Interpret word: %s", w->name);
|
|
|
|
TRY(w->handler(fh, w));
|
|
|
|
}
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
w++;
|
|
|
|
cnt++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* word not found, try parsing as number */
|
|
|
|
errno = 0;
|
|
|
|
char *endptr;
|
|
|
|
long v = strtol(start, &endptr, (int) fh->base); // XXX if base is 0, this will use auto-detection
|
|
|
|
if (errno != 0 || endptr == start) {
|
|
|
|
LOGE("Unknown word and fail to parse as number: %.*s", (int) len, start);
|
|
|
|
return FH_ERR_UNKNOWN_WORD;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fh->state == FH_STATE_COMPILE) {
|
|
|
|
LOG("Compile number: %ld", v);
|
|
|
|
instr_init(&instr, FH_INSTR_NUMBER, (uint32_t) v);
|
|
|
|
TRY(fh_compile_put(fh, &instr, INSTR_SIZE));
|
|
|
|
} else {
|
|
|
|
/* interpret */
|
|
|
|
LOG("Interpret number: %ld", v);
|
|
|
|
TRY(ds_push(fh, (uint32_t) v));
|
|
|
|
}
|
|
|
|
|
|
|
|
return FH_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** True if the character is CR or LF */
|
|
|
|
static inline bool isnl(char c)
|
|
|
|
{
|
|
|
|
return c == '\n' || c == '\r';
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Process a line read from input */
|
|
|
|
enum fh_error fh_process_line(struct fh_thread_s *fh, const char *linebuf)
|
|
|
|
{
|
|
|
|
enum fh_error rv;
|
|
|
|
const char *rp = linebuf;
|
|
|
|
char c;
|
|
|
|
|
|
|
|
if (!fh_globals.interactive) {
|
|
|
|
LOGI("%s", linebuf);
|
|
|
|
}
|
|
|
|
|
|
|
|
while (0 != (c = *rp) && fh->state != FH_STATE_SHUTDOWN) {
|
|
|
|
/* end on newline */
|
|
|
|
if (isnl(c)) {
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
/* skip whitespace */
|
|
|
|
if (isspace(c)) {
|
|
|
|
rp++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *end;
|
|
|
|
size_t length;
|
|
|
|
switch (fh->substate) {
|
|
|
|
case FH_SUBSTATE_NONE:
|
|
|
|
case FH_SUBSTATE_COLONNAME:
|
|
|
|
/* try to read a word */
|
|
|
|
end = strchr(rp, ' ');
|
|
|
|
if (end) {
|
|
|
|
length = end - rp; /* exclude the space */
|
|
|
|
} else {
|
|
|
|
length = strlen(rp);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fh->substate == FH_SUBSTATE_NONE) {
|
|
|
|
/* eval a word */
|
|
|
|
LOG("Handle \"%.*s\"", (int) length, rp);
|
|
|
|
TRY(fh_handle_word(fh, rp, length));
|
|
|
|
} else {
|
|
|
|
/* new word's name is found */
|
|
|
|
LOG("New word name = \"%.*s\"", (int) length, rp);
|
|
|
|
strncpy(fh->dict[fh->dict_top].name, rp, length);
|
|
|
|
fh_setsubstate(fh, FH_SUBSTATE_NONE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (end) {
|
|
|
|
rp = end + 1;
|
|
|
|
} else {
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FH_SUBSTATE_SQUOTE:
|
|
|
|
case FH_SUBSTATE_DOTQUOTE:
|
|
|
|
end = strchr(rp, '"');
|
|
|
|
if (end) {
|
|
|
|
length = end - rp;
|
|
|
|
LOG("Quoted string: \"%.*s\"", (int) length, rp);
|
|
|
|
TRY(fh_handle_quoted_string(fh, rp, length));
|
|
|
|
fh_setsubstate(fh, FH_SUBSTATE_NONE);
|
|
|
|
rp = end + 1;
|
|
|
|
} else {
|
|
|
|
/* no end. this is weird. */
|
|
|
|
LOGE("Unterminated quoted string!");
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FH_SUBSTATE_PARENCOMMENT:
|
|
|
|
end = strchr(rp, ')');
|
|
|
|
if (end) {
|
|
|
|
LOG("Discard inline comment");
|
|
|
|
fh_setsubstate(fh, FH_SUBSTATE_NONE);
|
|
|
|
rp = end + 1;
|
|
|
|
} else {
|
|
|
|
/* no end, discard all */
|
|
|
|
LOGE("Unterminated parenthesis comment");
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FH_SUBSTATE_LINECOMMENT:
|
|
|
|
LOG("Discard line comment");
|
|
|
|
goto done; // just discard the rest
|
|
|
|
|
|
|
|
default:
|
|
|
|
LOGE("Bad substate %s", substatenames[fh->substate]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
done:
|
|
|
|
LOG("Line done.");
|
|
|
|
return FH_OK;
|
|
|
|
}
|