summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorubq323 <ubq323@ubq323.website>2024-06-21 18:24:42 +0100
committerubq323 <ubq323@ubq323.website>2024-06-21 18:24:42 +0100
commitfbdfd9bf74b178a34543e0e347f441d689f73438 (patch)
treea415f804d07a9a0a6846ceafc86fad754fe645d6
parent151a7fd11fa4b30dec75fdcebdc30d95c76f7960 (diff)
refactor compilation of builtin forms
-rw-r--r--com.c259
-rw-r--r--tests/compile_error.out2
2 files changed, 159 insertions, 102 deletions
diff --git a/com.c b/com.c
index 45b1f80..c68e663 100644
--- a/com.c
+++ b/com.c
@@ -20,16 +20,144 @@ static void patch(State *S, Chunk *ch, size_t addr, uint16_t val) {
}
+
+static void compile_node(State *S, Chunk *ch, AstNode a);
+
+typedef void (*form_compiler)(State *S, Chunk *ch, AstVec l, Op op);
+typedef struct {
+ char *name;
+ int min_params;
+ bool ellipsis;
+ form_compiler action;
+ Op op;
+} BuiltinForm;
+
+
+void single_form(State *S, Chunk *ch, AstVec l, Op op) {
+ compile_node(S, ch, l.vals[1]);
+ chunk_wbc(S, ch, op);
+}
+
+void set_form(State *S, Chunk *ch, AstVec l, Op op) {
+ AstNode ident = l.vals[1];
+ if (ident.ty != AST_IDENT) {
+ fprintf(stderr, "set's first argument must be identifier");
+ exit(1);
+ }
+ ObjString *o = objstring_copy_cstr(S, ident.as.str);
+ compile_node(S, ch, l.vals[2]);
+ chunk_wbc(S, ch, OP_SETGLOBAL);
+ chunk_wbc(S, ch, chunk_wconst(S, ch, VAL_OBJ(o)));
+}
+
+void do_form(State *S, Chunk *ch, AstVec l, Op op) {
+ for (int i = 1; i < l.len - 1; i++) {
+ compile_node(S, ch, l.vals[i]);
+ chunk_wbc(S, ch, OP_DROP);
+ }
+ compile_node(S, ch, l.vals[l.len - 1]);
+}
+
+void if_form(State *S, Chunk *ch, AstVec l, Op op) {
+ // (if cond if-true if-false)
+ // cond
+ // 0branch ->A
+ // if-true
+ // skip ->B
+ // A: if-false
+ // B:
+ compile_node(S, ch, l.vals[1]);
+ chunk_wbc(S, ch, OP_0BRANCH);
+ size_t ph_a = placeholder(S, ch);
+ compile_node(S, ch, l.vals[2]);
+ chunk_wbc(S, ch, OP_SKIP);
+ size_t ph_b = placeholder(S, ch);
+ size_t dest_a = ch->bc.len;
+ compile_node(S, ch, l.vals[3]);
+ size_t dest_b = ch->bc.len;
+
+ patch(S, ch, ph_a, dest_a - ph_a - 2);
+ patch(S, ch, ph_b, dest_b - ph_b - 2);
+}
+
+void while_form(State *S, Chunk *ch, AstVec l, Op op) {
+ // (while cond body ...)
+ // A:
+ // cond
+ // 0branch ->B
+ // body ....
+ // redo ->A
+ // B:
+ // nil (while loop always returns nil)
+ size_t dest_a = ch->bc.len;
+ compile_node(S, ch, l.vals[1]);
+ chunk_wbc(S, ch, OP_0BRANCH);
+ size_t ph_b = placeholder(S, ch);
+ for (int i = 2; i < l.len; i++) {
+ compile_node(S, ch, l.vals[i]);
+ chunk_wbc(S, ch, OP_DROP);
+ }
+ chunk_wbc(S, ch, OP_REDO);
+ size_t ph_a = placeholder(S, ch);
+ size_t dest_b = ch->bc.len;
+ chunk_wbc(S, ch, OP_NIL);
+
+ patch(S, ch, ph_a, ph_a - dest_a + 2);
+ patch(S, ch, ph_b, dest_b - ph_b - 2);
+}
+
+void arith_form(State *S, Chunk *ch, AstVec l, Op op) {
+ compile_node(S, ch, l.vals[1]);
+ compile_node(S, ch, l.vals[2]);
+ chunk_wbc(S, ch, op);
+}
+
+static BuiltinForm builtin_forms[] = {
+ { "puts", 1, false, single_form, OP_PUTS },
+ { "print", 1, false, single_form, OP_PRINT },
+ { "set", 2, false, set_form, 0 },
+ { "do", 1, true, do_form, 0 },
+ { "if", 3, false, if_form, 0 },
+ { "while", 2, true, while_form, 0 },
+#define ARITH_OP(str, op) \
+ { str, 2, false, arith_form, op },
+ ARITH_OP("+", OP_ADD)
+ ARITH_OP("-", OP_SUB)
+ ARITH_OP("*", OP_MUL)
+ ARITH_OP("/", OP_DIV)
+ ARITH_OP("=", OP_EQU)
+ ARITH_OP("<", OP_CMP)
+ ARITH_OP("%", OP_MOD)
+#undef ARITH_OP
+ { 0 },
+};
+
+typedef struct {
+ char *name;
+ Op op;
+} BuiltinIdent;
+static BuiltinIdent builtin_idents[] = {
+ { "true", OP_TRUE },
+ { "false", OP_FALSE },
+ { "nil", OP_NIL },
+ { 0 },
+};
+
static void compile_node(State *S, Chunk *ch, AstNode a) {
switch (a.ty) {
case AST_IDENT:;
char *ident = a.as.str;
-
- if (0 == strcmp(ident, "true")) chunk_wbc(S, ch, OP_TRUE);
- else if (0 == strcmp(ident, "false")) chunk_wbc(S, ch, OP_FALSE);
- else if (0 == strcmp(ident, "nil")) chunk_wbc(S, ch, OP_NIL);
- else {
- // global read
+ BuiltinIdent *found_builtin = NULL;
+ for (BuiltinIdent *b = builtin_idents; b->name != NULL; b++) {
+ if (0 == strcmp(b->name, ident)) {
+ found_builtin = b;
+ break;
+ }
+ }
+ if (found_builtin != NULL) {
+ chunk_wbc(S, ch, found_builtin->op);
+ } else {
+ // read global variable
ObjString *o = objstring_copy_cstr(S, a.as.str);
chunk_wbc(S, ch, OP_GETGLOBAL);
chunk_wbc(S, ch, chunk_wconst(S, ch, VAL_OBJ(o)));
@@ -47,113 +175,43 @@ static void compile_node(State *S, Chunk *ch, AstNode a) {
}
case AST_LIST: {
AstVec l = a.as.list;
+
#define CK(cond, msg) if (!(cond)) { fputs(msg "\n", stderr); exit(1); }
CK(l.len > 0, "can't handle empty list");
CK(l.vals[0].ty == AST_IDENT, "can only call ops");
- char *name = l.vals[0].as.str;
-
- if (0 == strcmp(name, "puts")) {
- CK(l.len == 2, "puts requires exactly 1 argument");
- compile_node(S, ch, l.vals[1]);
- chunk_wbc(S, ch, OP_PUTS);
- } else if (0 == strcmp(name, "print")) {
- CK(l.len == 2, "print requires exactly 1 argument");
- compile_node(S, ch, l.vals[1]);
- chunk_wbc(S, ch, OP_PRINT);
- } else if (0 == strcmp(name, "set")) {
- CK(l.len == 3, "set requires exactly 2 arguments");
- AstNode ident = l.vals[1];
- CK(ident.ty == AST_IDENT, "set's first argument must be identifier");
-
- ObjString *o = objstring_copy_cstr(S, ident.as.str);
- compile_node(S, ch, l.vals[2]);
- chunk_wbc(S, ch, OP_SETGLOBAL);
- chunk_wbc(S, ch, chunk_wconst(S, ch, VAL_OBJ(o)));
- } else if (0 == strcmp(name, "do")) {
- for (int i = 1; i < l.len - 1; i++) {
- compile_node(S, ch, l.vals[i]);
- chunk_wbc(S, ch, OP_DROP);
- }
- compile_node(S, ch, l.vals[l.len - 1]);
- } else if (0 == strcmp(name, "if")) {
- CK(l.len == 4, "if requires exactly 3 arguments");
- // (if cond if-true if-false)
- // cond
- // 0branch ->A
- // if-true
- // skip ->B
- // A: if-false
- // B:
- compile_node(S, ch, l.vals[1]);
- chunk_wbc(S, ch, OP_0BRANCH);
- size_t ph_a = placeholder(S, ch);
- compile_node(S, ch, l.vals[2]);
- chunk_wbc(S, ch, OP_SKIP);
- size_t ph_b = placeholder(S, ch);
- size_t dest_a = ch->bc.len;
- compile_node(S, ch, l.vals[3]);
- size_t dest_b = ch->bc.len;
-
- patch(S, ch, ph_a, dest_a - ph_a - 2);
- patch(S, ch, ph_b, dest_b - ph_b - 2);
-
- } else if (0 == strcmp(name, "while")) {
- CK(l.len >= 3, "while requires at least 2 arguments");
- // (while cond body ...)
- // A:
- // cond
- // 0branch ->B
- // body ....
- // redo ->A
- // B:
- // nil (while loop always returns nil)
- size_t dest_a = ch->bc.len;
- compile_node(S, ch, l.vals[1]);
- chunk_wbc(S, ch, OP_0BRANCH);
- size_t ph_b = placeholder(S, ch);
- for (int i = 2; i < l.len; i++) {
- compile_node(S, ch, l.vals[i]);
- chunk_wbc(S, ch, OP_DROP);
+ char *head = l.vals[0].as.str;
+
+ BuiltinForm *form = NULL;
+ for (BuiltinForm *b = builtin_forms; b->name != NULL; b++) {
+ if (0 == strcmp(b->name, head)) {
+ form = b;
+ break;
}
- chunk_wbc(S, ch, OP_REDO);
- size_t ph_a = placeholder(S, ch);
- size_t dest_b = ch->bc.len;
- chunk_wbc(S, ch, OP_NIL);
+ }
- patch(S, ch, ph_a, ph_a - dest_a + 2);
- patch(S, ch, ph_b, dest_b - ph_b - 2);
+ if (form != NULL) {
+ if (form->ellipsis && l.len < form->min_params + 1) {
+ fprintf(stderr, "%s requires at least %d parameters\n",
+ form->name, form->min_params);
+ exit(1);
+ } else if (!form->ellipsis && l.len != form->min_params + 1) {
+ fprintf(stderr, "%s requires exactly %d parameters\n",
+ form->name, form->min_params);
+ exit(1);
+ }
+ form->action(S, ch, l, form->op);
} else {
-
- CK(l.len == 3, "can only compile binary ops");
- char opchar = l.vals[0].as.str[0];
- Op op;
- switch (opchar) {
- #define OP(char, code) case char: op = code; break;
- OP('+', OP_ADD)
- OP('-', OP_SUB)
- OP('*', OP_MUL)
- OP('/', OP_DIV)
- OP('=', OP_EQU)
- OP('<', OP_CMP)
- OP('%', OP_MOD)
- #undef OP
- default:
- printf("unkown op %s\n",l.vals[0].as.str);
- exit(1);
- break;
- }
- compile_node(S, ch, l.vals[1]);
- compile_node(S, ch, l.vals[2]);
- chunk_wbc(S, ch, op);
+ fprintf(stderr, "unknown form %s\n", head);
+ exit(1);
}
- #undef CK
+
+ break;
}
}
}
-
int main(int argc, char **argv) {
State st = state_new();
State *S = &st;
@@ -183,4 +241,3 @@ int main(int argc, char **argv) {
return runvm(S);
}
-
diff --git a/tests/compile_error.out b/tests/compile_error.out
index b7b8184..2bf3ee6 100644
--- a/tests/compile_error.out
+++ b/tests/compile_error.out
@@ -1 +1 @@
-while requires at least 2 arguments
+while requires at least 2 parameters