From fbdfd9bf74b178a34543e0e347f441d689f73438 Mon Sep 17 00:00:00 2001 From: ubq323 Date: Fri, 21 Jun 2024 18:24:42 +0100 Subject: refactor compilation of builtin forms --- com.c | 259 +++++++++++++++++++++++++++++------------------- tests/compile_error.out | 2 +- 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 -- cgit v1.2.3