diff --git a/.gitignore b/.gitignore index 7c4c28d..113722a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ cmake-* *.bak .idea/ a.out +./forth diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d95d5d..fb36ff9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,4 +5,8 @@ set(CMAKE_C_STANDARD 99) add_executable(forth main.c + fh_builtins.c + fh_runtime.c + fh_stack.c + fh_mem.c ) diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..e4411be --- /dev/null +++ b/build.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +cc *.c -o forth diff --git a/fh_builtins.c b/fh_builtins.c new file mode 100644 index 0000000..7a08955 --- /dev/null +++ b/fh_builtins.c @@ -0,0 +1,185 @@ +#include +#include "forth.h" +#include "fh_runtime.h" +#include "fh_builtins.h" +#include "fh_stack.h" +#include "fh_mem.h" + +static enum fh_error w_add(struct fh_thread_s *fh) +{ + enum fh_error rv; + uint32_t a = 0, b = 0; + TRY(ds_pop(fh, &a)); + TRY(ds_pop(fh, &b)); + TRY(ds_push(fh, a + b)); + return FH_OK; +} + +static enum fh_error w_sub(struct fh_thread_s *fh) +{ + enum fh_error rv; + uint32_t a = 0, b = 0; + TRY(ds_pop(fh, &a)); + TRY(ds_pop(fh, &b)); + TRY(ds_push(fh, a - b)); + return FH_OK; +} + +static enum fh_error w_mul(struct fh_thread_s *fh) +{ + enum fh_error rv; + uint32_t a = 0, b = 0; + TRY(ds_pop(fh, &a)); + TRY(ds_pop(fh, &b)); + TRY(ds_push(fh, a * b)); + return FH_OK; +} + +static enum fh_error w_colon(struct fh_thread_s *fh) +{ + if (fh->state != FH_STATE_INTERPRET) { + return FH_ERR_INVALID_STATE; + } + + fh_setstate(fh, FH_STATE_COMPILE, FH_SUBSTATE_COLONNAME); + + if (fh->dict_top >= DICT_SIZE) { + return FH_ERR_DICT_FULL; + } + fh->dict[fh->dict_top].start = fh->compile_top; + fh->dict[fh->dict_top].handler = w_user_word; + return FH_OK; +} + +static enum fh_error w_semicolon(struct fh_thread_s *fh) +{ + enum fh_error rv; + struct fh_instruction_s instr; + + if (fh->state != FH_STATE_COMPILE) { + return FH_ERR_INVALID_STATE; + } + + instr.kind = FH_INSTR_WORD; + instr.data = CPLWORD_ENDWORD; + TRY(fh_compile_put(fh, &instr, INSTR_SIZE)); + + /* Return to interpret state */ + fh_setstate(fh, FH_STATE_INTERPRET, 0); + fh->dict_top++; + return FH_OK; +} + +static enum fh_error w_dot(struct fh_thread_s *fh) +{ + enum fh_error rv; + uint32_t a = 0; + TRY(ds_pop(fh, &a)); + + FHPRINT("%d ", (int32_t) a); + return FH_OK; +} + +static enum fh_error w_type(struct fh_thread_s *fh) +{ + enum fh_error rv; + uint32_t count = 0, addr = 0; + TRY(ds_pop(fh, &count)); + TRY(ds_pop(fh, &addr)); + + FHPRINT("%.*s", count, &fh->heap[addr]); + return FH_OK; +} + +static enum fh_error w_cr(struct fh_thread_s *fh) +{ + (void) fh; + FHPRINT("\n"); + return FH_OK; +} + +static enum fh_error w_space(struct fh_thread_s *fh) +{ + (void) fh; + FHPRINT(" "); + return FH_OK; +} + +static enum fh_error w_s_quote(struct fh_thread_s *fh) +{ + fh_setsubstate(fh, FH_SUBSTATE_SQUOTE); + return FH_OK; +} + +static enum fh_error w_dot_quote(struct fh_thread_s *fh) +{ + fh_setsubstate(fh, FH_SUBSTATE_DOTQUOTE); + return FH_OK; +} + +static enum fh_error w_backslash(struct fh_thread_s *fh) +{ + fh_setsubstate(fh, FH_SUBSTATE_LINECOMMENT); + return FH_OK; +} + +static enum fh_error w_paren(struct fh_thread_s *fh) +{ + fh_setsubstate(fh, FH_SUBSTATE_PARENCOMMENT); + return FH_OK; +} + +static enum fh_error w_bye(struct fh_thread_s *fh) +{ + LOG("state=SHUTDOWN"); + fh_setstate(fh, FH_STATE_SHUTDOWN, 0); + return FH_OK; +} + +/** Add pointers to built-in word handlers to a runtime struct */ +enum fh_error register_builtin_words(struct fh_thread_s *fh) +{ + struct name_and_handler { + const char *name; + word_exec_t handler; + bool immediate; + }; + + const struct name_and_handler builtins[] = { + {"s\"", w_s_quote, 1}, + {".\"", w_dot_quote, 1}, + /* Compiler control words */ + {"bye", w_bye, 0}, + /* Basic arithmetics */ + {"+", w_add, 0}, + {"-", w_sub, 0}, + {"*", w_mul, 0}, + /* Control words */ + {":", w_colon, 0}, + {";", w_semicolon, 1}, + {".", w_dot, 0}, + {"type", w_type, 0}, + {"cr", w_cr, 0}, + {"space", w_space, 0}, + {"\\", w_backslash, 0}, // line comment + {"(", w_paren, 0}, // enclosed comment + { /* end marker */ } + }; + + // foreach + struct fh_word_s w; + const struct name_and_handler *p = builtins; + enum fh_error rv; + while (p->handler) { + strcpy(w.name, p->name); + w.handler = p->handler; + w.builtin = 1; + w.immediate = p->immediate; + rv = fh_add_word(&w, fh); + if (rv != FH_OK) { + return rv; + } + p++; + } + return FH_OK; +} diff --git a/fh_builtins.h b/fh_builtins.h new file mode 100644 index 0000000..02e00d5 --- /dev/null +++ b/fh_builtins.h @@ -0,0 +1,12 @@ +/** + * Forth built-ins + * + * Created on 2021/11/13. + */ + +#ifndef FORTH_FH_BUILTINS_H +#define FORTH_FH_BUILTINS_H + +enum fh_error register_builtin_words(struct fh_thread_s *fh); + +#endif //FORTH_FH_BUILTINS_H diff --git a/fh_config.h b/fh_config.h new file mode 100644 index 0000000..68ff300 --- /dev/null +++ b/fh_config.h @@ -0,0 +1,19 @@ +/** + * Runtime configuration + * + * Created on 2021/11/13. + */ + +#ifndef FORTH_FH_CONFIG_H +#define FORTH_FH_CONFIG_H + +#define CONTROL_STACK_DEPTH 1024 +#define DATA_STACK_DEPTH 1024 +#define RETURN_STACK_DEPTH 1024 +#define MAX_NAME_LEN 32 +#define DICT_SIZE 1024 +#define COMPILED_BUFFER_SIZE (1024*1024) +#define HEAP_SIZE (1024*1024) +#define MAXLINE 65535 + +#endif //FORTH_FH_CONFIG_H diff --git a/fh_error.h b/fh_error.h new file mode 100644 index 0000000..0564437 --- /dev/null +++ b/fh_error.h @@ -0,0 +1,31 @@ +/** + * Forth errors + * + * Created on 2021/11/13. + */ + +#ifndef FORTH_FH_ERROR_H +#define FORTH_FH_ERROR_H + +/** Error codes */ +enum fh_error { + FH_OK = 0, + FH_ERR_CS_OVERFLOW, + FH_ERR_DS_OVERFLOW, + FH_ERR_RS_OVERFLOW, + FH_ERR_CS_UNDERFLOW, + FH_ERR_DS_UNDERFLOW, + FH_ERR_RS_UNDERFLOW, + FH_ERR_HEAP_FULL, + FH_ERR_DICT_FULL, + FH_ERR_COMPILE_FULL, + FH_ERR_NAME_TOO_LONG, + FH_ERR_INVALID_STATE, + FH_ERR_INTERNAL, + FH_ERR_UNKNOWN_WORD, + FH_ERR_MAX, +}; + +const char *fherr_name(enum fh_error e); + +#endif //FORTH_FH_ERROR_H diff --git a/fh_mem.c b/fh_mem.c new file mode 100644 index 0000000..706e193 --- /dev/null +++ b/fh_mem.c @@ -0,0 +1,82 @@ +#include +#include "forth.h" +#include "fh_runtime.h" +#include "fh_mem.h" + +/** Allocate a heap region, e.g. for a string. The address is stored to `addr` */ +enum fh_error fh_heap_reserve( + struct fh_thread_s *fh, + size_t len, + uint32_t *addr +) +{ + uint32_t p = WORDALIGNED(fh->heap_top); // FIXME this shouldn't be needed + + if (p + len > HEAP_SIZE) { + return FH_ERR_HEAP_FULL; + } + + *addr = p; + + fh->heap_top = WORDALIGNED(p + len); + + return FH_OK; +} + +/** Write bytes to heap at a given location. The region must have been previously allocated! */ +void fh_heap_write(struct fh_thread_s *fh, uint32_t addr, const void *src, uint32_t len) +{ + memcpy(&fh->heap[addr], src, len); +} + +/** Allocate heap region and write bytes to it */ +enum fh_error fh_heap_put(struct fh_thread_s *fh, const void *src, uint32_t len) +{ + enum fh_error rv; + uint32_t addr; + TRY(fh_heap_reserve(fh, len, &addr)); + fh_heap_write(fh, addr, src, len); + return FH_OK; +} + +/** Copy bytes from compile area to heap. The region must have been previously allocated! */ +void fh_heap_copy_from_compile(struct fh_thread_s *fh, uint32_t addr, uint32_t srcaddr, uint32_t len) +{ + memcpy(&fh->heap[addr], &fh->compile[srcaddr], len); +} + +/** Reserve space in the compile memory area */ +enum fh_error fh_compile_reserve( + struct fh_thread_s *fh, + size_t len, + uint32_t *addr +) +{ + uint32_t p = WORDALIGNED(fh->compile_top); // FIXME this shouldn't be needed + + if (p + len > COMPILED_BUFFER_SIZE) { + return FH_ERR_HEAP_FULL; + } + + *addr = p; + + fh->compile_top = WORDALIGNED(p + len); + + return FH_OK; +} + +/** Write bytes to compile area at a given location. The region must have been previously allocated! */ +void fh_compile_write(struct fh_thread_s *fh, uint32_t addr, const void *src, uint32_t len) +{ + memcpy(&fh->compile[addr], src, len); +} + +/** Allocate compile region and write bytes to it */ +enum fh_error fh_compile_put(struct fh_thread_s *fh, const void *src, uint32_t len) +{ + enum fh_error rv; + uint32_t addr; + TRY(fh_compile_reserve(fh, len, &addr)); + fh_compile_write(fh, addr, src, len); + return FH_OK; +} diff --git a/fh_mem.h b/fh_mem.h new file mode 100644 index 0000000..096a569 --- /dev/null +++ b/fh_mem.h @@ -0,0 +1,27 @@ +/** + * Forth heap and compile memory + * + * Created on 2021/11/13. + */ + +#ifndef FORTH_FH_MEM_H +#define FORTH_FH_MEM_H + +enum fh_error fh_heap_reserve( + struct fh_thread_s *fh, + size_t len, + uint32_t *addr +); +void fh_heap_write(struct fh_thread_s *fh, uint32_t addr, const void *src, uint32_t len); +enum fh_error fh_heap_put(struct fh_thread_s *fh, const void *src, uint32_t len); +void fh_heap_copy_from_compile(struct fh_thread_s *fh, uint32_t addr, uint32_t srcaddr, uint32_t len); + +enum fh_error fh_compile_reserve( + struct fh_thread_s *fh, + size_t len, + uint32_t *addr +); +void fh_compile_write(struct fh_thread_s *fh, uint32_t addr, const void *src, uint32_t len); +enum fh_error fh_compile_put(struct fh_thread_s *fh, const void *src, uint32_t len); + +#endif //FORTH_FH_MEM_H diff --git a/fh_runtime.c b/fh_runtime.c new file mode 100644 index 0000000..3d4cb39 --- /dev/null +++ b/fh_runtime.c @@ -0,0 +1,386 @@ +#include "forth.h" +#include "fh_runtime.h" +#include "fh_builtins.h" +#include "fh_stack.h" +#include "fh_mem.h" +#include +#include +#include + +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_thread(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; + return FH_OK; +} + +/** Process a quoted string read from input */ +static enum fh_error fh_handle_quoted_string( + struct fh_thread_s *fh, + 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, + 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); + 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, char *linebuf) +{ + enum fh_error rv; + 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; +} diff --git a/fh_runtime.h b/fh_runtime.h new file mode 100644 index 0000000..605116d --- /dev/null +++ b/fh_runtime.h @@ -0,0 +1,164 @@ +/** + * Forth runtime internals + * + * Created on 2021/11/13. + */ + +#ifndef FORTH_FH_RUNTIME_H +#define FORTH_FH_RUNTIME_H + +#include +#include + +/** Bytecode instruction type marker */ +enum fb_instruction_kind { + /* Data = word pointer (dict index) */ + FH_INSTR_WORD, + + /* Data = numeric value to push onto the data stack */ + FH_INSTR_NUMBER, +}; + +/** Bytecode word indices that are not in the dict, have special effect */ +enum compiler_word { + /** End of a user defined word, pop address and jump back */ + CPLWORD_ENDWORD = DICT_SIZE + 1, + /** This is the `s"` instruction, the length (u32) and string data immediately follow */ + CPLWORD_ALLOCSTR, + /** This is the `."` instruction, same format as above. */ + CPLWORD_TYPESTR, +}; + +/** One instruction in bytecode */ +struct fh_instruction_s { + /** What is the meaning of data? */ + enum fb_instruction_kind kind; + /** Data word */ + uint32_t data; +}; + +static inline void instr_init(struct fh_instruction_s *instr, enum fb_instruction_kind kind, uint32_t data) +{ + instr->kind = kind; + instr->data = data; +} + +#define INSTR_SIZE (sizeof(struct fh_instruction_s)) + +_Static_assert(sizeof(struct fh_instruction_s) % 4 == 0, "Instruction struct is aligned"); + +/** Forth runtime major state */ +enum fh_state { + FH_STATE_INTERPRET = 0, + FH_STATE_COMPILE, + FH_STATE_SHUTDOWN, + FH_STATE_MAX, +}; + +/** Forth runtime minor state */ +enum fh_substate { + FH_SUBSTATE_NONE = 0, + FH_SUBSTATE_COLONNAME, + FH_SUBSTATE_SQUOTE, + FH_SUBSTATE_DOTQUOTE, + FH_SUBSTATE_PARENCOMMENT, + FH_SUBSTATE_LINECOMMENT, + FH_SUBSTATE_MAX, +}; + +/** Word struct as they are stored in the dictionary */ +struct fh_word_s { + /** Word name */ + char name[MAX_NAME_LEN]; + /** + * Handler function. + * Builtin functions use pre-defined native handlers. + * User words use a shared handler that executes compiled + * bytecode at 'start' address of the compile-memory area. + */ + word_exec_t handler; + /** Indicates that this is a built-in instruction and not a word call */ + bool builtin; + /** Indicates that this instruction should always be treated as interpreted, + * in practice this is only used for `;` */ + bool immediate; + /** Start address in case of user words */ + uint32_t start; +}; + +/** + * Forth runtime instance - state variables and memory areas. + * + * Some memory areas, such as the dict or heap, could be moved + * to a shared pointer if multi-threading and synchronization is added. + */ +struct fh_thread_s { + /** Control stack */ + uint32_t control_stack[CONTROL_STACK_DEPTH]; + size_t control_stack_top; + size_t control_stack_hwm; + + /** Data stack */ + uint32_t data_stack[DATA_STACK_DEPTH]; + size_t data_stack_top; + size_t data_stack_hwm; + + /** Return stack */ + uint32_t return_stack[RETURN_STACK_DEPTH]; + size_t return_stack_top; + size_t return_stack_hwm; + + /** Data heap */ + uint8_t heap[HEAP_SIZE]; + size_t heap_top; + + /** Compile buffer, used for both word data and literals */ + uint8_t compile[COMPILED_BUFFER_SIZE]; + size_t compile_top; + /** Pointer into the compile buffer for execution */ + uint32_t execptr; + + /** Word dict */ + struct fh_word_s dict[DICT_SIZE]; + uint32_t dict_top; + + /** Forth state */ + enum fh_state state; + + /** Forth sub-state */ + enum fh_substate substate; + + /** Word currently being executed - a pointer is placed here + * before calling the handler */ + struct fh_word_s *exec_word; +}; + +enum fh_error fh_add_word(const struct fh_word_s *w, struct fh_thread_s *fh); + +void fh_setstate(struct fh_thread_s *fh, enum fh_state state, enum fh_substate substate); +void fh_setsubstate(struct fh_thread_s *fh, enum fh_substate substate); + +enum fh_error w_user_word(struct fh_thread_s *fh); + +/* if the return address is this, we should drop back to interactive mode */ +#define MAGICADDR_INTERACTIVE 0xFFFFFFFFULL + +/** Get a value rounded up to multiple of word size */ +#define WORDALIGNED(var) (((var) + 3) & ~3) + +_Static_assert(WORDALIGNED(0) == 0, "word align"); +_Static_assert(WORDALIGNED(1) == 4, "word align"); +_Static_assert(WORDALIGNED(2) == 4, "word align"); +_Static_assert(WORDALIGNED(3) == 4, "word align"); +_Static_assert(WORDALIGNED(4) == 4, "word align"); +_Static_assert(WORDALIGNED(5) == 8, "word align"); +_Static_assert(WORDALIGNED(1023) == 1024, "word align"); +_Static_assert(WORDALIGNED(1024) == 1024, "word align"); + +#define TRY(x) \ + do { \ + if (FH_OK != (rv = (x))) return rv; \ + } while (0) + + +#endif //FORTH_FH_RUNTIME_H diff --git a/fh_stack.c b/fh_stack.c new file mode 100644 index 0000000..1898550 --- /dev/null +++ b/fh_stack.c @@ -0,0 +1,82 @@ +#include "forth.h" +#include "fh_runtime.h" +#include "fh_stack.h" + +/** Pop from data stack */ +enum fh_error ds_pop(struct fh_thread_s *fh, uint32_t *out) +{ + if (fh->data_stack_top == 0) { + LOG("DS pop UNDERFLOW"); + return FH_ERR_DS_UNDERFLOW; + } + *out = fh->data_stack[--fh->data_stack_top]; + LOG("DS pop %d", *out); + return FH_OK; +} + +/** Pop from return stack */ +enum fh_error rs_pop(struct fh_thread_s *fh, uint32_t *out) +{ + if (fh->return_stack_top == 0) { + LOG("RS pop UNDERFLOW"); + return FH_ERR_RS_UNDERFLOW; + } + *out = fh->return_stack[--fh->return_stack_top]; + LOG("RS pop %d", *out); + return FH_OK; +} + +/** Pop from control stack */ +enum fh_error cs_pop(struct fh_thread_s *fh, uint32_t *out) +{ + if (fh->control_stack_top == 0) { + LOG("CS pop UNDERFLOW"); + return FH_ERR_CS_UNDERFLOW; + } + *out = fh->control_stack[--fh->control_stack_top]; + LOG("CS pop %d", *out); + return FH_OK; +} + +#define UPDATE_HWM(hwm, top) \ + do { \ + if((hwm) < (top)) { \ + (hwm) = (top); \ + } \ + } while(0) + +/** Push to data stack */ +enum fh_error ds_push(struct fh_thread_s *fh, uint32_t in) +{ + LOG("DS push %d", in); + if (fh->data_stack_top == DATA_STACK_DEPTH) { + return FH_ERR_DS_OVERFLOW; + } + fh->data_stack[fh->data_stack_top++] = in; + UPDATE_HWM(fh->data_stack_hwm, fh->data_stack_top); + return FH_OK; +} + +/** Push to return stack */ +enum fh_error rs_push(struct fh_thread_s *fh, uint32_t in) +{ + LOG("RS push %d", in); + if (fh->return_stack_top == RETURN_STACK_DEPTH) { + return FH_ERR_RS_OVERFLOW; + } + fh->return_stack[fh->return_stack_top++] = in; + UPDATE_HWM(fh->return_stack_hwm, fh->return_stack_top); + return FH_OK; +} + +/** Push to control stack */ +enum fh_error cs_push(struct fh_thread_s *fh, uint32_t in) +{ + LOG("CS push %d", in); + if (fh->control_stack_top == CONTROL_STACK_DEPTH) { + return FH_ERR_CS_OVERFLOW; + } + fh->control_stack[fh->control_stack_top++] = in; + UPDATE_HWM(fh->control_stack_hwm, fh->control_stack_top); + return FH_OK; +} diff --git a/fh_stack.h b/fh_stack.h new file mode 100644 index 0000000..f5f7584 --- /dev/null +++ b/fh_stack.h @@ -0,0 +1,17 @@ +/** + * Forth internal stack operations + * + * Created on 2021/11/13. + */ + +#ifndef FORTH_FH_STACK_H +#define FORTH_FH_STACK_H + +enum fh_error ds_pop(struct fh_thread_s *fh, uint32_t *out); +enum fh_error rs_pop(struct fh_thread_s *fh, uint32_t *out); +enum fh_error cs_pop(struct fh_thread_s *fh, uint32_t *out); +enum fh_error ds_push(struct fh_thread_s *fh, uint32_t in); +enum fh_error rs_push(struct fh_thread_s *fh, uint32_t in); +enum fh_error cs_push(struct fh_thread_s *fh, uint32_t in); + +#endif //FORTH_FH_STACK_H diff --git a/forth b/forth new file mode 100755 index 0000000..86a2b5d Binary files /dev/null and b/forth differ diff --git a/forth.h b/forth.h new file mode 100644 index 0000000..00e0d8f --- /dev/null +++ b/forth.h @@ -0,0 +1,48 @@ +/** + * TODO file description + * + * Created on 2021/11/13. + */ + +#ifndef FORTH_H +#define FORTH_H + +#include +#include + +/* for printing */ +#include +#include + +#include "fh_config.h" +#include "fh_error.h" + +struct fh_thread_s; +struct fh_word_s; +struct fh_instruction_s; + +/** Word handler typedef */ +typedef enum fh_error (*word_exec_t)(struct fh_thread_s *fh); + +/** Forth runtime global state */ +struct fh_global_s { + /** Verbose logging enabled */ + bool verbose; + /** Interactive mode (i.e. not started with a file argument) */ + bool interactive; +}; + +extern struct fh_global_s fh_globals; + +/* logging */ +#define LOG(format, ...) do { if(fh_globals.verbose) { fprintf(stderr, format "\n", ##__VA_ARGS__); } } while (0) +#define LOGI(format, ...) fprintf(stderr, "\x1b[32m" format "\x1b[m\n", ##__VA_ARGS__) +#define LOGE(format, ...) fprintf(stderr, "\x1b[31;1m" format "\x1b[m\n", ##__VA_ARGS__) +/* Forth standard output. XXX should be stdout, but then colors get mangled if logging is used */ +#define FHPRINT(format, ...) fprintf(stderr, "\x1b[33;1m" format "\x1b[m", ##__VA_ARGS__) +#define FHPRINT_SVC(format, ...) fprintf(stderr, "" format "", ##__VA_ARGS__) + +enum fh_error fh_init_thread(struct fh_thread_s *fh); +enum fh_error fh_process_line(struct fh_thread_s *fh, char *linebuf); + +#endif //FORTH_H diff --git a/main.c b/main.c index e103ed4..df7b18d 100644 --- a/main.c +++ b/main.c @@ -5,919 +5,10 @@ #include #include #include +#include -#define CONTROL_STACK_DEPTH 1024 -#define DATA_STACK_DEPTH 1024 -#define RETURN_STACK_DEPTH 1024 -#define MAX_NAME_LEN 32 -#define DICT_SIZE 1024 -#define COMPILED_BUFFER_SIZE (1024*1024) -#define HEAP_SIZE (1024*1024) -#define MAXLINE 65535 - -struct fh_thread_s; -struct fh_word_s; -struct fh_instruction_s; - -/** Forth runtime global state */ -struct fh_global_s { - /** Verbose logging enabled */ - bool verbose; - /** Interactive mode (i.e. not started with a file argument) */ - bool interactive; -} fh_globals = {}; - -/* if the return address is this, we should drop back to interactive mode */ -#define MAGICADDR_INTERACTIVE 0xFFFFFFFFULL - -/** Get a value rounded up to multiple of word size */ -#define WORDALIGNED(var) (((var) + 3) & ~3) - -_Static_assert(WORDALIGNED(0) == 0, "word align"); -_Static_assert(WORDALIGNED(1) == 4, "word align"); -_Static_assert(WORDALIGNED(2) == 4, "word align"); -_Static_assert(WORDALIGNED(3) == 4, "word align"); -_Static_assert(WORDALIGNED(4) == 4, "word align"); -_Static_assert(WORDALIGNED(5) == 8, "word align"); -_Static_assert(WORDALIGNED(1023) == 1024, "word align"); -_Static_assert(WORDALIGNED(1024) == 1024, "word align"); - -/* logging */ -#define LOG(format, ...) do { if(fh_globals.verbose) { fprintf(stderr, format "\n", ##__VA_ARGS__); } } while (0) -#define LOGI(format, ...) fprintf(stderr, "\x1b[32m" format "\x1b[m\n", ##__VA_ARGS__) -#define LOGE(format, ...) fprintf(stderr, "\x1b[31;1m" format "\x1b[m\n", ##__VA_ARGS__) -/* Forth standard output. XXX should be stdout, but then colors get mangled if logging is used */ -#define FHPRINT(format, ...) fprintf(stderr, "\x1b[33;1m" format "\x1b[m", ##__VA_ARGS__) -#define FHPRINT_SVC(format, ...) fprintf(stderr, "" format "", ##__VA_ARGS__) - -/** Error codes */ -enum fh_error { - FH_OK = 0, - FH_ERR_CS_OVERFLOW, - FH_ERR_DS_OVERFLOW, - FH_ERR_RS_OVERFLOW, - FH_ERR_CS_UNDERFLOW, - FH_ERR_DS_UNDERFLOW, - FH_ERR_RS_UNDERFLOW, - FH_ERR_HEAP_FULL, - FH_ERR_DICT_FULL, - FH_ERR_COMPILE_FULL, - FH_ERR_NAME_TOO_LONG, - FH_ERR_INVALID_STATE, - FH_ERR_INTERNAL, - FH_ERR_UNKNOWN_WORD, - FH_ERR_MAX, -}; - -/** 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]; -} - -/** Word handler typedef */ -typedef enum fh_error (*word_exec_t)(struct fh_thread_s *fh); - -/** Word struct as they are stored in the dictionary */ -struct fh_word_s { - /** Word name */ - char name[MAX_NAME_LEN]; - /** - * Handler function. - * Builtin functions use pre-defined native handlers. - * User words use a shared handler that executes compiled - * bytecode at 'start' address of the compile-memory area. - */ - word_exec_t handler; - /** Indicates that this is a built-in instruction and not a word call */ - bool builtin; - /** Indicates that this instruction should always be treated as interpreted, - * in practice this is only used for `;` */ - bool immediate; - /** Start address in case of user words */ - uint32_t start; -}; - -/** Bytecode instruction type marker */ -enum fb_instruction_kind { - /* Data = word pointer (dict index) */ - FH_INSTR_WORD, - - /* Data = numeric value to push onto the data stack */ - FH_INSTR_NUMBER, -}; - -/** One instruction in bytecode */ -struct fh_instruction_s { - /** What is the meaning of data? */ - enum fb_instruction_kind kind; - /** Data word */ - uint32_t data; -}; - -static inline void instr_init(struct fh_instruction_s *instr, enum fb_instruction_kind kind, uint32_t data) -{ - instr->kind = kind; - instr->data = data; -} - -#define INSTR_SIZE (sizeof(struct fh_instruction_s)) - -/** Bytecode word indices that are not in the dict, have special effect */ -enum compiler_word { - /** End of a user defined word, pop address and jump back */ - CPLWORD_ENDWORD = DICT_SIZE + 1, - /** This is the `s"` instruction, the length (u32) and string data immediately follow */ - CPLWORD_ALLOCSTR, - /** This is the `."` instruction, same format as above. */ - CPLWORD_TYPESTR, -}; - -_Static_assert(sizeof(struct fh_instruction_s) % 4 == 0, "Instruction struct is aligned"); - -/** Forth runtime major state */ -enum fh_state { - FH_STATE_INTERPRET = 0, - FH_STATE_COMPILE, - FH_STATE_SHUTDOWN, - FH_STATE_MAX, -}; - -/** State names */ -static const char *statenames[] = { - [FH_STATE_INTERPRET] = "INTERPRET", - [FH_STATE_COMPILE] = "COMPILE", - [FH_STATE_SHUTDOWN] = "SHUTDOWN", -}; - -/** Forth runtime minor state */ -enum fh_substate { - FH_SUBSTATE_NONE = 0, - FH_SUBSTATE_COLONNAME, - FH_SUBSTATE_SQUOTE, - FH_SUBSTATE_DOTQUOTE, - FH_SUBSTATE_PARENCOMMENT, - FH_SUBSTATE_LINECOMMENT, - FH_SUBSTATE_MAX, -}; - -/** 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", -}; - -/** - * Forth runtime instance - state variables and memory areas. - * - * Some memory areas, such as the dict or heap, could be moved - * to a shared pointer if multi-threading and synchronization is added. - */ -struct fh_thread_s { - /** Control stack */ - uint32_t control_stack[CONTROL_STACK_DEPTH]; - size_t control_stack_top; - size_t control_stack_hwm; - - /** Data stack */ - uint32_t data_stack[DATA_STACK_DEPTH]; - size_t data_stack_top; - size_t data_stack_hwm; - - /** Return stack */ - uint32_t return_stack[RETURN_STACK_DEPTH]; - size_t return_stack_top; - size_t return_stack_hwm; - - /** Data heap */ - uint8_t heap[HEAP_SIZE]; - size_t heap_top; - - /** Compile buffer, used for both word data and literals */ - uint8_t compile[COMPILED_BUFFER_SIZE]; - size_t compile_top; - /** Pointer into the compile buffer for execution */ - uint32_t execptr; - - /** Word dict */ - struct fh_word_s dict[DICT_SIZE]; - uint32_t dict_top; - - /** Forth state */ - enum fh_state state; - - /** Forth sub-state */ - enum fh_substate substate; - - /** Word currently being executed - a pointer is placed here - * before calling the handler */ - struct fh_word_s *exec_word; -}; - -#define TRY(x) \ - do { \ - if (FH_OK != (rv = (x))) return rv; \ - } while (0) - -/** 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; -} - -/** Pop from data stack */ -static inline enum fh_error ds_pop(struct fh_thread_s *fh, uint32_t *out) -{ - if (fh->data_stack_top == 0) { - LOG("DS pop UNDERFLOW"); - return FH_ERR_DS_UNDERFLOW; - } - *out = fh->data_stack[--fh->data_stack_top]; - LOG("DS pop %d", *out); - return FH_OK; -} - -/** Pop from return stack */ -static inline enum fh_error rs_pop(struct fh_thread_s *fh, uint32_t *out) -{ - if (fh->return_stack_top == 0) { - LOG("RS pop UNDERFLOW"); - return FH_ERR_RS_UNDERFLOW; - } - *out = fh->return_stack[--fh->return_stack_top]; - LOG("RS pop %d", *out); - return FH_OK; -} - -/** Pop from control stack */ -static inline enum fh_error cs_pop(struct fh_thread_s *fh, uint32_t *out) -{ - if (fh->control_stack_top == 0) { - LOG("CS pop UNDERFLOW"); - return FH_ERR_CS_UNDERFLOW; - } - *out = fh->control_stack[--fh->control_stack_top]; - LOG("CS pop %d", *out); - return FH_OK; -} - -#define UPDATE_HWM(hwm, top) \ - do { \ - if((hwm) < (top)) { \ - (hwm) = (top); \ - } \ - } while(0) - -/** Push to data stack */ -static inline enum fh_error ds_push(struct fh_thread_s *fh, uint32_t in) -{ - LOG("DS push %d", in); - if (fh->data_stack_top == DATA_STACK_DEPTH) { - return FH_ERR_DS_OVERFLOW; - } - fh->data_stack[fh->data_stack_top++] = in; - UPDATE_HWM(fh->data_stack_hwm, fh->data_stack_top); - return FH_OK; -} - -/** Push to return stack */ -static inline enum fh_error rs_push(struct fh_thread_s *fh, uint32_t in) -{ - LOG("RS push %d", in); - if (fh->return_stack_top == RETURN_STACK_DEPTH) { - return FH_ERR_RS_OVERFLOW; - } - fh->return_stack[fh->return_stack_top++] = in; - UPDATE_HWM(fh->return_stack_hwm, fh->return_stack_top); - return FH_OK; -} - -/** Push to control stack */ -static inline enum fh_error cs_push(struct fh_thread_s *fh, uint32_t in) -{ - LOG("CS push %d", in); - if (fh->control_stack_top == CONTROL_STACK_DEPTH) { - return FH_ERR_CS_OVERFLOW; - } - fh->control_stack[fh->control_stack_top++] = in; - UPDATE_HWM(fh->control_stack_hwm, fh->control_stack_top); - 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); -} - -/** Allocate a heap region, e.g. for a string. The address is stored to `addr` */ -enum fh_error fh_heap_reserve( - struct fh_thread_s *fh, - size_t len, - uint32_t *addr -) -{ - uint32_t p = WORDALIGNED(fh->heap_top); // FIXME this shouldn't be needed - - if (p + len > HEAP_SIZE) { - return FH_ERR_HEAP_FULL; - } - - *addr = p; - - fh->heap_top = WORDALIGNED(p + len); - - return FH_OK; -} - -/** Write bytes to heap at a given location. The region must have been previously allocated! */ -void fh_heap_write(struct fh_thread_s *fh, uint32_t addr, const void *src, uint32_t len) -{ - memcpy(&fh->heap[addr], src, len); -} - -/** Allocate heap region and write bytes to it */ -enum fh_error fh_heap_put(struct fh_thread_s *fh, const void *src, uint32_t len) -{ - enum fh_error rv; - uint32_t addr; - TRY(fh_heap_reserve(fh, len, &addr)); - fh_heap_write(fh, addr, src, len); - return FH_OK; -} - -/** Copy bytes from compile area to heap. The region must have been previously allocated! */ -void fh_heap_copy_from_compile(struct fh_thread_s *fh, uint32_t addr, uint32_t srcaddr, uint32_t len) -{ - memcpy(&fh->heap[addr], &fh->compile[srcaddr], len); -} - -/** Reserve space in the compile memory area */ -enum fh_error fh_compile_reserve( - struct fh_thread_s *fh, - size_t len, - uint32_t *addr -) -{ - uint32_t p = WORDALIGNED(fh->compile_top); // FIXME this shouldn't be needed - - if (p + len > COMPILED_BUFFER_SIZE) { - return FH_ERR_HEAP_FULL; - } - - *addr = p; - - fh->compile_top = WORDALIGNED(p + len); - - return FH_OK; -} - -/** Write bytes to compile area at a given location. The region must have been previously allocated! */ -void fh_compile_write(struct fh_thread_s *fh, uint32_t addr, const void *src, uint32_t len) -{ - memcpy(&fh->compile[addr], src, len); -} - -/** Allocate compile region and write bytes to it */ -enum fh_error fh_compile_put(struct fh_thread_s *fh, const void *src, uint32_t len) -{ - enum fh_error rv; - uint32_t addr; - TRY(fh_compile_reserve(fh, len, &addr)); - fh_compile_write(fh, addr, src, len); - return FH_OK; -} - -enum fh_error w_add(struct fh_thread_s *fh) -{ - enum fh_error rv; - uint32_t a = 0, b = 0; - TRY(ds_pop(fh, &a)); - TRY(ds_pop(fh, &b)); - TRY(ds_push(fh, a + b)); - return FH_OK; -} - -enum fh_error w_sub(struct fh_thread_s *fh) -{ - enum fh_error rv; - uint32_t a = 0, b = 0; - TRY(ds_pop(fh, &a)); - TRY(ds_pop(fh, &b)); - TRY(ds_push(fh, a - b)); - return FH_OK; -} - -enum fh_error w_mul(struct fh_thread_s *fh) -{ - enum fh_error rv; - uint32_t a = 0, b = 0; - TRY(ds_pop(fh, &a)); - TRY(ds_pop(fh, &b)); - TRY(ds_push(fh, a * b)); - return FH_OK; -} - -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; -} - -enum fh_error w_colon(struct fh_thread_s *fh) -{ - if (fh->state != FH_STATE_INTERPRET) { - return FH_ERR_INVALID_STATE; - } - - fh_setstate(fh, FH_STATE_COMPILE, FH_SUBSTATE_COLONNAME); - - if (fh->dict_top >= DICT_SIZE) { - return FH_ERR_DICT_FULL; - } - fh->dict[fh->dict_top].start = fh->compile_top; - fh->dict[fh->dict_top].handler = w_user_word; - return FH_OK; -} - -enum fh_error w_semicolon(struct fh_thread_s *fh) -{ - enum fh_error rv; - struct fh_instruction_s instr; - - if (fh->state != FH_STATE_COMPILE) { - return FH_ERR_INVALID_STATE; - } - - instr.kind = FH_INSTR_WORD; - instr.data = CPLWORD_ENDWORD; - TRY(fh_compile_put(fh, &instr, INSTR_SIZE)); - - /* Return to interpret state */ - fh_setstate(fh, FH_STATE_INTERPRET, 0); - fh->dict_top++; - return FH_OK; -} - -enum fh_error w_dot(struct fh_thread_s *fh) -{ - enum fh_error rv; - uint32_t a = 0; - TRY(ds_pop(fh, &a)); - - FHPRINT("%d ", (int32_t) a); - return FH_OK; -} - -enum fh_error w_type(struct fh_thread_s *fh) -{ - enum fh_error rv; - uint32_t count = 0, addr = 0; - TRY(ds_pop(fh, &count)); - TRY(ds_pop(fh, &addr)); - - FHPRINT("%.*s", count, &fh->heap[addr]); - return FH_OK; -} - -enum fh_error w_cr(struct fh_thread_s *fh) -{ - (void) fh; - FHPRINT("\n"); - return FH_OK; -} - -enum fh_error w_space(struct fh_thread_s *fh) -{ - (void) fh; - FHPRINT(" "); - return FH_OK; -} - -enum fh_error w_s_quote(struct fh_thread_s *fh) -{ - fh_setsubstate(fh, FH_SUBSTATE_SQUOTE); - return FH_OK; -} - -enum fh_error w_dot_quote(struct fh_thread_s *fh) -{ - fh_setsubstate(fh, FH_SUBSTATE_DOTQUOTE); - return FH_OK; -} - -enum fh_error w_backslash(struct fh_thread_s *fh) -{ - fh_setsubstate(fh, FH_SUBSTATE_LINECOMMENT); - return FH_OK; -} - -enum fh_error w_paren(struct fh_thread_s *fh) -{ - fh_setsubstate(fh, FH_SUBSTATE_PARENCOMMENT); - return FH_OK; -} - -enum fh_error w_bye(struct fh_thread_s *fh) -{ - LOG("state=SHUTDOWN"); - fh_setstate(fh, FH_STATE_SHUTDOWN, 0); - return FH_OK; -} - -/** Add pointers to built-in word handlers to a runtime struct */ -enum fh_error register_builtin_words(struct fh_thread_s *fh) -{ - struct name_and_handler { - const char *name; - word_exec_t handler; - bool immediate; - }; - - const struct name_and_handler builtins[] = { - {"s\"", w_s_quote, 1}, - {".\"", w_dot_quote, 1}, - /* Compiler control words */ - {"bye", w_bye, 0}, - /* Basic arithmetics */ - {"+", w_add, 0}, - {"-", w_sub, 0}, - {"*", w_mul, 0}, - /* Control words */ - {":", w_colon, 0}, - {";", w_semicolon, 1}, - {".", w_dot, 0}, - {"type", w_type, 0}, - {"cr", w_cr, 0}, - {"space", w_space, 0}, - {"\\", w_backslash, 0}, // line comment - {"(", w_paren, 0}, // enclosed comment - { /* end marker */ } - }; - - // foreach - struct fh_word_s w; - const struct name_and_handler *p = builtins; - enum fh_error rv; - while (p->handler) { - strcpy(w.name, p->name); - w.handler = p->handler; - w.builtin = 1; - w.immediate = p->immediate; - rv = fh_add_word(&w, fh); - if (rv != FH_OK) { - return rv; - } - p++; - } - return FH_OK; -} - -#undef ADDWORD - -/** Initialize a runtime */ -enum fh_error fh_init_thread(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; - return FH_OK; -} - -/** Process a quoted string read from input */ -static enum fh_error fh_handle_quoted_string( - struct fh_thread_s *fh, - 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, - 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); - 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 whitespace */ -static inline bool iswhite(char c) -{ - return c == ' ' || c == '\n' || c == '\t' || c == '\r'; -} - -/** 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 */ -static enum fh_error fh_process_line(struct fh_thread_s *fh, char *linebuf) -{ - enum fh_error rv; - 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 (iswhite(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; -} - +#include "forth.h" +#include "fh_runtime.h" int main(int argc, char *argv[]) { @@ -968,7 +59,7 @@ int main(int argc, char *argv[]) // trim size_t end = strlen(linebuf) - 1; - while (iswhite(linebuf[end])) { + while (isspace(linebuf[end])) { linebuf[end] = 0; }