split to files

master
Ondřej Hruška 3 years ago
parent 85feba4fdf
commit 68ddeb3e17
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 1
      .gitignore
  2. 4
      CMakeLists.txt
  3. 3
      build.sh
  4. 185
      fh_builtins.c
  5. 12
      fh_builtins.h
  6. 19
      fh_config.h
  7. 31
      fh_error.h
  8. 82
      fh_mem.c
  9. 27
      fh_mem.h
  10. 386
      fh_runtime.c
  11. 164
      fh_runtime.h
  12. 82
      fh_stack.c
  13. 17
      fh_stack.h
  14. BIN
      forth
  15. 48
      forth.h
  16. 917
      main.c

1
.gitignore vendored

@ -3,3 +3,4 @@ cmake-*
*.bak *.bak
.idea/ .idea/
a.out a.out
./forth

@ -5,4 +5,8 @@ set(CMAKE_C_STANDARD 99)
add_executable(forth add_executable(forth
main.c main.c
fh_builtins.c
fh_runtime.c
fh_stack.c
fh_mem.c
) )

@ -0,0 +1,3 @@
#!/bin/bash
cc *.c -o forth

@ -0,0 +1,185 @@
#include <string.h>
#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;
}

@ -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

@ -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

@ -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

@ -0,0 +1,82 @@
#include <string.h>
#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;
}

@ -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

@ -0,0 +1,386 @@
#include "forth.h"
#include "fh_runtime.h"
#include "fh_builtins.h"
#include "fh_stack.h"
#include "fh_mem.h"
#include <string.h>
#include <errno.h>
#include <ctype.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_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;
}

@ -0,0 +1,164 @@
/**
* Forth runtime internals
*
* Created on 2021/11/13.
*/
#ifndef FORTH_FH_RUNTIME_H
#define FORTH_FH_RUNTIME_H
#include <stdint.h>
#include <stddef.h>
/** 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

@ -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;
}

@ -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

BIN
forth

Binary file not shown.

@ -0,0 +1,48 @@
/**
* TODO file description
*
* Created on 2021/11/13.
*/
#ifndef FORTH_H
#define FORTH_H
#include <stdint.h>
#include <stdbool.h>
/* for printing */
#include <stdlib.h>
#include <stdio.h>
#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

917
main.c

@ -5,919 +5,10 @@
#include <string.h> #include <string.h>
#include <errno.h> #include <errno.h>
#include <unistd.h> #include <unistd.h>
#include <ctype.h>
#define CONTROL_STACK_DEPTH 1024 #include "forth.h"
#define DATA_STACK_DEPTH 1024 #include "fh_runtime.h"
#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;
}
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
@ -968,7 +59,7 @@ int main(int argc, char *argv[])
// trim // trim
size_t end = strlen(linebuf) - 1; size_t end = strlen(linebuf) - 1;
while (iswhite(linebuf[end])) { while (isspace(linebuf[end])) {
linebuf[end] = 0; linebuf[end] = 0;
} }

Loading…
Cancel
Save