Trying to build a forth runtime in C
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.
forth/src/fh_runtime.c

390 lines
9.9 KiB

#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"
#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 */
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,
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,
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 */
enum fh_error fh_process_line(struct fh_thread_s *fh, const char *linebuf)
3 years ago
{
enum fh_error rv;
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;
}