From 15848e203b0eb6608a8473afc7fc6d1a3b654e22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Sat, 13 Nov 2021 15:40:09 +0100 Subject: [PATCH] add comments --- a.f => a.forth | 0 hello.forth | 2 + main.c | 158 ++++++++++++++++++++++++++++++--------------- str.f => str.forth | 0 4 files changed, 108 insertions(+), 52 deletions(-) rename a.f => a.forth (100%) create mode 100644 hello.forth rename str.f => str.forth (100%) diff --git a/a.f b/a.forth similarity index 100% rename from a.f rename to a.forth diff --git a/hello.forth b/hello.forth new file mode 100644 index 0000000..b8748e8 --- /dev/null +++ b/hello.forth @@ -0,0 +1,2 @@ +: hi ." Hello, World!" ; +hi diff --git a/main.c b/main.c index e271fd6..aef14e7 100644 --- a/main.c +++ b/main.c @@ -4,11 +4,12 @@ #include #include #include +#include #define CONTROL_STACK_DEPTH 1024 #define DATA_STACK_DEPTH 1024 #define RETURN_STACK_DEPTH 1024 -#define MAX_NAME_LEN 64 +#define MAX_NAME_LEN 32 #define DICT_SIZE 1024 #define COMPILED_BUFFER_SIZE (1024*1024) #define HEAP_SIZE (1024*1024) @@ -18,27 +19,38 @@ 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 -#define ALIGNWORD(var) \ - do { \ - while (((var) % 4) != 0) { (var)++; } \ - } while (0) +/** 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, TODO make levels configurable */ +/* logging */ #define LOG(format, ...) do { if(fh_globals.verbose) { fprintf(stderr, format "\r\n", ##__VA_ARGS__); } } while (0) #define LOGI(format, ...) fprintf(stderr, "\x1b[32m" format "\x1b[m\r\n", ##__VA_ARGS__) #define LOGE(format, ...) fprintf(stderr, "\x1b[31;1m" format "\x1b[m\r\n", ##__VA_ARGS__) -/* Forth standard output. XXX should be stdout, but then colors get mangled */ +/* 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, @@ -57,6 +69,7 @@ enum fh_error { FH_ERR_MAX, }; +/** Error names */ static const char *errornames[] = { [FH_OK] = "OK", [FH_ERR_CS_OVERFLOW] = "CS_OVERFLOW", @@ -74,6 +87,7 @@ static const char *errornames[] = { [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) { @@ -82,39 +96,59 @@ const char *fherr_name(enum fh_error e) 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; - uint32_t end; }; +/** Bytecode instruction type marker */ enum fb_instruction_kind { - /* Data is a word number in the dict */ + /* Data = word pointer (dict index) */ FH_INSTR_WORD, - /* Data is a numeric value to push on the data stack */ + /* 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; }; -/** words that are not in the dict, have special effect */ +/** 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, @@ -122,12 +156,14 @@ enum fh_state { 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, @@ -138,6 +174,7 @@ enum fh_substate { FH_SUBSTATE_MAX, }; +/** Sub-state names */ static const char *substatenames[] = { [FH_SUBSTATE_NONE] = "NONE", [FH_SUBSTATE_COLONNAME] = "COLONNAME", @@ -147,6 +184,12 @@ static const char *substatenames[] = { [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]; @@ -176,8 +219,10 @@ struct fh_thread_s { /** 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; @@ -188,11 +233,6 @@ struct fh_thread_s { if (FH_OK != (rv = (x))) return rv; \ } while (0) -#define TRY_FAIL(x) \ - do { \ - if (FH_OK != (rv = (x))) goto fail; \ - } 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) { @@ -203,8 +243,7 @@ enum fh_error fh_add_word(const struct fh_word_s *w, struct fh_thread_s *fh) return FH_OK; } -//region Push & Pop - +/** 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) { @@ -216,6 +255,7 @@ static inline enum fh_error ds_pop(struct fh_thread_s *fh, uint32_t *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) { @@ -227,6 +267,7 @@ static inline enum fh_error rs_pop(struct fh_thread_s *fh, uint32_t *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) { @@ -238,6 +279,7 @@ static inline enum fh_error cs_pop(struct fh_thread_s *fh, uint32_t *out) return FH_OK; } +/** Push to data stack */ static inline enum fh_error ds_push(struct fh_thread_s *fh, uint32_t in) { LOG("DS push %d", in); @@ -248,6 +290,7 @@ static inline enum fh_error ds_push(struct fh_thread_s *fh, uint32_t in) 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); @@ -258,6 +301,7 @@ static inline enum fh_error rs_push(struct fh_thread_s *fh, uint32_t in) 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); @@ -268,16 +312,17 @@ static inline enum fh_error cs_push(struct fh_thread_s *fh, uint32_t in) return FH_OK; } -//endregion Push & Pop - -static void showstate(const struct fh_thread_s *fh) { - if(fh->substate==0) { +/** 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; @@ -285,20 +330,21 @@ void fh_setstate(struct fh_thread_s *fh, enum fh_state state, enum fh_substate s 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 fh_allot( +/** 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 = fh->heap_top; - ALIGNWORD(p); + uint32_t p = WORDALIGNED(fh->heap_top); // FIXME this shouldn't be needed if (p + len > HEAP_SIZE) { return FH_ERR_HEAP_FULL; @@ -306,22 +352,19 @@ enum fh_error fh_allot( *addr = p; - size_t next = p + len; - ALIGNWORD(next); - fh->heap_top = next; + fh->heap_top = WORDALIGNED(p + len); return FH_OK; } +/** 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 = fh->compile_top; - // align up - ALIGNWORD(p); + uint32_t p = WORDALIGNED(fh->compile_top); // FIXME this shouldn't be needed if (p + len > COMPILED_BUFFER_SIZE) { return FH_ERR_HEAP_FULL; @@ -329,16 +372,11 @@ enum fh_error fh_compile_reserve( *addr = p; - size_t next = p + len; - ALIGNWORD(next); - fh->compile_top = next; + fh->compile_top = WORDALIGNED(p + len); return FH_OK; } - -//region Builtin Words - enum fh_error w_add(struct fh_thread_s *fh) { enum fh_error rv; @@ -387,7 +425,7 @@ enum fh_error w_user_word(struct fh_thread_s *fh) instr:; // make sure it's aligned - ALIGNWORD(fh->execptr); + 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); @@ -406,7 +444,7 @@ enum fh_error w_user_word(struct fh_thread_s *fh) strl = *((uint32_t *) &fh->compile[fh->execptr]); fh->execptr += 4; if (wn == CPLWORD_ALLOCSTR) { - TRY(fh_allot(fh, strl, &addr)); + TRY(fh_heap_reserve(fh, strl, &addr)); memcpy(&fh->heap[addr], &fh->compile[fh->execptr], strl); LOG("Exec: alloc-str \"%.*s\"", strl, &fh->heap[addr]); TRY(ds_push(fh, addr)); @@ -477,7 +515,6 @@ enum fh_error w_semicolon(struct fh_thread_s *fh) /* Return to interpret state */ fh_setstate(fh, FH_STATE_INTERPRET, 0); - fh->dict[fh->dict_top].end = fh->compile_top; /* one past the end cell */ fh->dict_top++; return FH_OK; } @@ -505,12 +542,14 @@ enum fh_error w_type(struct fh_thread_s *fh) enum fh_error w_cr(struct fh_thread_s *fh) { + (void) fh; FHPRINT("\r\n"); return FH_OK; } enum fh_error w_space(struct fh_thread_s *fh) { + (void) fh; FHPRINT(" "); return FH_OK; } @@ -546,6 +585,7 @@ enum fh_error w_bye(struct fh_thread_s *fh) 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 { @@ -575,6 +615,7 @@ enum fh_error register_builtin_words(struct fh_thread_s *fh) { /* end marker */ } }; + // foreach struct fh_word_s w; const struct name_and_handler *p = builtins; enum fh_error rv; @@ -594,8 +635,7 @@ enum fh_error register_builtin_words(struct fh_thread_s *fh) #undef ADDWORD -//endregion Builtin Words - +/** Initialize a runtime */ enum fh_error fh_init_thread(struct fh_thread_s *fh) { enum fh_error rv; @@ -609,7 +649,8 @@ enum fh_error fh_init_thread(struct fh_thread_s *fh) return FH_OK; } -enum fh_error fh_handle_quoted_string( +/** 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 @@ -623,7 +664,7 @@ enum fh_error fh_handle_quoted_string( if (fh->state == FH_STATE_INTERPRET) { switch (fh->substate) { case FH_SUBSTATE_SQUOTE: - TRY(fh_allot(fh, len, &addr)); + TRY(fh_heap_reserve(fh, len, &addr)); memcpy(&fh->heap[addr], start, len); TRY(ds_push(fh, addr)); TRY(ds_push(fh, len)); @@ -653,7 +694,8 @@ enum fh_error fh_handle_quoted_string( return FH_OK; } -enum fh_error fh_handle_word( +/** Process a word read from input */ +static enum fh_error fh_handle_word( struct fh_thread_s *fh, char *start, size_t len @@ -700,37 +742,42 @@ enum fh_error fh_handle_word( } if (fh->state == FH_STATE_COMPILE) { - LOG("Compile number: %d", v); + LOG("Compile number: %ld", v); TRY(fh_compile_reserve(fh, sizeof(struct fh_instruction_s), &addr)); instr.kind = FH_INSTR_NUMBER; instr.data = (uint32_t) v; memcpy(&fh->compile[addr], &instr, sizeof(struct fh_instruction_s)); } else { /* interpret */ - LOG("Interpret number: %d", v); + 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'; } -enum fh_error fh_process_line(struct fh_thread_s *fh, char *linebuf) +/** 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; - LOGI("%s", linebuf); + if (!fh_globals.interactive) { + LOGI("%s", linebuf); + } while (0 != (c = *rp) && fh->state != FH_STATE_SHUTDOWN) { /* end on newline */ @@ -806,6 +853,9 @@ enum fh_error fh_process_line(struct fh_thread_s *fh, char *linebuf) case FH_SUBSTATE_LINECOMMENT: LOG("Discard line comment"); goto done; // just discard the rest + + default: + LOGE("Bad substate %s", substatenames[fh->substate]); } } done: @@ -825,7 +875,7 @@ int main(int argc, char *argv[]) } fh_globals.verbose = false; - fh_globals.interactive = false; + fh_globals.interactive = isatty(STDIN_FILENO); FILE *infile = stdin; @@ -835,7 +885,7 @@ int main(int argc, char *argv[]) // opt char *cc = argv[a] + 1; char c; - while(0 != (c = *cc++)) { + while (0 != (c = *cc++)) { switch (c) { case 'v': fh_globals.verbose = 1; @@ -876,7 +926,11 @@ int main(int argc, char *argv[]) FHPRINT_SVC(" ok\r\n"); } else { LOGE("ERROR %s on line %d", fherr_name(rv), linecnt); - return 1; + if (!fh_globals.interactive) { + return 1; + } + /* reset state */ + fh_setstate(&fh, FH_STATE_INTERPRET, FH_SUBSTATE_NONE); } } diff --git a/str.f b/str.forth similarity index 100% rename from str.f rename to str.forth