diff --git a/lisp.c b/lisp.c index 931b8df..aa5c11b 100644 --- a/lisp.c +++ b/lisp.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -14,6 +15,25 @@ #define unexpected(exp, act, ...) \ 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); + snprintf(errmsg, sizeof(errmsg), "error: "); + vsnprintf(errmsg, sizeof(errmsg), fmt, ap); + + longjmp(jmp_runtime_error, Qundef); +} + +const char *error_message(void) +{ + return errmsg; +} + typedef enum { // immediate TYPE_BOOL, @@ -338,7 +358,7 @@ static const char *unintern(Symbol sym) { const char *name = name_nth(symbol_names, (long) sym); if (name == NULL) - error("symbol %lu not found", sym); + runtime_error("symbol %lu not found", sym); return name; } @@ -428,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) @@ -556,8 +576,8 @@ 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); } Value apply(Value *env, Value func, Value vargs) @@ -565,7 +585,7 @@ Value apply(Value *env, Value func, Value vargs) static const long FUNCARG_MAX = 7; long n = FUNCTION(func)->arity; - if (n > FUNCARG_MAX) + if (n > FUNCARG_MAX) // fatal; wrong at define_function() error("arguments too long: max is %ld but got %ld", FUNCARG_MAX, n); expect_arity(n, length(vargs)); @@ -637,7 +657,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("variable %s not found", value_to_string(name)); + return cdr(found); } Value eval_string(const char *in) @@ -703,6 +725,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; @@ -821,8 +845,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) @@ -839,7 +863,7 @@ 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"); + runtime_error("wrong number of arguments: expected 1 or more but got 0"); Value rest = cdr(args); int64_t y = 0; if (rest == Qnil) @@ -871,7 +895,7 @@ 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"); + runtime_error("wrong number of arguments: expected 1 or more but got 0"); Value rest = cdr(args); int64_t y = 1; if (rest == 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..908a8b7 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, "x not found"); v = eval_string("(+ x 2)"); - cr_assert_eq(v, Qundef); + assert_runtime_error(v, "x not found"); } Test(lisp, true_false) {