From 44b2634d0c7bf74043d50a75f361e4ed6d48e0f0 Mon Sep 17 00:00:00 2001 From: Tadashi Saito Date: Sun, 21 Jul 2024 00:16:31 +0900 Subject: [PATCH 1/9] Improve error handlings with setjmp/longjmp. --- lisp.c | 86 ++++++++++++++++++++++++++++++----------------------- lisp.h | 2 ++ test_lisp.c | 22 +++++++++----- 3 files changed, 66 insertions(+), 44 deletions(-) diff --git a/lisp.c b/lisp.c index 1fca8bb..5e7c59d 100644 --- a/lisp.c +++ b/lisp.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -12,7 +13,24 @@ #define error(fmt, ...) \ error("%s:%d of %s: " fmt, __FILE__, __LINE__, __func__ __VA_OPT__(,) __VA_ARGS__) #define unexpected(exp, act, ...) \ - error("expected %s but got " act, exp __VA_OPT__(,) __VA_ARGS__) + runtime_error("expected %s but got " act, exp __VA_OPT__(,) __VA_ARGS__) + +static jmp_buf jmp_runtime_error; +static char errmsg[BUFSIZ]; + +ATTR_NORETURN +static void runtime_error(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vsnprintf(errmsg, sizeof(errmsg), fmt, ap); + longjmp(jmp_runtime_error, Qundef); +} + +const char *error_message(void) +{ + return errmsg; +} typedef enum { // immediate @@ -339,7 +357,7 @@ static const char *name_nth(Value list, long n) static const char *unintern(Symbol sym) { const char *name = name_nth(symbol_names, (long) sym); - if (name == NULL) + if (name == NULL) // fatal; known symbol should have name error("symbol %lu not found", sym); return name; } @@ -430,7 +448,7 @@ static Token get_token(Parser *p) ungetc(c, p->in); return get_token_ident(p); } - error("got unexpected char '%c'", c); + runtime_error("got unexpected char: '%c'", c); } static void unget_token(Parser *p, Token t) @@ -558,8 +576,18 @@ static void expect_arity(long expected, long actual) { if (expected < 0 || expected == actual) return; - error("wrong number of arguments: expected %ld but got %ld", - expected, actual); + runtime_error("wrong number of arguments: expected %ld but got %ld", + expected, actual); +} + +static void expect_arity_range(long min, long max, long actual) +{ + if ((min == -1 && actual <= max) || + (max == -1 && min <= actual) || + (min <= actual && actual <= max)) + return; + runtime_error("wrong number of arguments: expected %ld..%ld but got %ld", + min, max, actual); } Value apply(Value *env, Value func, Value vargs) @@ -645,7 +673,9 @@ static Value define_function(Value *env, const char *name, CFunc cfunc, long ari static Value lookup(Value env, Value name) { Value found = alist_find(env, name); - return found == Qnil ? Qundef : cdr(found); + if (found == Qnil) + runtime_error("unbound variable: %s", value_to_string(name)); + return cdr(found); } Value eval_string(const char *in) @@ -656,15 +686,6 @@ Value eval_string(const char *in) return v; } -static Value memq(Value needle, Value list) -{ - for (Value p = list; p != Qnil; p = cdr(p)) { - if (car(p) == needle) - return p; - } - return Qnil; -} - static Value ieval(Value *env, Value v); // internal typedef Value (*MapFunc)(Value *common, Value v); @@ -682,14 +703,10 @@ static Value map2(MapFunc f, Value *common, Value l) static Value eval_funcy(Value *env, Value list) { Value f = ieval(env, car(list)); - if (f == Qundef) - return Qundef; Value args = cdr(list); if (tagged_value_is(f, TAG_SPECIAL)) return apply_special(env, f, args); Value l = map2(ieval, env, args); - if (memq(Qundef, l) != Qnil) - return Qundef; return apply(env, f, l); } @@ -711,6 +728,8 @@ Value load(FILE *in) { Value env = toplevel_environment; Value last = Qnil; + if (setjmp(jmp_runtime_error) != 0) + return Qundef; for (Value v = parse(in); v != Qnil; v = cdr(v)) last = ieval(&env, car(v)); return last; @@ -829,8 +848,8 @@ static void expect_type(Type expected, Value v, const char *header) Type t = value_typeof(v); if (t == expected) return; - error("%s: type error: expected %s but got %s", - header, TYPE_NAMES[expected], TYPE_NAMES[t]); + runtime_error("%s: type error: expected %s but got %s", + header, TYPE_NAMES[expected], TYPE_NAMES[t]); } static Value builtin_add(Value args) @@ -846,8 +865,8 @@ static Value builtin_add(Value args) static Value builtin_sub(Value args) { - if (args == Qnil) - error("wrong number of arguments: expected 1 or more but got 0"); + expect_arity_range(1, -1, length(args)); + Value rest = cdr(args); int64_t y = 0; if (rest == Qnil) @@ -878,8 +897,8 @@ static Value builtin_mul(Value args) static Value builtin_div(Value args) { - if (args == Qnil) - error("wrong number of arguments: expected 1 or more but got 0"); + expect_arity_range(1, -1, length(args)); + Value rest = cdr(args); int64_t y = 1; if (rest == Qnil) @@ -897,16 +916,9 @@ static Value builtin_div(Value args) return value_of_int(y); } -static bool validate_arity_range(Value args, long min, long max) -{ - long l = length(args); - return min <= l && l <= max; -} - static Value builtin_if(Value *env, Value args) { - if (!validate_arity_range(args, 2, 3)) - return Qundef; + expect_arity_range(2, 3, length(args)); Value cond = car(args), then = cadr(args); if (ieval(env, cond) != Qfalse) @@ -920,19 +932,19 @@ static Value builtin_if(Value *env, Value args) static Value builtin_define(Value *env, Value ident, Value expr) { expect_type(TYPE_SYMBOL, ident, "define"); + Value val = ieval(env, expr); - if (val == Qundef) - return Qundef; *env = alist_prepend(*env, ident, val); return Qnil; } static Value builtin_set(Value *env, Value ident, Value expr) { - expect_type(TYPE_SYMBOL, ident, "define"); + expect_type(TYPE_SYMBOL, ident, "set"); + Value found = alist_find(*env, ident); if (found == Qnil) - return Qundef; + runtime_error("set!: unbound variable: %s", value_to_string(ident)); PAIR(found)->cdr = ieval(env, expr); return Qnil; } diff --git a/lisp.h b/lisp.h index 55367b5..c2f26b3 100644 --- a/lisp.h +++ b/lisp.h @@ -76,4 +76,6 @@ ATTR_MALLOC char *stringify(Value v); Value parse_string(const char *in); Value parse_expr_string(const char *in); +const char *error_message(void); + #endif diff --git a/test_lisp.c b/test_lisp.c index 6ddef46..a60f702 100644 --- a/test_lisp.c +++ b/test_lisp.c @@ -5,11 +5,6 @@ #include "lisp.h" -Test(lisp, nil) { - Value a = Qnil; - cr_assert(value_is_nil(a)); -} - #define assert_stringify(expected, v) do { \ char *s = stringify(v); \ cr_assert_str_eq(expected, s); \ @@ -27,6 +22,16 @@ Test(lisp, nil) { #define V(x) \ _Generic(x, int: value_of_int(x), const char *: value_of_symbol, Value: x) +#define assert_runtime_error(v, pattern) do { \ + cr_assert_eq(Qundef, v); \ + cr_assert_not_null(strstr(error_message(), pattern)); \ + } while (0) + +Test(lisp, nil) { + Value a = Qnil; + cr_assert(value_is_nil(a)); +} + Test(lisp, printing) { assert_stringify("#t", Qtrue); assert_stringify("#f", Qfalse); @@ -125,10 +130,10 @@ Test(lisp, eval_arithmetic_expr) { Test(lisp, unbound_variable) { Value v = eval_string("x"); - cr_assert_eq(v, Qundef); + assert_runtime_error(v, "unbound variable: x"); v = eval_string("(+ x 2)"); - cr_assert_eq(v, Qundef); + assert_runtime_error(v, "unbound variable: x"); } Test(lisp, true_false) { @@ -214,4 +219,7 @@ Test(lisp, set) { v = eval_string("(define x 1) (set! x 42) x"); cr_assert(value_is_int(v)); cr_assert_eq(42, value_to_int(v)); + + v = eval_string("(set! x 42) x"); + assert_runtime_error(v, "unbound variable: x"); } From 1acaf95c8d421fe21b26cb9eae89a05ce3167495 Mon Sep 17 00:00:00 2001 From: Tadashi Saito Date: Sun, 21 Jul 2024 11:22:47 +0900 Subject: [PATCH 2/9] Handle error for ./lisp (main.c). --- main.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/main.c b/main.c index e54f8ce..a7049aa 100644 --- a/main.c +++ b/main.c @@ -33,8 +33,11 @@ static FILE *parse_opt(int argc, char *const *argv) int main(int argc, char **argv) { FILE *in = parse_opt(argc, argv); - print(load(in)); - printf("\n"); + Value v = load(in); fclose(in); + if (v == Qundef) + error("%s", error_message()); + print(v); + printf("\n"); return 0; } From 13530a1b87a9b27f60a02a839516339060a714ea Mon Sep 17 00:00:00 2001 From: Tadashi Saito Date: Sun, 21 Jul 2024 11:48:40 +0900 Subject: [PATCH 3/9] Better message. --- lisp.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lisp.c b/lisp.c index 5e7c59d..7832de0 100644 --- a/lisp.c +++ b/lisp.c @@ -580,14 +580,14 @@ static void expect_arity(long expected, long actual) expected, actual); } -static void expect_arity_range(long min, long max, long actual) +static void expect_arity_range(const char *func, long min, long max, long actual) { if ((min == -1 && actual <= max) || (max == -1 && min <= actual) || (min <= actual && actual <= max)) return; - runtime_error("wrong number of arguments: expected %ld..%ld but got %ld", - min, max, actual); + runtime_error("%s: wrong number of arguments: expected %ld..%ld but got %ld", + func, min, max, actual); } Value apply(Value *env, Value func, Value vargs) @@ -865,7 +865,7 @@ static Value builtin_add(Value args) static Value builtin_sub(Value args) { - expect_arity_range(1, -1, length(args)); + expect_arity_range("-", 1, -1, length(args)); Value rest = cdr(args); int64_t y = 0; @@ -897,7 +897,7 @@ static Value builtin_mul(Value args) static Value builtin_div(Value args) { - expect_arity_range(1, -1, length(args)); + expect_arity_range("/", 1, -1, length(args)); Value rest = cdr(args); int64_t y = 1; @@ -918,7 +918,7 @@ static Value builtin_div(Value args) static Value builtin_if(Value *env, Value args) { - expect_arity_range(2, 3, length(args)); + expect_arity_range("if", 2, 3, length(args)); Value cond = car(args), then = cadr(args); if (ieval(env, cond) != Qfalse) From a4b5bd03110c3d1fa53bafc1c0b99204ccd267b1 Mon Sep 17 00:00:00 2001 From: Tadashi Saito Date: Sun, 21 Jul 2024 12:18:37 +0900 Subject: [PATCH 4/9] Divided-by-zero... --- lisp.c | 9 ++++++--- test_lisp.c | 5 +++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lisp.c b/lisp.c index 7832de0..4083fbd 100644 --- a/lisp.c +++ b/lisp.c @@ -909,9 +909,12 @@ static Value builtin_div(Value args) y = value_to_int(vy); } for (Value l = rest; l != Qnil; l = cdr(l)) { - Value x = car(l); - expect_type(TYPE_INT, x, "/"); - y /= value_to_int(x); + Value vx = car(l); + expect_type(TYPE_INT, vx, "/"); + long x = value_to_int(vx); + if (x == 0) + runtime_error("/: divided by zero"); + y /= x; } return value_of_int(y); } diff --git a/test_lisp.c b/test_lisp.c index a60f702..ead7840 100644 --- a/test_lisp.c +++ b/test_lisp.c @@ -128,6 +128,11 @@ Test(lisp, eval_arithmetic_expr) { cr_assert_eq(42, value_to_int(v)); } +Test(lisp, div0) { + Value v = eval_string("(/ 42 0)"); + assert_runtime_error(v, "divided by zero"); +} + Test(lisp, unbound_variable) { Value v = eval_string("x"); assert_runtime_error(v, "unbound variable: x"); From e339830e78833cc37537d12085f3d057067a9485 Mon Sep 17 00:00:00 2001 From: Tadashi Saito Date: Sun, 21 Jul 2024 12:21:24 +0900 Subject: [PATCH 5/9] Fix typo. --- lisp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp.c b/lisp.c index 4083fbd..cfef7e7 100644 --- a/lisp.c +++ b/lisp.c @@ -943,7 +943,7 @@ static Value builtin_define(Value *env, Value ident, Value expr) static Value builtin_set(Value *env, Value ident, Value expr) { - expect_type(TYPE_SYMBOL, ident, "set"); + expect_type(TYPE_SYMBOL, ident, "set!"); Value found = alist_find(*env, ident); if (found == Qnil) From a57c59f9aa07129a51e45edc54533d2465afa7c2 Mon Sep 17 00:00:00 2001 From: Tadashi Saito Date: Sun, 21 Jul 2024 12:24:22 +0900 Subject: [PATCH 6/9] Rename unexpected to parse_error and make the message better. --- lisp.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lisp.c b/lisp.c index cfef7e7..cc90bb0 100644 --- a/lisp.c +++ b/lisp.c @@ -12,8 +12,8 @@ #define error(fmt, ...) \ error("%s:%d of %s: " fmt, __FILE__, __LINE__, __func__ __VA_OPT__(,) __VA_ARGS__) -#define unexpected(exp, act, ...) \ - runtime_error("expected %s but got " act, exp __VA_OPT__(,) __VA_ARGS__) +#define parse_error(exp, act, ...) \ + runtime_error("while parsing: expected %s but got " act, exp __VA_OPT__(,) __VA_ARGS__) static jmp_buf jmp_runtime_error; static char errmsg[BUFSIZ]; @@ -317,7 +317,7 @@ static Token get_token_int(Parser *p, int sign) int64_t i; int n = fscanf(p->in, "%ld", &i); if (n != 1) - unexpected("integer", "invalid string"); + parse_error("integer", "invalid string"); return TOK_INT(sign * i); } @@ -386,7 +386,7 @@ static Token get_token_ident(Parser *p) break; *s++ = c; if (s == end) - unexpected("identifier", "too long"); + parse_error("identifier", "too long"); } ungetc(c, p->in); *s = '\0'; @@ -432,7 +432,7 @@ static Token get_token(Parser *p) return TOK_CONST(Qtrue); if (c == 'f') return TOK_CONST(Qfalse); - unexpected("constants", "#%c", c); + parse_error("constants", "#%c", c); case EOF: return TOK_EOF; default: @@ -448,7 +448,7 @@ static Token get_token(Parser *p) ungetc(c, p->in); return get_token_ident(p); } - runtime_error("got unexpected char: '%c'", c); + parse_error("valid char", "'%c'", c); } static void unget_token(Parser *p, Token t) @@ -511,7 +511,7 @@ static Value parse_dotted_pair(Parser *p) Value e = parse_expr(p); Token t = get_token(p); if (t.type != TTYPE_RPAREN) - unexpected("')'", "'%s'", token_stringify(t)); + parse_error("')'", "'%s'", token_stringify(t)); return e; } @@ -524,7 +524,7 @@ static Value parse_list(Parser *p) Value car = parse_expr(p), cdr; t = get_token(p); if (t.type == TTYPE_EOF) - unexpected("')'", "'%s'", token_stringify(t)); + parse_error("')'", "'%s'", token_stringify(t)); if (t.type == TTYPE_DOT) { cdr = parse_dotted_pair(p); } else { @@ -541,9 +541,9 @@ static Value parse_expr(Parser *p) case TTYPE_LPAREN: return parse_list(p); // parse til ')' case TTYPE_RPAREN: - unexpected("expression", "')'"); + parse_error("expression", "')'"); case TTYPE_DOT: - unexpected("expression", "'.'"); + parse_error("expression", "'.'"); case TTYPE_INT: case TTYPE_CONST: case TTYPE_IDENT: From 62445c0528289730f7604ffc20af038bbece1681 Mon Sep 17 00:00:00 2001 From: Tadashi Saito Date: Sun, 21 Jul 2024 12:28:11 +0900 Subject: [PATCH 7/9] Refactoring: reorder arguments. --- lisp.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lisp.c b/lisp.c index cc90bb0..36dd5ec 100644 --- a/lisp.c +++ b/lisp.c @@ -843,7 +843,7 @@ Value parse_string(const char *in) return v; } -static void expect_type(Type expected, Value v, const char *header) +static void expect_type(const char *header, Type expected, Value v) { Type t = value_typeof(v); if (t == expected) @@ -857,7 +857,7 @@ static Value builtin_add(Value args) int64_t y = 0; for (Value l = args; l != Qnil; l = cdr(l)) { Value x = car(l); - expect_type(TYPE_INT, x, "+"); + expect_type("+", TYPE_INT, x); y += value_to_int(x); } return value_of_int(y); @@ -873,12 +873,12 @@ static Value builtin_sub(Value args) rest = args; else { Value vy = car(args); - expect_type(TYPE_INT, vy, "-"); + expect_type("-", TYPE_INT, vy); y = value_to_int(vy); } for (Value l = rest; l != Qnil; l = cdr(l)) { Value x = car(l); - expect_type(TYPE_INT, x, "-"); + expect_type("-", TYPE_INT, x); y -= value_to_int(x); } return value_of_int(y); @@ -889,7 +889,7 @@ static Value builtin_mul(Value args) int64_t y = 1; for (Value l = args; l != Qnil; l = cdr(l)) { Value x = car(l); - expect_type(TYPE_INT, x, "*"); + expect_type("*", TYPE_INT, x); y *= value_to_int(x); } return value_of_int(y); @@ -905,12 +905,12 @@ static Value builtin_div(Value args) rest = args; else { Value vy = car(args); - expect_type(TYPE_INT, vy, "/"); + expect_type("/", TYPE_INT, vy); y = value_to_int(vy); } for (Value l = rest; l != Qnil; l = cdr(l)) { Value vx = car(l); - expect_type(TYPE_INT, vx, "/"); + expect_type("/", TYPE_INT, vx); long x = value_to_int(vx); if (x == 0) runtime_error("/: divided by zero"); @@ -934,7 +934,7 @@ static Value builtin_if(Value *env, Value args) static Value builtin_define(Value *env, Value ident, Value expr) { - expect_type(TYPE_SYMBOL, ident, "define"); + expect_type("define", TYPE_SYMBOL, ident); Value val = ieval(env, expr); *env = alist_prepend(*env, ident, val); @@ -943,7 +943,7 @@ static Value builtin_define(Value *env, Value ident, Value expr) static Value builtin_set(Value *env, Value ident, Value expr) { - expect_type(TYPE_SYMBOL, ident, "set!"); + expect_type("set!", TYPE_SYMBOL, ident); Value found = alist_find(*env, ident); if (found == Qnil) From 1c89f73ea541739a2e7b612187de049aee2fe5e1 Mon Sep 17 00:00:00 2001 From: Tadashi Saito Date: Sun, 21 Jul 2024 12:33:12 +0900 Subject: [PATCH 8/9] Tweak type error message. --- lisp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp.c b/lisp.c index 36dd5ec..c57e395 100644 --- a/lisp.c +++ b/lisp.c @@ -848,7 +848,7 @@ static void expect_type(const char *header, Type expected, Value v) Type t = value_typeof(v); if (t == expected) return; - runtime_error("%s: type error: expected %s but got %s", + runtime_error("type error in %s: expected %s but got %s", header, TYPE_NAMES[expected], TYPE_NAMES[t]); } From a0b49f9918c0702c6f8cdbe25db65523b7048110 Mon Sep 17 00:00:00 2001 From: Tadashi Saito Date: Sun, 21 Jul 2024 13:12:18 +0900 Subject: [PATCH 9/9] Refactoring. --- lisp.c | 42 ++++++++++++++++-------------------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/lisp.c b/lisp.c index c57e395..50e166e 100644 --- a/lisp.c +++ b/lisp.c @@ -852,14 +852,17 @@ static void expect_type(const char *header, Type expected, Value v) header, TYPE_NAMES[expected], TYPE_NAMES[t]); } +static int64_t value_get_int(const char *header, Value v) +{ + expect_type(header, TYPE_INT, v); + return value_to_int(v); +} + static Value builtin_add(Value args) { int64_t y = 0; - for (Value l = args; l != Qnil; l = cdr(l)) { - Value x = car(l); - expect_type("+", TYPE_INT, x); - y += value_to_int(x); - } + for (Value l = args; l != Qnil; l = cdr(l)) + y += value_get_int("+", car(l)); return value_of_int(y); } @@ -872,26 +875,18 @@ static Value builtin_sub(Value args) if (rest == Qnil) rest = args; else { - Value vy = car(args); - expect_type("-", TYPE_INT, vy); - y = value_to_int(vy); - } - for (Value l = rest; l != Qnil; l = cdr(l)) { - Value x = car(l); - expect_type("-", TYPE_INT, x); - y -= value_to_int(x); + y = value_get_int("-", car(args)); } + for (Value l = rest; l != Qnil; l = cdr(l)) + y -= value_get_int("-", car(l)); return value_of_int(y); } static Value builtin_mul(Value args) { int64_t y = 1; - for (Value l = args; l != Qnil; l = cdr(l)) { - Value x = car(l); - expect_type("*", TYPE_INT, x); - y *= value_to_int(x); - } + for (Value l = args; l != Qnil; l = cdr(l)) + y *= value_get_int("*", car(l)); return value_of_int(y); } @@ -903,15 +898,10 @@ static Value builtin_div(Value args) int64_t y = 1; if (rest == Qnil) rest = args; - else { - Value vy = car(args); - expect_type("/", TYPE_INT, vy); - y = value_to_int(vy); - } + else + y = value_get_int("/", car(args)); for (Value l = rest; l != Qnil; l = cdr(l)) { - Value vx = car(l); - expect_type("/", TYPE_INT, vx); - long x = value_to_int(vx); + int64_t x = value_get_int("/", car(l)); if (x == 0) runtime_error("/: divided by zero"); y /= x;