#include #include #include #include "fh_error.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 = {}; /** State names */ static const char *statenames[FH_STATE_MAX] = { [FH_STATE_INTERPRET] = "INTERPRET", [FH_STATE_COMPILE] = "COMPILE", [FH_STATE_QUIT] = "RUN", [FH_STATE_SHUTDOWN] = "SHUTDOWN", }; /** Sub-state names */ static const char *substatenames[FH_SUBSTATE_MAX] = { [FH_SUBSTATE_NONE] = "NONE", [FH_SUBSTATE_PAREN_COMMENT] = "PAREN_COMMENT", [FH_SUBSTATE_LINE_COMMENT] = "LINE_COMMENT", [FH_SUBSTATE_EXIT] = "EXIT", }; void fh_input_consume_spaces(struct fh_thread_s *fh) { char *rp = (char *) &fh->heap[INPUTBUF_ADDR + fh->inputptr]; while (isspace(*rp)) { rp++; fh->inputptr++; } } enum fh_error fh_input_read_word(struct fh_thread_s *fh, char **out, size_t *len) { char *rp = (char *) &fh->heap[INPUTBUF_ADDR + fh->inputptr]; char *start = rp; while (1) { char c = *rp; if (isspace(c) || c == 0) { if (rp == start) { LOGE("Expected a word!"); return FH_ERR_SYNTAX; } *out = start; *len = rp - start; return FH_OK; } rp++; fh->inputptr++; } } enum fh_error fh_input_read_quotedstring(struct fh_thread_s *fh, bool escaped, char *outbuf, size_t capacity, size_t *out_len) { char *rp = (char *) &fh->heap[INPUTBUF_ADDR + fh->inputptr]; bool next_escaped = false; size_t remains = capacity; size_t len = 0; int hexdigits = 0; uint32_t hex = 0; while (len < capacity) { char c = *rp; if (c == 0) { LOGE("Unterminated quoted string!"); return FH_ERR_SYNTAX; } if (hexdigits) { hex <<= 4; if (isdigit(c)) { hex |= c - '0'; } else if (c>='a' && c<='f') { hex |= c - 'a'; } else if (c>='A' && c<='F') { hex |= c - 'A'; } else { LOGE("Bad hex escape"); return FH_ERR_SYNTAX; } hexdigits--; if (hexdigits == 0) { c = (char) hex; goto append; } } if (!escaped || !next_escaped) { if (c == '\"') { *outbuf = 0; *out_len = len; // advance past the quote fh->inputptr++; return FH_OK; } if (c == '\\') { next_escaped = true; goto skip; } } else { next_escaped = false; switch (c) { case 'a': c = 7; break; case 'b': c = 8; break; case 'e': c = 27; break; case 'f': c = 12; break; case 'l': c = 10; break; case 'm': case 'n': if (remains < 2) goto full; *outbuf++ = '\r'; *outbuf++ = '\n'; remains -= 2; len += 2; goto skip; case 'q': c = '"'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; case 'z': c = 0; break; // XXX this will cause problems! case 'x': hex = 0; hexdigits = 2; goto skip; default:; // just append normally } } append: *outbuf++ = c; len++; skip: rp++; fh->inputptr++; } full: LOGE("String too long!"); return FH_ERR_SYNTAX; } /** Add a word to the dictionary. */ enum fh_error fh_add_word(const struct fh_word_s *w, struct fh_thread_s *fh) { enum fh_error rv; fh_align(fh); uint32_t ptr = fh->here; TRY(fh_heap_put(fh, w, DICTWORD_SIZE)); //LOG("Added word \"%s\" at 0x%08x", w->name, ptr); // thread it onto the linked list fh_word_at(fh, ptr)->previous = fh->dict_last; fh->dict_last = ptr; 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); } /** Execute a user word */ enum fh_error w_user_word(struct fh_thread_s *fh, const struct fh_word_s *w0) { enum fh_error rv; const struct fh_word_s *w; const struct fh_word_s *w2; w = w0; call: if (!w) { return FH_ERR_INTERNAL; } LOG("Run user word: %s", w->name); TRY(rs_push(fh, fh->execptr)); fh->execptr = w->param; instr:; if (fh->state == FH_STATE_QUIT) { /* abort or quit was called, return to interactive mode */ fh_setstate(fh, FH_STATE_INTERPRET, FH_SUBSTATE_NONE); return FH_OK; } // make sure it's aligned fh->execptr = WORDALIGNED(fh->execptr); const struct fh_instruction_s *instr = fh_instr_at(fh, fh->execptr); fh->execptr += INSTR_SIZE; uint32_t strl; uint32_t val; uint32_t addr = 0; uint32_t limit, index, index0; struct fh_instruction_s instr2; switch (instr->kind) { case FH_INSTR_NUMBER: TRY(ds_push(fh, instr->data)); goto instr; case FH_INSTR_POSTPONED_WORD: if (fh->state == FH_STATE_COMPILE) { w2 = fh_word_at(fh, instr->data); if (w2->flags & WORDFLAG_IMMEDIATE) { LOG("Call immediate postponed word: %s", w2->name); TRY(w2->handler(fh, w2)); } else { LOG("Add postponed word: %s", w2->name); TRY(fh_put_instr(fh, FH_INSTR_WORD, instr->data)); } } else { LOGE("Postpone in interpret mode!"); goto end; } goto instr; case FH_INSTR_WORD: w2 = fh_word_at(fh, instr->data); if (w2->flags & WORDFLAG_BUILTIN) { LOG("Exec: builtin-word \"%s\"", w2->name); w2->handler(fh, w2); if (fh->substate == FH_SUBSTATE_EXIT) { fh_setsubstate(fh, 0); LOG("Exec: early return"); TRY(rs_pop(fh, &fh->execptr)); if (fh->execptr == MAGICADDR_INTERACTIVE) { goto end; } } goto instr; } else { LOG("Exec: user-word %s (CALL)", w2->name); w = fh_word_at(fh, instr->data); goto call; } case FH_INSTR_JUMPZERO: if (instr->data == MAGICADDR_UNRESOLVED) { LOGE("Encountered unresolved jump!"); goto end; } TRY(ds_pop(fh, &val)); if (0 == val) { fh->execptr = instr->data; } goto instr; case FH_INSTR_JUMP: if (instr->data == MAGICADDR_UNRESOLVED) { LOGE("Encountered unresolved jump!"); goto end; } fh->execptr = instr->data; goto instr; case FH_INSTR_DO: TRY(ds_pop(fh, &index)); TRY(ds_pop(fh, &limit)); // just make sure it exists TRY(fh_loop_nest(fh, index)); TRY(rs_push(fh, limit)); goto instr; case FH_INSTR_DO_QUESTION: if (instr->data == MAGICADDR_UNRESOLVED) { LOGE("Encountered unresolved jump!"); goto end; } TRY(ds_pop(fh, &index)); TRY(ds_pop(fh, &limit)); if (index == limit) { // jump to end fh->execptr = instr->data; } else { TRY(fh_loop_nest(fh, index)); TRY(rs_push(fh, limit)); } goto instr; case FH_INSTR_LOOP_PLUS: TRY(ds_pop(fh, &val)); // fall-through case FH_INSTR_LOOP: if (instr->kind == FH_INSTR_LOOP) { val = 1; } // R: index,limit TRY(rs_peek(fh, &limit)); index0 = fh->loop_i; fh->loop_i += val; if (index0 < limit == fh->loop_i < limit) { // boundary not crossed, continue fh->execptr = instr->data; // go to beginning } else { // end of loop TRY(rs_pop(fh, &limit)); TRY(fh_loop_unnest(fh)); } goto instr; case FH_INSTR_LEAVE: if (instr->data == MAGICADDR_UNRESOLVED) { LOGE("Encountered unresolved jump!"); goto end; } TRY(rs_pop(fh, &limit)); TRY(fh_loop_unnest(fh)); fh->execptr = instr->data; goto instr; /* special case for strings stored in compile memory */ case FH_INSTR_ALLOCSTR: case FH_INSTR_TYPESTR: strl = instr->data; if (instr->kind == FH_INSTR_ALLOCSTR) { TRY(fh_heap_reserve(fh, strl, &addr)); LOG("Exec: alloc-str \"%.*s\"", strl, fh_str_at(fh, fh->execptr)); fh_heap_copy(fh, addr, fh->execptr, strl); TRY(ds_push(fh, addr)); TRY(ds_push(fh, strl)); } else { LOG("Exec: type-str \"%.*s\"", strl, fh_str_at(fh, fh->execptr)); FHPRINT("%.*s", (int) strl, fh_str_at(fh, fh->execptr)); } fh->execptr += strl; goto instr; case FH_INSTR_ENDWORD: LOG("Exec: word-end (RETURN)"); TRY(rs_pop(fh, &fh->execptr)); if (fh->execptr == MAGICADDR_INTERACTIVE) { goto end; } goto instr; } 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)); fh->dict_last = MAGICADDR_DICTFIRST; TRY(register_builtin_words(fh)); fh->execptr = MAGICADDR_INTERACTIVE; fh->base = 10; return FH_OK; } enum fh_error fh_handle_word(struct fh_thread_s *fh, uint32_t addr) { struct fh_instruction_s instr; enum fh_error rv; struct fh_word_s *w = fh_word_at(fh, addr); if (fh->state == FH_STATE_COMPILE && 0 == (w->flags & WORDFLAG_IMMEDIATE)) { LOG("Compile word call: %s", w->name); TRY(fh_put_instr(fh, FH_INSTR_WORD, addr)); } else { /* interpret or immediate in compiled code */ LOG("Run word: %s (state=%d)", w->name, fh->state); TRY(w->handler(fh, w)); } return FH_OK; } enum fh_error fh_find_word(struct fh_thread_s *fh, const char *name, size_t wordlen, uint32_t *addr_out) { if (wordlen == 0) { wordlen = strlen(name); } uint32_t addr = fh->dict_last; while (addr != MAGICADDR_DICTFIRST) { struct fh_word_s *w = fh_word_at(fh, addr); if (0 == strncasecmp(name, w->name, wordlen) && w->name[wordlen] == 0) { if (addr_out) { *addr_out = addr; } return FH_OK; } addr = w->previous; } // no log message, this can be OK - e.g. parsing a number LOG("fail to find word %.*s", wordlen, name); return FH_ERR_UNKNOWN_WORD; } enum fh_error fh_loop_nest(struct fh_thread_s *fh, uint32_t indexvalue) { enum fh_error rv; TRY(rs_push(fh, fh->loop_j)); fh->loop_j = fh->loop_i; fh->loop_i = indexvalue; return FH_OK; } enum fh_error fh_loop_unnest(struct fh_thread_s *fh) { enum fh_error rv; fh->loop_i = fh->loop_j; TRY(rs_pop(fh, &fh->loop_j)); return FH_OK; } /** Process a word read from input */ static enum fh_error fh_handle_ascii_word( struct fh_thread_s *fh, const char *name, const size_t wordlen ) { enum fh_error rv; if (wordlen >= MAX_NAME_LEN) { return FH_ERR_NAME_TOO_LONG; } /* First, try if it's a known word */ uint32_t wadr = 0; if (FH_OK == fh_find_word(fh, name, wordlen, &wadr)) { TRY(fh_handle_word(fh, wadr)); return FH_OK; } /* word not found, try parsing as number */ errno = 0; char *endptr; int base = (int) fh->base; // prefix can override BASE - this is a syntax extension if (name[0] == '0') { if (name[1] == 'x') { base = 16; } else if (name[1] == 'b') { base = 2; } else if (name[1] == 'o') { base = 8; } } long v = strtol(name, &endptr, base); // XXX if base is 0, this will use auto-detection if (errno != 0 || (endptr - name) != wordlen) { LOGE("Unknown word and fail to parse as number: \"%.*s\"", (int) wordlen, name); return FH_ERR_UNKNOWN_WORD; } struct fh_instruction_s instr; if (fh->state == FH_STATE_COMPILE) { LOG("Compile number: %ld", v); TRY(fh_put_instr(fh, FH_INSTR_NUMBER, (uint32_t) v)); } else { /* interpret */ LOG("Interpret number: %ld", v); TRY(ds_push(fh, (uint32_t) v)); } return FH_OK; } /** Postpone a word */ enum fh_error fh_postpone_word( struct fh_thread_s *fh, const char *name, const size_t wordlen ) { enum fh_error rv; uint32_t wadr; TRY(fh_find_word(fh, name, wordlen, &wadr)); LOG("Postpone word"); TRY(fh_put_instr(fh, FH_INSTR_POSTPONED_WORD, wadr)); 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, size_t len) { enum fh_error rv; #define ReadPtr ((char*)(&fh->heap[INPUTBUF_ADDR + fh->inputptr])) #define ReadPos (fh->inputptr) #define ReadLen (fh->inputlen) fh_fill_input_buffer(fh, linebuf, len); char c; if (!fh_globals.interactive) { LOGI("%s", linebuf); } while (ReadPos < ReadLen && fh->state != FH_STATE_SHUTDOWN) { c = *ReadPtr; /* end on newline */ if (isnl(c)) { goto done; } /* skip whitespace */ if (isspace(c)) { ReadPos++; continue; } const char * const rp = ReadPtr; char *end; size_t length; switch (fh->substate) { case FH_SUBSTATE_NONE: /* try to read a word */ end = strchr(rp, ' '); if (end) { length = end - rp; /* exclude the space */ } else { length = strlen(rp); } ReadPos += length + 1; /* eval a word */ LOG("Handle \"%.*s\"", (int) length, rp); TRY(fh_handle_ascii_word(fh, rp, length)); if (!end) { goto done; } break; case FH_SUBSTATE_PAREN_COMMENT: end = strchr(rp, ')'); if (end) { length = end - rp; LOG("Discard inline comment"); fh_setsubstate(fh, FH_SUBSTATE_NONE); ReadPos += length + 1; } else { /* no end, discard all */ LOGE("Unterminated parenthesis comment"); goto done; } break; case FH_SUBSTATE_LINE_COMMENT: 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; }