Stack handling and basic math ops working

This commit is contained in:
Fabio Scotto di Santolo
2025-12-13 16:16:10 +01:00
parent 7461069855
commit 3a07fea447
3 changed files with 375 additions and 301 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
toyforth

View File

@@ -1 +1 @@
5 10 + dup * print 10 1 +

View File

@@ -1,37 +1,42 @@
#include <assert.h>
#include <ctype.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <ctype.h>
#include <assert.h>
#define TFOBJ_TYPE_INT 0 #define TFOBJ_TYPE_INT 0
#define TFOBJ_TYPE_STR 1 #define TFOBJ_TYPE_STR 1
#define TFOBJ_TYPE_BOOL 2 #define TFOBJ_TYPE_BOOL 2
#define TFOBJ_TYPE_LIST 3 #define TFOBJ_TYPE_LIST 3
#define TFOBJ_TYPE_SYMBOL 4 #define TFOBJ_TYPE_SYMBOL 4
#define TFOBJ_TYPE_ALL 255 // Used by listPop() and other functions
#define TF_ERR 1
#define TF_OK 0
/* ############################# Data Structures ########################################## */ /* ############################# Data Structures ########################################## */
typedef struct tfobj { typedef struct tfobj {
int refcount; int refcount;
int type; // TFOBJ_TYPE_* int type; // TFOBJ_TYPE_*
union { union {
int i; int i;
struct { struct {
char *ptr; char *ptr;
size_t len; size_t len;
int quoted; int quoted;
} str; } str;
struct { struct {
struct tfobj **ele; struct tfobj **ele;
size_t len; size_t len;
} list; } list;
}; };
} tfobj; } tfobj;
typedef struct tfparser { typedef struct tfparser {
char *prg; // The program to compile into a list. char *prg; // The program to compile into a list.
char *p; // Next token to parse. char *p; // Next token to parse.
} tfparser; } tfparser;
/* Function table entry: each of this entry represents a symbol name /* Function table entry: each of this entry represents a symbol name
@@ -39,410 +44,478 @@ typedef struct tfparser {
*/ */
struct tfctx; struct tfctx;
typedef struct FunctionTableEntry { typedef struct FunctionTableEntry {
tfobj *name; tfobj *name;
void (*callback) (struct tfctx *ctx, tfobj *name); int (*callback)(struct tfctx *ctx, char *name);
tfobj *user_func; tfobj *user_func;
} tffuncentry; } tffuncentry;
struct FunctionTable { struct FunctionTable {
tffuncentry **func_table; tffuncentry **func_table;
size_t func_count; size_t func_count;
}; };
/* Our execution context. */ /* Our execution context. */
typedef struct tfctx { typedef struct tfctx {
tfobj *stack; tfobj *stack;
struct FunctionTable functable; struct FunctionTable functable;
} tfctx; } tfctx;
/* ############################# Function Prototypes #################################### */ /* ############################# Function Prototypes #################################### */
void retain(tfobj *o); void retain(tfobj *o);
void release(tfobj *o); void release(tfobj *o);
int basicMathFunctions(tfctx *ctx, char *name);
/* ############################# Allocations wrappers ################################### */ /* ############################# Allocations wrappers ################################### */
void *xmalloc(size_t size) void *xmalloc(size_t size) {
{ void *ptr = malloc(size);
void *ptr = malloc(size); if (ptr == NULL) {
if (ptr == NULL) { fprintf(stderr, "Out of memory allocating %zu bytes", size);
fprintf(stderr, "Out of memory allocating %zu bytes", size); exit(1);
exit(1); }
} return ptr;
return ptr;
} }
void *xrealloc(void *oldptr, size_t size) void *xrealloc(void *oldptr, size_t size) {
{ void *ptr = realloc(oldptr, size);
void *ptr = realloc(oldptr, size); if (ptr == NULL) {
if (ptr == NULL) { fprintf(stderr, "Out of memory allocating %zu bytes", size);
fprintf(stderr, "Out of memory allocating %zu bytes", size); exit(1);
exit(1); }
} return ptr;
return ptr;
} }
/* ######################### Object related functions ################################### /* ######################### Object related functions ###################################
* The following functions allocate Toy Forth objects of different types. * The following functions allocate ToyForth objects of different types.
*/ */
/*Allocate and initialize a new Toy Forth object. */ /*Allocate and initialize a new Toy Forth object. */
tfobj *createObject(int type) tfobj *createObject(int type) {
{ tfobj *o = xmalloc(sizeof(tfobj));
tfobj *o = xmalloc(sizeof(tfobj)); o->type = type;
o->type = type; o->refcount = 1;
o->refcount = 1; return o;
return o;
} }
tfobj *createIntObject(int i) tfobj *createIntObject(int i) {
{ tfobj *obj = createObject(TFOBJ_TYPE_INT);
tfobj *obj = createObject(TFOBJ_TYPE_INT); obj->i = i;
obj->i = i; return obj;
return obj;
} }
tfobj *createBoolObject(int b) tfobj *createBoolObject(int b) {
{ tfobj *obj = createObject(TFOBJ_TYPE_BOOL);
tfobj *obj = createObject(TFOBJ_TYPE_BOOL); obj->i = b;
obj->i = b; return obj;
return obj;
} }
/* Free an object and all the other nested objects. */ /* Free an object and all the other nested objects. */
void freeObject(tfobj *obj) { void freeObject(tfobj *obj) {
switch (obj->type) { switch (obj->type) {
case TFOBJ_TYPE_LIST: case TFOBJ_TYPE_LIST:
for (size_t j = 0; j < obj->list.len; j++) { for (size_t j = 0; j < obj->list.len; j++) {
tfobj *ele = obj->list.ele[j]; tfobj *ele = obj->list.ele[j];
release(ele); release(ele);
}
break;
case TFOBJ_TYPE_SYMBOL:
case TFOBJ_TYPE_STR:
free(obj->str.ptr);
break;
} }
free(obj); break;
case TFOBJ_TYPE_SYMBOL:
case TFOBJ_TYPE_STR:
free(obj->str.ptr);
break;
}
free(obj);
} }
void retain(tfobj *obj) void retain(tfobj *obj) { obj->refcount++; }
{
obj->refcount++;
}
void release(tfobj *obj) void release(tfobj *obj) {
{ assert(obj->refcount > 0);
assert(obj->refcount > 0); obj->refcount--;
obj->refcount--; if (obj->refcount == 0)
if (obj->refcount == 0) freeObject(obj);
freeObject(obj);
} }
void printObject(tfobj *obj) { void printObject(tfobj *obj) {
switch (obj->type) { switch (obj->type) {
case TFOBJ_TYPE_INT: case TFOBJ_TYPE_INT:
printf("%d", obj->i); printf("%d", obj->i);
break; break;
case TFOBJ_TYPE_LIST: case TFOBJ_TYPE_LIST:
printf("["); printf("[");
for (size_t j = 0; j < obj->list.len; j++) { for (size_t j = 0; j < obj->list.len; j++) {
tfobj *o = obj->list.ele[j]; tfobj *o = obj->list.ele[j];
printObject(o); printObject(o);
if (j != o->list.len-1) if (j != o->list.len - 1)
printf(" "); printf(" ");
}
printf("]");
break;
case TFOBJ_TYPE_STR:
printf("\"%s\"", obj->str.ptr);
break;
case TFOBJ_TYPE_SYMBOL:
printf("%s", obj->str.ptr);
break;
default:
printf("?");
break;
} }
printf("]");
break;
case TFOBJ_TYPE_STR:
printf("\"%s\"", obj->str.ptr);
break;
case TFOBJ_TYPE_SYMBOL:
printf("%s", obj->str.ptr);
break;
default:
printf("?");
break;
}
} }
/* ############################ String Object ########################################## */ /* ############################ String Object ########################################## */
tfobj *createStringObject(char *s, size_t len) tfobj *createStringObject(char *s, size_t len) {
{ tfobj *obj = createObject(TFOBJ_TYPE_STR);
tfobj *obj = createObject(TFOBJ_TYPE_STR); obj->str.ptr = xmalloc(len + 1);
obj->str.ptr = xmalloc(len+1); obj->str.len = len;
obj->str.len = len; memcpy(obj->str.ptr, s, len);
memcpy(obj->str.ptr, s, len); obj->str.ptr[len] = 0;
obj->str.ptr[len] = 0; return obj;
return obj;
} }
tfobj *createSymbolObject(char *s, size_t len) tfobj *createSymbolObject(char *s, size_t len) {
{ tfobj *obj = createStringObject(s, len);
tfobj *obj = createStringObject(s, len); obj->type = TFOBJ_TYPE_SYMBOL;
obj->type = TFOBJ_TYPE_SYMBOL; return obj;
return obj;
} }
/* Compare the two string objects 'a' and 'b', returns 0 if they are /* Compare the two string objects 'a' and 'b', returns 0 if they are
* the same, '1' if a > b , '-1' if a < b. * the same, '1' if a > b , '-1' if a < b.
* The comparison is performed using memcmp(). */ * The comparison is performed using memcmp(). */
int compareStringObject(tfobj *a, tfobj *b) int compareStringObject(tfobj *a, tfobj *b) {
{ size_t minlen = a->str.len < b->str.len ? a->str.len : b->str.len;
size_t minlen = a->str.len < b->str.len ? a->str.len : b->str.len; int cmp = memcmp(a->str.ptr, b->str.ptr, minlen);
int cmp = memcmp(a->str.ptr, b->str.ptr, minlen); if (cmp == 0) {
if (cmp == 0) { if (a->str.len == b->str.len)
if (a->str.len == b->str.len) return 0; return 0;
else if (a->str.len > b->str.len) return 1; else if (a->str.len > b->str.len)
else return -1; return 1;
} else { else
if (cmp < 0) return -1; return -1;
else return 1; } else {
} if (cmp < 0)
return -1;
else
return 1;
}
} }
/* ############################ List Object ############################################ */ /* ############################ List Object ############################################ */
tfobj *createListObject(void) tfobj *createListObject(void) {
tfobj *obj = createObject(TFOBJ_TYPE_LIST);
obj->list.ele = NULL;
obj->list.len = 0;
return obj;
}
tfobj *listPopType(tfctx *ctx, int type) {
tfobj *stack = ctx->stack;
if (stack->list.len == 0)
return NULL;
tfobj *to_pop = stack->list.ele[stack->list.len - 1];
if (type != TFOBJ_TYPE_ALL && to_pop->type != type)
return NULL;
stack->list.len--;
if (stack->list.len == 0) {
free(stack->list.ele);
stack->list.ele = NULL;
} else {
stack->list.ele =
xrealloc(stack->list.ele, sizeof(tfobj *) * (stack->list.len));
}
return to_pop;
}
tfobj *listPop(tfctx *ctx)
{ {
tfobj *obj = createObject(TFOBJ_TYPE_LIST); return listPopType(ctx, TFOBJ_TYPE_ALL);
obj->list.ele = NULL;
obj->list.len = 0;
return obj;
} }
/* Add the new element at the end of the 'list'. /* Add the new element at the end of the 'list'.
* It is up to the caller to increment the reference count of the * It is up to the caller to increment the reference count of the
* element added to the list, if needed. */ * element added to the list, if needed. */
void listPush(tfobj *l, tfobj *ele) void listPush(tfobj *l, tfobj *ele) {
{ l->list.ele = xrealloc(l->list.ele, sizeof(tfobj *) * (l->list.len + 1));
l->list.ele = xrealloc(l->list.ele, sizeof(tfobj*) * (l->list.len+1)); l->list.ele[l->list.len] = ele;
l->list.ele[l->list.len] = ele; l->list.len++;
l->list.len++;
} }
/* ####################### Turn program into toy forth list ############################ */ /* ####################### Turn program into toy forth list ############################ */
void parseSpaces(tfparser *parser) void parseSpaces(tfparser *parser) {
{ while (isspace(parser->p[0]))
while (isspace(parser->p[0])) parser->p++; parser->p++;
} }
#define MAX_NUM_LEN 128 #define MAX_NUM_LEN 128
tfobj *parseNumber(tfparser *parser) tfobj *parseNumber(tfparser *parser) {
{ char buf[MAX_NUM_LEN];
char buf[MAX_NUM_LEN]; char *start = parser->p;
char *start = parser->p; char *end;
char *end;
if (parser->p[0] == '-') parser->p++; if (parser->p[0] == '-')
while (parser->p[0] && isdigit(parser->p[0])) parser->p++; parser->p++;
end = parser->p; while (parser->p[0] && isdigit(parser->p[0]))
int numLen = end - start; parser->p++;
if (numLen >= MAX_NUM_LEN) return NULL; end = parser->p;
int numLen = end - start;
if (numLen >= MAX_NUM_LEN)
return NULL;
memcpy(buf, start, numLen); memcpy(buf, start, numLen);
buf[numLen] = 0; buf[numLen] = 0;
tfobj *obj = createIntObject(atoi(buf)); tfobj *obj = createIntObject(atoi(buf));
return obj; return obj;
} }
/* Return true if the character 'c' is one of the characters /* Return true if the character 'c' is one of the characters
* acceptable for our symbols. * acceptable for our symbols.
*/ */
int isSymbolChar(int c) int isSymbolChar(int c) {
{ char symchars[] = "+-*/%";
char symchars[] = "+-*/%"; return isalpha(c) || strchr(symchars, c) != NULL;
return isalpha(c) || strchr(symchars, c) != NULL;
} }
tfobj *parseSymbol(tfparser *parser) tfobj *parseSymbol(tfparser *parser) {
{ char *start = parser->p;
char *start = parser->p; while (parser->p[0] && isSymbolChar(parser->p[0]))
while (parser->p[0] && isSymbolChar(parser->p[0])) parser->p++; parser->p++;
int len = parser->p - start; int len = parser->p - start;
return createSymbolObject(start, len); return createSymbolObject(start, len);
} }
tfobj *compile(char *prg) tfobj *compile(char *prg) {
{ tfparser parser;
tfparser parser; parser.prg = prg;
parser.prg = prg; parser.p = prg;
parser.p = prg;
tfobj *parsed = createListObject(); tfobj *parsed = createListObject();
while (parser.p) { while (parser.p) {
tfobj *obj; tfobj *obj;
char *token_start = parser.p; char *token_start = parser.p;
parseSpaces(&parser); parseSpaces(&parser);
if (parser.p[0] == 0) break; // End of program reached. if (parser.p[0] == 0)
break; // End of program reached.
if (isdigit(parser.p[0]) || if (isdigit(parser.p[0]) || (parser.p[0] == '-' && isdigit(parser.p[1]))) {
(parser.p[0] == '-' && isdigit(parser.p[1]))) { obj = parseNumber(&parser);
obj = parseNumber(&parser); } else if (isSymbolChar(parser.p[0])) {
} else if (isSymbolChar(parser.p[0])) { obj = parseSymbol(&parser);
obj = parseSymbol(&parser); } else {
} else { obj = NULL;
obj = NULL;
}
// Check if the current token produced a parsing error.
if (obj == NULL) {
release(parsed);
printf("Syntax error near: %32s ...\n", token_start);
return NULL;
} else {
listPush(parsed, obj);
}
} }
return parsed; // Check if the current token produced a parsing error.
if (obj == NULL) {
release(parsed);
printf("Syntax error near: %32s ...\n", token_start);
return NULL;
} else {
listPush(parsed, obj);
}
}
return parsed;
} }
/* ############################# Basic Standard Library ################################# */ int ctxCheckStackMinLen(tfctx *ctx, size_t min)
void basicMathFunctions(tfctx *ctx, tfobj *name)
{ {
// TODO unimplemeted function return (ctx->stack->list.len < min) ? TF_ERR : TF_OK;
/* if (ctxCheckStackMinLen(ctx, 2)) return; */ }
/* tfobj *b = ctxStackPop(ctx, TFOBJ_TYPE_INT); */
/* tfobj *a = ctxStackPop(ctx, TFOBJ_TYPE_INT); */
/* if (a == NULL || b == NULL) return; */
/* int result = a->i + b->i; */ /* Pop the top element from the interpreter main stack, assuming it
/* ctxStackPush(ctx, createIntObject(result)); */ * will match 'type', otherwise NULL is returned. Also the function
* returns NULL if the stack is empty.
*
* The reference counting of the popped object is not modified: it
* is assumed that we just transfer the ownership from stack to
* the caller. */
tfobj *ctxStackPop(tfctx *ctx, int type)
{
return listPopType(ctx, type);
}
/* Just push the object on the interpreter main stack */
void ctxStackPush(tfctx *ctx, tfobj *obj)
{
listPush(ctx->stack, obj);
} }
/* ############################# Execution and context ################################## */ /* ############################# Execution and context ################################## */
tffuncentry *getFunctionByName(tfctx *ctx, tfobj *name) /* Resolve the function scanning the function table looking for a matching
{ * name. If a matching function was not found, NULL is returned, otherwise
for (size_t j = 0; j < ctx->functable.func_count; j++) { * the function returns the function entry object. */
tffuncentry *fe = ctx->functable.func_table[j]; tffuncentry *getFunctionByName(tfctx *ctx, tfobj *name) {
if (compareStringObject(fe->name, name) == 0) for (size_t j = 0; j < ctx->functable.func_count; j++) {
return fe; tffuncentry *fe = ctx->functable.func_table[j];
} if (compareStringObject(fe->name, name) == 0)
return NULL; return fe;
}
return NULL;
} }
/* Push a new function entry in the context. It's up to the caller /* Push a new function entry in the context. It's up to the caller
* to set either the C callback or the list representing the user * to set either the C callback or the list representing the user
* defined function */ * defined function */
tffuncentry *registerFunction(tfctx *ctx, tfobj *name) tffuncentry *registerFunction(tfctx *ctx, tfobj *name) {
{ ctx->functable.func_table =
ctx->functable.func_table = xrealloc(ctx->functable.func_table, xrealloc(ctx->functable.func_table,
sizeof(tffuncentry*) * ctx->functable.func_count + 1); sizeof(tffuncentry *) * ctx->functable.func_count + 1);
tffuncentry *fe = xmalloc(sizeof(tffuncentry)); tffuncentry *fe = xmalloc(sizeof(tffuncentry));
ctx->functable.func_table[ctx->functable.func_count] = fe; ctx->functable.func_table[ctx->functable.func_count] = fe;
ctx->functable.func_count++; ctx->functable.func_count++;
fe->name = name; fe->name = name;
retain(name); retain(name);
fe->callback = NULL; fe->callback = NULL;
fe->user_func = NULL; fe->user_func = NULL;
return fe; return fe;
} }
/* Register a new function with the given name in the function table /* Register a new function with the given name in the function table
* of the context. The function can't fail since if a function with the * of the context. The function can't fail since if a function with the
* same name already exist, it gets replaced by the new one. */ * same name already exist, it gets replaced by the new one. */
void registerCFunction(tfctx *ctx, char *name, void (*callback)(tfctx *ctx, tfobj *name)) void registerCFunction(tfctx *ctx, char *name,
{ int (*callback)(tfctx *ctx, char *name)) {
tffuncentry *fe; tffuncentry *fe;
tfobj *oname = createStringObject(name, strlen(name)); tfobj *oname = createStringObject(name, strlen(name));
fe = getFunctionByName(ctx, oname); fe = getFunctionByName(ctx, oname);
if (fe) { if (fe) {
if (fe->user_func) { if (fe->user_func) {
release(fe->user_func); release(fe->user_func);
fe->user_func = NULL; fe->user_func = NULL;
}
fe->callback = callback;
} else {
fe = registerFunction(ctx, oname);
fe->callback = callback;
} }
release(oname); fe->callback = callback;
} else {
fe = registerFunction(ctx, oname);
fe->callback = callback;
}
release(oname);
} }
tfctx *createContext(void) tfctx *createContext(void) {
{ tfctx *ctx = xmalloc(sizeof(*ctx));
tfctx *ctx = xmalloc(sizeof(*ctx)); ctx->stack = createListObject();
ctx->stack = createListObject(); ctx->functable.func_table = NULL;
ctx->functable.func_table = NULL; ctx->functable.func_count = 0;
ctx->functable.func_count = 0; registerCFunction(ctx, "+", basicMathFunctions);
registerCFunction(ctx, "+", basicMathFunctions); return ctx;
return ctx;
} }
/* Try to resolve and call the function associated with the symbol /* Try to resolve and call the function associated with the symbol
* name 'word'. Return 0 if the symbol was actually bound to some * name 'word'. Return 0 if the symbol was actually bound to some
* function and was executed, return 1 otherwise (on error). * function and was executed, return 1 otherwise (on error).
*/ */
int callSymbol(tfctx *ctx, tfobj *word) int callSymbol(tfctx *ctx, tfobj *word) {
{ tffuncentry *fe = getFunctionByName(ctx, word);
tffuncentry *fe = getFunctionByName(ctx, word); if (fe == NULL)
if (fe == NULL) return 1; return TF_ERR;
return 0; if (fe->user_func) {
// TODO
return TF_ERR;
} else {
return fe->callback(ctx, fe->name->str.ptr);
}
return TF_OK;
} }
/* Execute the Toy Forth program stored into the list 'prg'. */ /* Execute the Toy Forth program stored into the list 'prg'. */
void exec(tfctx *ctx, tfobj *prg) { int exec(tfctx *ctx, tfobj *prg) {
assert(prg->type == TFOBJ_TYPE_LIST); assert(prg->type == TFOBJ_TYPE_LIST);
for (size_t j = 0; j < prg->list.len; j++) { for (size_t j = 0; j < prg->list.len; j++) {
tfobj *word = prg->list.ele[j]; tfobj *word = prg->list.ele[j];
switch (word->type) { switch (word->type) {
case TFOBJ_TYPE_SYMBOL: case TFOBJ_TYPE_SYMBOL:
callSymbol(ctx, word); if (callSymbol(ctx, word) == TF_ERR) {
break; printf("Runtime error\n");
default: return TF_ERR;
listPush(ctx->stack, word); }
retain(word); break;
break; default:
} ctxStackPush(ctx, word);
retain(word);
break;
} }
}
return TF_OK;
} }
/* ############################### Main ################################################# */
int main(int argc, char **argv) /* ############################# Basic Standard Library ################################# */
{
if (argc != 2) {
fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
return 1;
}
// Read the program in memory, for later parsing. int basicMathFunctions(tfctx *ctx, char *name) {
FILE *fp = fopen(argv[1], "r"); if (ctxCheckStackMinLen(ctx, 2))
if (fp == NULL) { return TF_ERR;
perror("Opening Toy Forth program"); tfobj *b = ctxStackPop(ctx, TFOBJ_TYPE_INT);
return 1; if (b == NULL) return TF_ERR;
} tfobj *a = ctxStackPop(ctx, TFOBJ_TYPE_INT);
if (a == NULL) {
ctxStackPush(ctx, b);
return TF_ERR;
}
fseek(fp, 0, SEEK_END); int result = a->i + b->i;
long file_size = ftell(fp); switch (name[0]) {
char *prgtext = xmalloc(file_size+1); case '+':
fseek(fp, 0, SEEK_SET); result = a->i + b->i;
fread(prgtext, file_size, 1, fp); break;
prgtext[file_size] = 0; case '-':
fclose(fp); result = a->i - b->i;
break;
case '*':
result = a->i * b->i;
break;
}
release(a);
release(b);
//printf("Program text: \"%s\"\n", prgtext); ctxStackPush(ctx, createIntObject(result));
tfobj *prg = compile(prgtext); return TF_OK;
printObject(prg); }
printf("\n");
/* ###################################### Main ############################################ */
tfctx *ctx = createContext();
exec(ctx, prg); int main(int argc, char **argv) {
if (argc != 2) {
printf("Stack content at end: "); fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
printObject(ctx->stack); return 1;
printf("\n"); }
return 0; // Read the program in memory, for later parsing.
FILE *fp = fopen(argv[1], "r");
if (fp == NULL) {
perror("Opening Toy Forth program");
return 1;
}
fseek(fp, 0, SEEK_END);
long file_size = ftell(fp);
char *prgtext = xmalloc(file_size + 1);
fseek(fp, 0, SEEK_SET);
fread(prgtext, file_size, 1, fp);
prgtext[file_size] = 0;
fclose(fp);
// printf("Program text: \"%s\"\n", prgtext);
tfobj *prg = compile(prgtext);
printObject(prg);
printf("\n");
tfctx *ctx = createContext();
exec(ctx, prg);
printf("Stack content at end: ");
printObject(ctx->stack);
printf("\n");
return 0;
} }