You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
390 lines
9.9 KiB
390 lines
9.9 KiB
3 years ago
|
#include <string.h>
|
||
|
#include <errno.h>
|
||
|
#include <ctype.h>
|
||
|
|
||
3 years ago
|
#include "forth.h"
|
||
|
#include "fh_runtime.h"
|
||
|
#include "fh_builtins.h"
|
||
|
#include "fh_stack.h"
|
||
|
#include "fh_mem.h"
|
||
3 years ago
|
#include "fh_globals.h"
|
||
|
#include "fh_print.h"
|
||
3 years ago
|
|
||
|
struct fh_global_s fh_globals = {};
|
||
|
|
||
|
/** Error names */
|
||
|
static const char *errornames[] = {
|
||
|
[FH_OK] = "OK",
|
||
|
[FH_ERR_CS_OVERFLOW] = "CS_OVERFLOW",
|
||
|
[FH_ERR_DS_OVERFLOW] = "DS_OVERFLOW",
|
||
|
[FH_ERR_RS_OVERFLOW] = "RS_OVERFLOW",
|
||
|
[FH_ERR_CS_UNDERFLOW] = "CS_UNDERFLOW",
|
||
|
[FH_ERR_DS_UNDERFLOW] = "DS_UNDERFLOW",
|
||
|
[FH_ERR_RS_UNDERFLOW] = "RS_UNDERFLOW",
|
||
|
[FH_ERR_HEAP_FULL] = "HEAP_FULL",
|
||
|
[FH_ERR_DICT_FULL] = "DICT_FULL",
|
||
|
[FH_ERR_COMPILE_FULL] = "COMPILE_FULL",
|
||
|
[FH_ERR_NAME_TOO_LONG] = "NAME_TOO_LONG",
|
||
|
[FH_ERR_INVALID_STATE] = "INVALID_STATE",
|
||
|
[FH_ERR_INTERNAL] = "INTERNAL",
|
||
|
[FH_ERR_UNKNOWN_WORD] = "UNKNOWN_WORD",
|
||
|
};
|
||
|
|
||
|
/** Get error name from code, returns Unknown if not defined */
|
||
|
const char *fherr_name(enum fh_error e)
|
||
|
{
|
||
|
if (e >= FH_ERR_MAX) {
|
||
|
return "Unknown";
|
||
|
}
|
||
|
return errornames[e];
|
||
|
}
|
||
|
|
||
|
/** State names */
|
||
|
static const char *statenames[] = {
|
||
|
[FH_STATE_INTERPRET] = "INTERPRET",
|
||
|
[FH_STATE_COMPILE] = "COMPILE",
|
||
|
[FH_STATE_SHUTDOWN] = "SHUTDOWN",
|
||
|
};
|
||
|
|
||
|
|
||
|
/** Sub-state names */
|
||
|
static const char *substatenames[] = {
|
||
|
[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);
|
||
|
}
|
||
|
|
||
|
enum fh_error w_user_word(struct fh_thread_s *fh)
|
||
|
{
|
||
|
enum fh_error rv;
|
||
|
const struct fh_word_s *w;
|
||
|
const struct fh_word_s *w2;
|
||
|
uint32_t wn;
|
||
|
|
||
|
call:
|
||
|
w = fh->exec_word;
|
||
|
if (!w) { return FH_ERR_INTERNAL; }
|
||
|
|
||
|
LOG("Run user word: %s", w->name);
|
||
|
|
||
|
TRY(rs_push(fh, fh->execptr));
|
||
|
fh->execptr = w->start;
|
||
|
|
||
|
instr:;
|
||
|
// 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);
|
||
|
goto instr;
|
||
|
} else {
|
||
|
LOG("Exec: user-word %s (CALL)", w2->name);
|
||
|
fh->exec_word = &fh->dict[instr->data];
|
||
|
goto call;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
end:
|
||
|
return FH_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
/** Initialize a runtime */
|
||
3 years ago
|
enum fh_error fh_init(struct fh_thread_s *fh)
|
||
3 years ago
|
{
|
||
|
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;
|
||
|
return FH_OK;
|
||
|
}
|
||
|
|
||
|
/** Process a quoted string read from input */
|
||
|
static enum fh_error fh_handle_quoted_string(
|
||
|
struct fh_thread_s *fh,
|
||
3 years ago
|
const char *start,
|
||
3 years ago
|
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,
|
||
3 years ago
|
const char *start,
|
||
3 years ago
|
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);
|
||
|
fh->exec_word = w;
|
||
|
TRY(w->handler(fh));
|
||
|
}
|
||
|
return FH_OK;
|
||
|
}
|
||
|
w++;
|
||
|
cnt++;
|
||
|
}
|
||
|
|
||
|
/* word not found, try parsing as number */
|
||
|
errno = 0;
|
||
|
char *endptr;
|
||
|
long v = strtol(start, &endptr, 0);
|
||
|
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 */
|
||
3 years ago
|
enum fh_error fh_process_line(struct fh_thread_s *fh, const char *linebuf)
|
||
3 years ago
|
{
|
||
|
enum fh_error rv;
|
||
3 years ago
|
const char *rp = linebuf;
|
||
3 years ago
|
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;
|
||
|
}
|