scratch/proglangs/nsforth/nsforth.c
2024-12-26 14:02:40 -03:00

580 lines
13 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdarg.h>
#include <ctype.h>
#include <inttypes.h>
#include <errno.h>
#include "defs.h"
#include "dict.h"
#define STACK_SIZE 4096u
#define DEF_NATIVE(name) void name##_fn(void)
#define DEF_ARITH_FN(op, name, ctype) \
DEF_NATIVE(name) { \
int err = 0; \
struct value_box *n2 = pop_stack(); \
struct value_box *n1 = pop_stack(); \
if (n1 == NULL || n2 == NULL) return; \
ctype v2 = unbox_exb(n2, ctype, CET_NUMBER, &err); \
ctype v1 = unbox_exb(n1, ctype, CET_NUMBER, &err); \
if (err) return; \
push_stack(box_number(v1 op v2)); \
}
#define DEF_BOX(name, val_type, box_member, type_tag) \
struct value_box *box_##name(val_type val) { \
struct value_box *tmp = xcalloc(1, sizeof(*tmp)); \
tmp->type = type_tag; \
tmp->box_member = val; \
return tmp; \
}
struct {
struct value_box *stack[STACK_SIZE];
size_t sp;
} data_stack;
struct dict_table *dict;
extern struct dict_ent *dict_last; // pointer to the last added dictionary entry
size_t pc;
char *next_word;
char **program;
size_t program_len;
int compile_mode;
int error_flag;
void error(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
error_flag = 1;
}
intmax_t xstrtoimax(const char *restrict nptr, int base, int *restrict eflag)
{
errno = 0;
char *endptr;
intmax_t val = strtoimax(nptr, &endptr, base);
if (((val == INTMAX_MAX || val == INTMAX_MIN) && errno == ERANGE) || (!val && errno == EINVAL) || *endptr != '\0') {
if (eflag != NULL)
*eflag = 1;
else
fatal_error("invalid number on stream\n");
}
return val;
}
// unboxes a value, only if it has the same type as specified.
// must be cast to the appropiate C type, since it returns a void *
void *unbox_ex(struct value_box *val, enum comp_ent_type type, int *err)
{
#define SETNNIL(v) if ((v) != NULL) {*(v) = 1;}
if (val == NULL) {
SETNNIL(err);
return NULL;
}
if (val->type != type) {
error("invalid type\n");
SETNNIL(err);
return NULL;
}
switch (val->type) {
case CET_NUMBER: return &val->number;
case CET_WORD: return &val->word;
case CET_ADDR: return &val->addr;
default: UNREACHABLE();
}
return NULL;
#undef SETNNIL
}
void push_stack(struct value_box *val)
{
size_t *sp = &data_stack.sp;
if ((*sp + 1) > SIZE_ARR(data_stack.stack)) {
error("stack overflow\n");
return;
}
data_stack.stack[(*sp)++] = val;
}
struct value_box *pop_stack()
{
size_t *sp = &data_stack.sp;
if ((*sp - 1) == SIZE_MAX) {
error("stack underflow\n");
return NULL;
}
return data_stack.stack[--*sp];
}
static inline void print_box(const struct value_box *obj)
{
switch (obj->type) {
case CET_NUMBER:
printf("%jd", obj->number);
break;
case CET_WORD:
printf("<word at %p (%s)>", (void *)obj->word, obj->word->word_name);
break;
case CET_ADDR: puts("address"); break;
default: UNREACHABLE();
}
}
// non-recursive word dump)
void word_dump(const char *word)
{
struct dict_ent *ent;
if ((ent = dict_lookup(dict, word)) != NULL) {
if (ent->flags & FLAG_NATIVE) {
printf("<native word at %p (%s)>\n", (void *)ent, ent->word_name);
return;
}
printf(": %s ", word);
for (size_t i = 0; i < ent->fn_def->len; ++i) {
struct value_box *cent = &(ent->fn_def->ents[i]);
switch (cent->type) {
case CET_ADDR:
printf("addr\n");
break;
case CET_WORD:
printf("%s ", cent->word->word_name);
break;
case CET_NUMBER:
printf("%jd ", cent->number);
break;
default: UNREACHABLE();
}
}
puts(";");
} else {
error("no such word `%s`\n", word);
}
}
DEF_BOX(number, INT_TYPE, number, CET_NUMBER)
DEF_BOX(word, struct dict_ent *, word, CET_WORD)
// --------------- native (primitives) --------------- //
DEF_ARITH_FN(+, add, INT_TYPE)
DEF_ARITH_FN(-, sub, INT_TYPE)
DEF_ARITH_FN(*, mul, INT_TYPE)
DEF_ARITH_FN(/, div, INT_TYPE)
DEF_ARITH_FN(==, equal, INT_TYPE)
DEF_ARITH_FN(<, less, INT_TYPE)
DEF_NATIVE(clearstack) {
while (data_stack.sp > 0)
free(data_stack.stack[--data_stack.sp]);
}
// ' <word> returns the address of a dictionary entry, aborting if it does not exist
DEF_NATIVE(quote) {
++pc;
if (strnil(next_word))
error("' requires a name\n");
struct dict_ent *ent = dict_lookup(dict, next_word);
if (ent == NULL) {
error("no such word `%s'\n", next_word);
return;
}
push_stack(box_word(ent));
}
// allot (n -- ) adds n bytes to the parameter field of the most recently defined word
DEF_NATIVE(allot) {
int err = 0;
const size_t nbytes = unbox_exsp(size_t, CET_NUMBER, &err);
if (err)
return;
const size_t org_len = dict_last->fn_def->len;
const size_t len = org_len + nbytes;
struct value_box **ents = &dict_last->fn_def->ents;
dict_last->fn_def->capacity = len;
*ents = reallocarray(*ents, len, sizeof(struct value_box));
// printf("allot: %d\n", len - dict_last->fn_def->len);
// clear the newly allocated part
//memset(*ents + org_len, 0, len - org_len);
}
DEF_NATIVE(comma) {
struct comp_word *pf = dict_last->fn_def;
if (pf->ents == NULL) {
pf->capacity = 4;
pf->ents = xcalloc(pf->capacity, sizeof(struct value_box));
} else if (pf->len + 1 > pf->capacity) {
pf->capacity <<= 1;
pf->ents = reallocarray(pf->ents, pf->capacity, sizeof(struct value_box));
}
struct value_box *val = pop_stack();
if (val == NULL)
return;
pf->ents[pf->len++] = *val;
}
DEF_NATIVE(create) {
++pc;
if (strnil(next_word))
error("create requires a name\n");
struct comp_word *param = xcalloc(1, sizeof(*param));
dict_insert(dict, next_word, 0, NULL, param);
}
DEF_NATIVE(forget) {
++pc;
if (strnil(next_word))
error("forget requires a name\n");
struct dict_ent *ent;
if ((ent = dict_lookup(dict, next_word)) == NULL) {
error("no such word `%s'\n", next_word);
return;
}
if (!(ent->flags & FLAG_NATIVE))
free(ent->fn_def->ents);
dict_delete(ent);
}
DEF_NATIVE(see) {
++pc;
if (strnil(next_word)) {
error("see requires a name\n");
return;
}
word_dump(next_word);
}
DEF_NATIVE(words) {
for (size_t i = 0; i < dict->capacity; ++i)
if (IS_ENTRY(&dict->entries[i]))
printf("%s ", dict->entries[i].word_name);
putchar('\n');
}
DEF_NATIVE(compile) {
compile_mode = 1;
}
DEF_NATIVE(endcompile) {
compile_mode = 0;
}
DEF_NATIVE(dup) {
push_stack(data_stack.stack[data_stack.sp - 1]);
}
DEF_NATIVE(drop) {
free(pop_stack());
}
DEF_NATIVE(over) {
push_stack(data_stack.stack[data_stack.sp - 2]);
}
DEF_NATIVE(swap) {
struct value_box *e2 = pop_stack();
struct value_box *e1 = pop_stack();
if (e1 == NULL || e2 == NULL)
return;
push_stack(e2);
push_stack(e1);
}
DEF_NATIVE(zero) {
int err = 0;
struct value_box *tmp = box_number(!unbox_exsp(INT_TYPE, CET_NUMBER, &err));
if (err)
return;
push_stack(tmp);
}
DEF_NATIVE(emit) {
int err = 0;
int chr = unbox_exsp(int, CET_NUMBER, &err);
if (!err)
putchar(chr);
}
DEF_NATIVE(print) {
struct value_box *val = pop_stack();
if (val == NULL)
return;
print_box(val);
putchar('\n');
free(val);
}
DEF_NATIVE(prints) {
if (data_stack.sp) {
size_t c = 0;
printf("stack dump: <%zu> ", data_stack.sp);
while (c < data_stack.sp) {
print_box(data_stack.stack[c++]);
putchar(' ');
}
putchar('\n');
}
}
static inline void append_to_code(struct value_box *slot, const char *current_word)
{
int errn = 0;
intmax_t val = xstrtoimax(current_word, 0, &errn);
if (!errn) {
slot->type = CET_NUMBER;
slot->number = val;
} else {
struct dict_ent *ent = dict_lookup(dict, current_word);
if (ent == NULL)
fatal_error("no such word `%s`\n", current_word);
slot->type = CET_WORD;
slot->word = ent;
}
}
void eval_def(struct comp_word *def);
void if_fn()
{
size_t start_pc = ++pc;
size_t end_pc;
while (pc < program_len && strcmp(program[pc], "end") != 0)
++pc;
if (pc >= program_len)
fatal_error("expected `end`, reached EOP\n");
end_pc = pc;
int err = 0;
int cond = unbox_exsp(int, CET_NUMBER, &err);
if (err)
fatal_error("if requires a condition\n");
if (cond) {
char **program_slice = program + start_pc;
size_t len = end_pc - start_pc;
struct value_box *code = xcalloc(len, sizeof(*code));
struct comp_word c = {.len = len, .capacity = len, .ents = code};
for (size_t i = 0; i < len; ++i)
append_to_code(&code[i], program_slice[i]);
eval_def(&c);
free(code);
}
}
// --------------- interpretation --------------- //
void eval_def(struct comp_word *def)
{
for (size_t i = 0; i < def->len; ++i) {
const struct value_box *cent = &(def->ents[i]);
struct dict_ent *ent;
switch (cent->type) {
case CET_WORD:
ent = cent->word;
if (ent->flags & FLAG_NATIVE)
ent->nat_fn();
else
eval_def(ent->fn_def);
break;
case CET_NUMBER:
push_stack(box_number(cent->number));
break;
default: UNREACHABLE();
}
}
}
void interpret(char **prog, size_t prog_len)
{
struct comp_word *c_word = NULL;
char *fn_name = NULL;
for (; pc < prog_len; ++pc) {
if (error_flag) {
pc = prog_len;
break;
}
char *word = prog[pc];
next_word = (pc + 1) < prog_len ? prog[pc + 1] : NULL;
if (compile_mode) {
if (*word == ';') {
dict_insert(dict, fn_name, 0, NULL, c_word);
c_word = NULL;
fn_name = NULL;
compile_mode = 0;
continue;
}
if (c_word == NULL) {
c_word = xcalloc(1, sizeof(*c_word));
c_word->ents = xcalloc(256, sizeof(*c_word->ents));
}
if (fn_name == NULL) {
fn_name = word;
continue;
}
append_to_code(&(c_word->ents[c_word->len++]), word);
} else {
int nef = 0;
INT_TYPE num = xstrtoimax(word, 0, &nef);
if (!nef) {
push_stack(box_number(num));
continue;
}
struct dict_ent *ent;
if ((ent = dict_lookup(dict, word)) != NULL) {
if (ent->flags & FLAG_NATIVE)
ent->nat_fn();
else
eval_def(ent->fn_def);
} else {
fatal_error("interpretation: no such word `%s`\n", word);
}
}
}
}
// --------------- Parsing --------------- //
void tokenize(char *s, char **buf, size_t buflen, size_t *i)
{
const char delim = ' ';
char *p = s, *wp = s;
while (*p != '\0') {
if (*p == '(') { // eat block comment
while (*p != ')' && *p != '\0')
++p;
p += 2; // jump past the ) and space
wp = p;
continue;
} else if (*p == '\\') {
while (*p++ != '\0');
wp = p;
continue;
}
if (*p == delim) {
if (*i + 1 > buflen)
return;
*p = '\0';
buf[(*i)++] = strdup(wp);
wp = ++p;
} else {
++p;
}
}
if (wp < p && *i < buflen) // handle last element
buf[(*i)++] = strdup(wp);
}
size_t parse_program(FILE *fp, char **buf, size_t buflen)
{
size_t i = 0;
char line_buf[1024] = {0};
while (fgets(line_buf, sizeof(line_buf), fp) != NULL) {
STRIP_FINAL_NL(line_buf);
tokenize(line_buf, buf, buflen, &i);
}
return i;
}
void repl(void)
{
char *prog[1024] = {0};
size_t prog_len = 0;
char *line_buf = NULL;
size_t line_buf_len = 0;
ssize_t rb = 0;
int prompt_comp_mode = 0;
errno = 0;
for (;;) {
printf("[%s]%s ", error_flag ? "err" : "ok", !prompt_comp_mode ? ">" : ">>");
fflush(stdout);
error_flag = 0;
if ((rb = getline(&line_buf, &line_buf_len, stdin)) < 0)
break;
STRIP_FINAL_NL(line_buf);
tokenize(line_buf, prog, SIZE_ARR(prog), &prog_len);
if (strchr(line_buf, ':') != NULL)
prompt_comp_mode = 1;
else if (strchr(line_buf, ';') != NULL)
prompt_comp_mode = 0;
if (!prompt_comp_mode)
interpret(prog, prog_len);
}
if (rb < 0 && errno)
fatal_error("getline err\n");
free(line_buf);
}
void init()
{
dict = dict_new_env(NULL);
dict_insert(dict, "+", FLAG_NATIVE, add_fn, NULL);
dict_insert(dict, "-", FLAG_NATIVE, sub_fn, NULL);
dict_insert(dict, "*", FLAG_NATIVE, mul_fn, NULL);
dict_insert(dict, "=", FLAG_NATIVE, equal_fn, NULL);
dict_insert(dict, "<", FLAG_NATIVE, less_fn, NULL);
dict_insert(dict, ".", FLAG_NATIVE, print_fn, NULL);
dict_insert(dict, ".s", FLAG_NATIVE, prints_fn, NULL);
dict_insert(dict, "zero?", FLAG_NATIVE, zero_fn, NULL);
dict_insert(dict, ":", FLAG_NATIVE, compile_fn, NULL);
dict_insert(dict, ";", FLAG_NATIVE | FLAG_IMMEDIATE, endcompile_fn, NULL);
dict_insert(dict, "'", FLAG_NATIVE, quote_fn, NULL);
dict_insert(dict, ",", FLAG_NATIVE, comma_fn, NULL);
dict_insert(dict, "print", FLAG_NATIVE, print_fn, NULL);
dict_insert(dict, "see", FLAG_NATIVE, see_fn, NULL);
dict_insert(dict, "dup", FLAG_NATIVE, dup_fn, NULL);
dict_insert(dict, "drop", FLAG_NATIVE, drop_fn, NULL);
dict_insert(dict, "over", FLAG_NATIVE, over_fn, NULL);
dict_insert(dict, "swap", FLAG_NATIVE, swap_fn, NULL);
dict_insert(dict, "create", FLAG_NATIVE, create_fn, NULL);
dict_insert(dict, "forget", FLAG_NATIVE, forget_fn, NULL);
dict_insert(dict, "allot", FLAG_NATIVE, allot_fn, NULL);
dict_insert(dict, "cst", FLAG_NATIVE, clearstack_fn, NULL);
dict_insert(dict, "words", FLAG_NATIVE, words_fn, NULL);
dict_insert(dict, "emit", FLAG_NATIVE, emit_fn, NULL);
dict_insert(dict, "if", FLAG_NATIVE, if_fn, NULL);
}
int main(int argc, char **argv)
{
init();
if (argc < 2) {
printf("nsforth interactive repl (build: %s, %s)\n", __DATE__, __TIME__);
repl();
} else {
FILE *fp = fopen(*++argv, "r");
if (fp == NULL)
fatal_error("error opening '%s': %s\n", *argv, strerror(errno));
char *prog[1024] = {0};
size_t prog_len = parse_program(fp, prog, SIZE_ARR(prog));
fclose(fp);
program = prog;
program_len = prog_len;
interpret(prog, prog_len);
}
dict_destroy_env(dict);
return EXIT_SUCCESS;
}