#include #include #include #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" 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) { 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, 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); 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) { 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; }