summary refs log tree commit diff stats
path: root/lith.c
diff options
context:
space:
mode:
Diffstat (limited to 'lith.c')
-rw-r--r--lith.c999
1 files changed, 999 insertions, 0 deletions
diff --git a/lith.c b/lith.c
new file mode 100644
index 0000000..45a6318
--- /dev/null
+++ b/lith.c
@@ -0,0 +1,999 @@
+/* lith: a small interpreter written in C89: as a library */
+#include "lith.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void *emalloc(lith_st *L, size_t len)
+{
+    void *p;
+    p = malloc(len);
+    if (!p) {
+        L->error = LITH_ERR_NOMEM;
+    }
+    return p;
+}
+
+static char *lith__strndup(lith_st *L, char *str, size_t len)
+{
+    char *newstr, *p;
+    newstr = emalloc(L, len + 1);
+    if (!newstr) return NULL;
+    p = newstr;
+    while (len--) *p++ = *str++;
+    *p = '\0';
+    return newstr;
+}
+
+static void print_string(lith_string string)
+{
+    size_t i;
+    char *s;
+    s = string.buf;
+    putchar('"');
+    for (i = 0; i < string.len; s++, i++) {
+        if ((*s == '\\') || (*s == '"')) {
+            putchar('\\');
+            putchar(*s);
+        } else if (*s == '\n') {
+            printf("\\n");
+        } else if (*s == '\t') {
+            printf("\\t");
+        } else if (*s == '\0') {
+            printf("\\0");
+        } else if ((*s < 32) || (*s > 126)) {
+            printf("\\x%02X", (unsigned char)(*s));
+        } else {
+            putchar(*s);
+        }
+    }
+    putchar('"');
+}
+
+static char *skip(lith_st *L, char *input)
+{
+    size_t len;
+    while (*input) {
+        if ((len = strspn(input, " \t\n")) > 0) {
+            input += len;
+        } else if (*input == ';') {
+            if (!(input = strchr(input, '\n')))
+                break;
+        } else { break; }
+    }
+    if (!input || !*input) {
+        L->error = LITH_ERR_EOF;
+    }
+    return input;
+}
+
+static int ishexchar(int c)
+{
+    return (('0' <= c) && (c <= '9')) || (('a' <= c) && (c <= 'f')) || (('A' <= c) && (c <= 'F'));
+}
+
+static void eat_string(lith_st *L, char *start, char **end)
+{
+    for (*end = start; **end && (**end != '"'); ++*end) {
+        if (**end == '\\') {
+            ++*end;
+            if (**end == 'x') {
+                if (!((ishexchar(*++*end) && ishexchar(*++*end)))) { /* May God and You forgive me */
+                    L->error = LITH_ERR_SYNTAX;
+                    L->error_state.success = 0;
+                    L->error_state.msg = "Invalid character escape literal, expecting two hexadecimal characters";
+                    return;
+                }
+            }
+        }
+    }
+    if (!**end) {
+        L->error = LITH_ERR_EOF;
+        L->error_state.success = 0;
+        L->error_state.msg = "while reading a string literal";
+    } else {
+        /* skip the string ending " character */
+        ++*end;
+    }
+}
+
+static void lex(lith_st *L, char *input, char **start, char **end)
+{
+    if (!(input = skip(L, input))) { *start = *end = NULL; return; }
+    *start = input;
+    if (strchr("()", *input)) {
+        *end = input + 1;
+    } else if (*input == '"') {
+        /* skip the string starting " character */
+        eat_string(L, *start + 1, end);
+    } else {
+        *end = *start + strcspn(*start, " \t\n;()");
+    }
+}
+
+static char *read_string(lith_st *L, char *start, char *end, size_t *len)
+{
+    char *string, *p;
+    p = string = emalloc(L, end - start);
+    if (!p) return NULL;
+    for (start++; start < end; start++, p++) {
+        if (*start == '\\') {
+            switch (*++start) {
+            case 'n': *p = '\n'; break;
+            case 'r': *p = '\r'; break;
+            case 't': *p = '\t'; break;
+            case '0': *p = '\0'; break;
+            case 'x': *p = (char) strtol(++start, NULL, 16); ++start; break;
+            default: *p = *start; break;
+            }
+        } else {
+            *p = *start;
+        }
+    }
+    *len = p - string;
+    return string;
+}
+
+static lith_value *read_atom(lith_st *L, char *start, char *end)
+{
+    char *string, *next;
+    int sign;
+    size_t length;
+    long integer;
+    double number;
+    lith_value *val;
+    
+    if (*start == '"') {
+        string = read_string(L, start, end - 1, &length);
+        if (LITH_IS_ERR(L)) return NULL; 
+        val = lith_make_string(L, string, length);
+        free(string);
+        return val;
+    }
+    if ((*start == '+') || (*start == '-')) sign = (*start == '-') ? -1 : 1;
+    else sign = 1;
+    integer = strtol(start, &next, 10);
+    if (*next == '.') {
+        number = strtod(next, &next);
+        number *= sign;
+        number += integer;
+        return lith_make_number(L, number);
+    } else if (next == end) {
+        return lith_make_integer(L, integer);
+    } else {
+        string = lith__strndup(L, start, end - start);
+        if (!string) return NULL;
+        val = (!strcmp(string, "nil")) ? L->nil : lith_get_symbol(L, string);
+        free(string);
+        return val;
+    }
+}
+
+static lith_value *read_expr(lith_st *L, char *start, char **end);
+
+static lith_value *read_list_expr(lith_st *L, char *start, char **end)
+{
+    lith_value *p, *r, *v;
+    char *t;
+    *end = start;
+    v = p = L->nil;
+    for (;;) {
+        lex(L, *end, &t, end);
+        if (LITH_IS_ERR(L)) return NULL;
+        if (*t == ')') return v;
+        if (*t == '.' && (*end - t == 1)) {
+            if (LITH_IS_NIL(p)) {
+                L->error = LITH_ERR_SYNTAX;
+                L->error_state.msg = "invalid improper list starting with '.'";
+                lith_free_value(v);
+                return NULL;
+            }
+            r = read_expr(L, *end, end);
+            if (LITH_IS_ERR(L)) {
+                 lith_free_value(v);
+                 return NULL;
+            }
+            LITH_CDR(p) = r;
+            lex(L, *end, &t, end);
+            if (LITH_IS_ERR(L) || (*t != ')')) {
+                L->error = LITH_ERR_SYNTAX;
+                L->error_state.msg = "expecting ')' for the end of this improper list";
+                lith_free_value(v);
+                return NULL;
+            }
+            return v;
+        }
+        r = read_expr(L, t, end);
+        if (LITH_IS_ERR(L)) {
+            lith_free_value(v);
+            return NULL;
+        }
+        if (LITH_IS_NIL(p)) {
+            v = LITH_CONS(L, r, L->nil);
+            p = v;
+        } else {
+            LITH_CDR(p) = LITH_CONS(L, r, L->nil);
+            p = LITH_CDR(p);
+        }
+    }
+}
+
+static lith_value *read_expr(lith_st *L, char *start, char **end)
+{
+    char *t;
+    lex(L, start, &t, end);
+    if (LITH_IS_ERR(L)) return NULL;
+    if (*t == '(') {
+        return read_list_expr(L, *end, end);
+    } else if (*t == ')') {
+         L->error = LITH_ERR_SYNTAX;
+         L->error_state.msg = "unbalanced parenthesis, expected an expression";
+         return NULL;
+    } else {
+         return read_atom(L, t, *end);
+    }
+}
+
+static int is_proper_list(lith_value *list)
+{
+    while (!LITH_IS_NIL(list)) {
+        list = LITH_CDR(list);
+        if (!(LITH_IS_NIL(list) || LITH_IS(list, LITH_TYPE_PAIR))) {
+            return 0;
+        }
+    }
+    return 1;
+}
+
+static size_t list_length(lith_value *v)
+{
+    size_t len;
+    for (len = 0; !LITH_IS_NIL(v); len++) v = LITH_CDR(v);
+    return len;
+}
+
+static size_t lamargs_length(lith_value *args, int *im)
+{
+    size_t i;
+    for (i = 0; LITH_IS(args, LITH_TYPE_PAIR); args = LITH_CDR(args)) ++i;
+    *im = !LITH_IS_NIL(args);
+    return i;
+}
+
+static lith_value *builtin__car(lith_st *L, lith_value *args)
+{
+    lith_value *list;
+    if (!lith_expect_nargs(L, "car", 1, args, 1)) return NULL;
+    list = LITH_CAR(args);
+    if (!lith_expect_type(L, "car", 1, LITH_TYPE_PAIR, list)) return NULL;
+    return LITH_CAR(list);
+}
+
+static lith_value *builtin__cdr(lith_st *L, lith_value *args)
+{
+    lith_value *pair;
+    if (!lith_expect_nargs(L, "cdr", 1, args, 1)) return NULL;
+    pair = LITH_CAR(args);
+    if (!lith_expect_type(L, "cdr", 1, LITH_TYPE_PAIR, pair)) return NULL;
+    return LITH_CDR(pair);
+}
+
+static lith_value *builtin__cons(lith_st *L, lith_value *args)
+{
+    lith_value *head, *tail;
+    if (!lith_expect_nargs(L, "cons", 2, args, 1)) return NULL;
+    head = LITH_CAR(args);
+    tail = LITH_CAR(LITH_CDR(args));
+    return LITH_CONS(L, head, tail);
+}
+
+static void lith__print(lith_value *v)
+{
+    char *s;
+    size_t i, len;
+    if (LITH_IS(v, LITH_TYPE_STRING)) {
+        s = v->value.string.buf;
+        len = v->value.string.len;
+        for (i = 0; i < len; i++)
+            putchar(*s++);
+    } else {
+        lith_print_value(v);
+    }
+}
+
+static lith_value *builtin__print(lith_st *L, lith_value *args)
+{
+    lith_value *v;
+    if (!lith_expect_nargs(L, "print", 1, args, 0)) return NULL;
+    v = args;
+    lith__print(LITH_CAR(v));
+    v = LITH_CDR(v);
+    while (!LITH_IS_NIL(v)) {
+        putchar(' ');
+        lith__print(LITH_CAR(v));
+        v = LITH_CDR(v);
+    }
+    putchar('\n');
+    return L->nil;
+}
+
+#define COMMON1(fname) \
+    int n1i, n1n, n2i, n2n, n1m, n2m; \
+    lith_value *ret, *arg1, *arg2; \
+    if (!lith_expect_nargs(L, fname, 2, args, 1)) return NULL; \
+    arg1 = LITH_CAR(args); \
+    arg2 = LITH_CAR(LITH_CDR(args)); \
+    n1i = LITH_IS(arg1, LITH_TYPE_INTEGER), n1n = LITH_IS(arg1, LITH_TYPE_NUMBER), n1m = n1i || n1n; \
+    n2i = LITH_IS(arg2, LITH_TYPE_INTEGER), n2n = LITH_IS(arg2, LITH_TYPE_NUMBER), n2m = n2i || n2n; \
+    if (!n1m || !n2m) { \
+        L->error = LITH_ERR_TYPE; \
+        L->error_state.manual = 1; \
+        L->error_state.msg = "expected numeric types (integers or numbers) as argument"; \
+        return NULL; \
+    }
+
+#define COMMON2(op) \
+    if (n1i && n2i) { \
+        ret = lith_make_integer(L, arg1->value.integer op arg2->value.integer); \
+    } else { \
+        ret = lith_make_number(L, (n1i ? ((double) (arg1->value.integer)) : arg1->value.number) op (n2i ? ((double) (arg2->value.integer)) : arg2->value.number)); \
+    } \
+    return ret;
+
+static lith_value *builtin__add(lith_st *L, lith_value *args)
+{
+    COMMON1(":+")
+    COMMON2(+)
+}
+
+static lith_value *builtin__subtract(lith_st *L, lith_value *args)
+{
+    COMMON1(":-")
+    COMMON2(-)
+}
+
+static lith_value *builtin__multiply(lith_st *L, lith_value *args)
+{
+    COMMON1(":*")
+    COMMON2(*)
+}
+
+#define COMMON3(op, q) \
+    if (q && (arg2->value.integer == 0L)) { \
+        L->error = LITH_ERR_TYPE; \
+        L->error_state.manual = 1; \
+        L->error_state.msg = "cannot " op " by zero!!"; \
+        return NULL; \
+    }
+
+static lith_value *builtin__divide(lith_st *L, lith_value *args)
+{
+    COMMON1(":/")
+    COMMON3("divide", n2i)
+    COMMON2(/)
+}
+
+static lith_value *builtin__modulus(lith_st *L, lith_value *args)
+{
+    lith_value *arg1, *arg2;
+    if (!lith_expect_nargs(L, ":%", 2, args, 1)) return NULL;
+    arg1 = LITH_CAR(args);
+    arg2 = LITH_CAR(LITH_CDR(args));
+    if (!LITH_IS(arg1, LITH_TYPE_INTEGER) || !LITH_IS(arg2, LITH_TYPE_INTEGER)) {
+        L->error = LITH_ERR_TYPE;
+        L->error_state.manual = 1;
+        L->error_state.msg = "can calculate modulus with integral only arguments";
+        return NULL;
+    }
+    COMMON3("mod", 1)
+    return lith_make_integer(L, arg1->value.integer % arg2->value.integer);
+}
+
+#define COMMON4(op) \
+    if (n1i && n2i) { \
+        return LITH_IN_BOOL(arg1->value.integer op arg2->value.integer); \
+    } \
+    return LITH_IN_BOOL((n1i ? ((double) (arg1->value.integer)) : arg1->value.number) op (n2i ? ((double) (arg2->value.integer)) : arg2->value.number)) ;
+
+static lith_value *builtin__less_than(lith_st *L, lith_value *args)
+{
+    COMMON1(":<")
+    COMMON4(<)
+}
+
+static lith_value *builtin__equal(lith_st *L, lith_value *args)
+{
+    COMMON1(":==")
+    COMMON4(==)
+}
+
+static lith_value *builtin__greater_than(lith_st *L, lith_value *args)
+{
+    COMMON1(":>")
+    COMMON4(>)
+}
+
+#undef COMMON4
+#undef COMMON3
+#undef COMMON2
+#undef COMMON1
+
+static lith_value *builtin__eq(lith_st *L, lith_value *args)
+{
+    int eq;
+    lith_value *arg1, *arg2;
+    if (!lith_expect_nargs(L, "eq?", 2, args, 1)) return NULL;
+    arg1 = LITH_CAR(args);
+    arg2 = LITH_CAR(LITH_CDR(args));
+    if (arg1->type != arg2->type) return L->False;
+    switch (arg1->type) {
+    case LITH_TYPE_NIL:
+        return L->True;
+    case LITH_TYPE_INTEGER:
+        eq = arg1->value.integer == arg2->value.integer; break;
+    case LITH_TYPE_NUMBER:
+        eq = arg1->value.number == arg2->value.number; break;
+    case LITH_TYPE_STRING:
+        if (arg1->value.string.len != arg2->value.string.len) return L->False;
+        eq = !memcmp(arg1->value.string.buf, arg2->value.string.buf, arg2->value.string.len); break;
+    default: eq = arg1 == arg2; break;
+    }
+    return LITH_IN_BOOL(eq);
+}
+
+static void init_types(char **types)
+{
+    types[LITH_TYPE_NIL] = "nil";
+    types[LITH_TYPE_PAIR] = "pair";
+    types[LITH_TYPE_BOOLEAN] = "boolean";
+    types[LITH_TYPE_INTEGER] = "integer";
+    types[LITH_TYPE_NUMBER] = "number";
+    types[LITH_TYPE_SYMBOL] = "symbol";
+    types[LITH_TYPE_STRING] = "string";
+    types[LITH_TYPE_BUILTIN] = "builtin";
+    types[LITH_TYPE_CLOSURE] = "closure";
+}
+
+/* Public functions */
+
+void lith_init(lith_st *L)
+{
+    L->error = LITH_ERR_OK;
+    L->error_state.manual = 0;
+    L->error_state.success = 1;
+    L->error_state.sym = L->error_state.msg = L->error_state.name = NULL;
+    L->nil = lith_new_value(L);
+    L->nil->type = LITH_TYPE_NIL;
+    L->True = lith_new_value(L);
+    L->False = lith_new_value(L);
+    L->True->type = L->False->type = LITH_TYPE_BOOLEAN;
+    L->True->value.boolean = 1;
+    L->False->value.boolean = 0;
+    L->symbol_table = L->nil;
+    L->global = lith_new_env(L, L->nil);
+    L->global = lith_new_env(L, L->global);
+    L->filename = "<<unspecified>>";
+    init_types(L->types);
+    lith_fill_env(L);
+}
+
+void lith_free(lith_st *L)
+{
+    lith_value *p, *v;
+    lith_free_value(L->global);
+    p = L->symbol_table;
+    while (!LITH_IS_NIL(p)) {
+        v = LITH_CAR(p);
+        free(v->value.symbol);
+        free(v);
+        p = LITH_CDR(p);
+    }
+    free(L->False);
+    free(L->True);
+    free(L->nil);
+}
+
+lith_value *lith_new_value(lith_st *L)
+{
+    return emalloc(L, sizeof(lith_value));
+}
+
+lith_value *lith_make_integer(lith_st *L, long integer)
+{
+    lith_value *val;
+    val = lith_new_value(L);
+    if (!val) return NULL;
+    val->type = LITH_TYPE_INTEGER;
+    val->value.integer = integer;
+    return val;
+}
+
+lith_value *lith_make_number(lith_st *L, double number)
+{
+    lith_value *val;
+    val = lith_new_value(L);
+    if (!val) return NULL;
+    val->type = LITH_TYPE_NUMBER;
+    val->value.number = number;
+    return val;
+}
+
+lith_value *lith_make_symbol(lith_st *L, char *symbol)
+{
+    lith_value *val;
+    char *sym;
+    val = lith_new_value(L);
+    if (!val) return NULL;
+    val->type = LITH_TYPE_SYMBOL;
+    sym = lith__strndup(L, symbol, strlen(symbol));
+    if (!sym) { free(val); return NULL; }
+    val->value.symbol = sym;
+    return val;
+}
+
+lith_value *lith_make_builtin(lith_st *L, lith_builtin_function function)
+{
+    lith_value *val;
+    val = lith_new_value(L);
+    if (!val) return NULL;
+    val->type = LITH_TYPE_BUILTIN;
+    val->value.function = function;
+    return val;
+}
+
+lith_value *lith_make_closure(lith_st *L, lith_env *parent_env, lith_value *arg_names, lith_value *body)
+{
+    lith_value *val, *p;
+    arg_names = lith_copy_value(L, arg_names);
+    if (!arg_names) return NULL;
+    body = lith_copy_value(L, body);
+    if (!body) { lith_free_value(arg_names); return NULL; }
+    p = LITH_CONS(L, arg_names, body);
+    if (!p) { lith_free_value(arg_names); lith_free_value(body); return NULL; }
+    val = LITH_CONS(L, parent_env, p);
+    if (!val) { lith_free_value(p); return NULL; }
+    val->type = LITH_TYPE_CLOSURE;
+    return val;
+}
+
+lith_value *lith_make_string(lith_st *L, char *string, size_t len)
+{
+    lith_value *val;
+    char *str;
+    val = lith_new_value(L);
+    if (!val) return NULL;
+    val->type = LITH_TYPE_STRING;
+    str = lith__strndup(L, string, len);
+    if (!str) { free(val); return NULL; }
+    val->value.string.len = len;
+    val->value.string.buf = str;
+    return val; 
+}
+
+lith_value *lith_make_pair(lith_st *L, lith_value *car, lith_value *cdr)
+{
+    lith_value *val;
+    val = lith_new_value(L);
+    if (!val) return NULL;
+    val->type = LITH_TYPE_PAIR;
+    LITH_CAR(val) = car;
+    LITH_CDR(val) = cdr;
+    return val;
+}
+
+void lith_free_value(lith_value *val)
+{
+    if (LITH_IS(val, LITH_TYPE_PAIR)) {
+        lith_free_value(LITH_CAR(val));
+        lith_free_value(LITH_CDR(val));
+    } else if (LITH_IS(val, LITH_TYPE_CLOSURE)) {
+        lith_free_value(LITH_CDR(val));
+    } else if (LITH_IS(val, LITH_TYPE_STRING)) {
+        free(val->value.string.buf);
+    } else if (LITH_IS_NIL(val) || LITH_IS(val, LITH_TYPE_BOOLEAN) || LITH_IS(val, LITH_TYPE_SYMBOL)) {
+        return;
+    }
+    free(val);
+}
+
+lith_value *lith_get_symbol(lith_st *L, char *name)
+{
+    lith_value *sym, *p;
+    p = L->symbol_table;
+    while (!LITH_IS_NIL(p)) {
+        sym = LITH_CAR(p);
+        if (LITH_SYM_EQ(sym, name)) return sym;
+        p = LITH_CDR(p);
+    }
+    sym = lith_make_symbol(L, name);
+    if (!sym) return NULL;
+    p = LITH_CONS(L, sym, L->symbol_table);
+    if (!p) { lith_free_value(sym); return NULL; }
+    L->symbol_table = p;
+    return sym;
+}
+
+void lith_print_value(lith_value *val)
+{
+    if (LITH_IS_NIL(val)) {
+        printf("()");
+    } else if (LITH_IS(val, LITH_TYPE_SYMBOL)) {
+        printf("%s", val->value.symbol);
+    } else if (LITH_IS(val, LITH_TYPE_STRING)) {
+        print_string(val->value.string);
+    } else if (LITH_IS(val, LITH_TYPE_BOOLEAN)) {
+        printf("#%c", val->value.boolean ? 't' : 'f');
+    } else if (LITH_IS(val, LITH_TYPE_INTEGER)) {
+        printf("%ld", val->value.integer);
+    } else if (LITH_IS(val, LITH_TYPE_NUMBER)) {
+        printf("%.*f", LITH_NFP, val->value.number);
+    } else if (LITH_IS(val, LITH_TYPE_BUILTIN)) {
+        printf("#builtin:<%p>", val->value.function);
+    } else if (LITH_IS(val, LITH_TYPE_CLOSURE)) {
+        printf("#lambda:<%p>", val);
+    } else if (!LITH_IS(val, LITH_TYPE_PAIR)) {
+        printf("#<%p>", val);
+    } else {
+        putchar('(');
+        lith_print_value(LITH_CAR(val));
+        val = LITH_CDR(val);
+        while (!LITH_IS_NIL(val)) {
+            if (LITH_IS(val, LITH_TYPE_PAIR)) {
+                putchar(' ');
+                lith_print_value(LITH_CAR(val));
+                val = LITH_CDR(val);
+            } else {
+                printf(" . ");
+                lith_print_value(val);
+                break;
+            }
+        }
+        putchar(')');
+    }
+}
+
+lith_value *lith_copy_value(lith_st *L, lith_value *val)
+{
+    lith_value *head, *pair, *p, *v, *w;
+    if (!val) return NULL;
+    switch (val->type) {
+    case LITH_TYPE_INTEGER:
+        return lith_make_integer(L, val->value.integer);
+    case LITH_TYPE_NUMBER:
+        return lith_make_number(L, val->value.number);
+    case LITH_TYPE_STRING:
+        return lith_make_string(L, val->value.string.buf, val->value.string.len);
+    case LITH_TYPE_BUILTIN:
+        return lith_make_builtin(L, val->value.function);
+    case LITH_TYPE_CLOSURE:
+        return lith_make_closure(L, LITH_CAR(val), LITH_CAR(LITH_CDR(val)), LITH_CDR(LITH_CDR(val)));
+    case LITH_TYPE_PAIR:
+        head = lith_copy_value(L, LITH_CAR(val));
+        if (!head) return NULL;
+        pair = LITH_CONS(L, head, L->nil);
+        if (!pair) { lith_free_value(head); return NULL; }
+        val = LITH_CDR(val);
+        for (p = pair; LITH_IS(val, LITH_TYPE_PAIR); val = LITH_CDR(val), p = LITH_CDR(p)) {
+            v = lith_copy_value(L, LITH_CAR(val));
+            if (!v) { lith_free_value(pair); return NULL; }
+            w = LITH_CONS(L, v, L->nil);
+            if (!w) { lith_free_value(pair); lith_free_value(v); }
+            LITH_CDR(p) = w;
+        }
+        if (!LITH_IS_NIL(val)) {
+            v = lith_copy_value(L, val);
+            if (!v) { lith_free_value(v); return NULL; }
+            LITH_CDR(p) = v;
+        }
+        return pair;
+    default: return val;
+    }
+}
+
+void lith_print_error(lith_st *L, int full)
+{
+    struct lith_error_state E = L->error_state;
+    if (full) fprintf(stderr, "lith: %s: ", L->filename);
+    switch (L->error) {
+    case LITH_ERR_OK:
+        fprintf(stderr, "none");
+        break;
+    case LITH_ERR_EOF:
+        if (!E.success) fprintf(stderr, "Unexpected ");
+        fprintf(stderr, "End of File");
+        if (!E.success) fprintf(stderr, ": %s", E.msg);
+        break;
+    case LITH_ERR_SYNTAX:
+        fprintf(stderr, "syntax error: %s", E.msg);
+        break;
+    case LITH_ERR_NOMEM:
+        fprintf(stderr, "no memory");
+        break;
+    case LITH_ERR_UNBOUND:
+        fprintf(stderr, "unbound symbol: '%s'", E.sym);
+        break;
+    case LITH_ERR_NARGS:
+        fprintf(stderr, "wrong number of arguments: expected %s%zu argument(s) but given %zu argument(s)", (E.nargs.exact ? "" : "at least "), E.nargs.expected, E.nargs.got);
+        break;
+    case LITH_ERR_TYPE:
+        fprintf(stderr, "type error: ");
+        if (E.manual) fprintf(stderr, E.msg);
+        else fprintf(stderr, "expecting %s instead of %s as the argument number %zu", L->types[E.type.expected], L->types[E.type.got], E.type.narg);
+        break;
+    }
+    if (E.name) fprintf(stderr, " [in '%s']", E.name);
+    fputc('\n', stderr);
+}
+
+lith_value *lith_read_expr(lith_st *L, char *start, char **end)
+{
+    return read_expr(L, start, end);
+}
+
+lith_env *lith_new_env(lith_st *L, lith_env *parent)
+{
+    return LITH_CONS(L, parent, L->nil);
+}
+
+void lith_free_env(lith_env *V)
+{
+    lith_free_value(LITH_CDR(V));
+}
+
+lith_value *lith_env_get(lith_st *L, lith_env *V, lith_value *name)
+{
+    lith_env *parent;
+    lith_value *kvs, *kv;
+    parent = V;
+    do {
+        kvs = LITH_CDR(parent);
+        parent = LITH_CAR(parent);
+        while (!LITH_IS_NIL(kvs)) {
+            kv = LITH_CAR(kvs);
+            if (LITH_CAR(kv) == name) return LITH_CDR(kv);
+            kvs = LITH_CDR(kvs);
+        }
+    } while (!LITH_IS_NIL(parent));
+    L->error = LITH_ERR_UNBOUND;
+    L->error_state.sym = name->value.symbol;
+    return NULL;
+}
+
+void lith_env_set(lith_st *L, lith_env *V, lith_value *name, lith_value *value)
+{
+    lith_value *kvs, *kv;
+    kvs = LITH_CDR(V);
+    while (!LITH_IS_NIL(kvs)) {
+        kv = LITH_CAR(kvs);
+        if (name == LITH_CAR(kv)) {
+            LITH_CDR(kv) = value;
+            return;
+        }
+        kvs = LITH_CDR(kvs);
+    }
+    kv = LITH_CONS(L, name, value);
+    if (!kv) return;
+    LITH_CDR(V) = LITH_CONS(L, kv, LITH_CDR(V));
+}
+
+void lith_fill_env(lith_st *L)
+{
+    lith_env *V;
+    V = L->global;
+    lith_env_set(L, V, lith_get_symbol(L, "#t"), L->True);
+    lith_env_set(L, V, lith_get_symbol(L, "#f"), L->False);
+    #define LITH_FN_REGISTER(L, V, s, fn) lith_env_set(L, V, lith_get_symbol(L, s), lith_make_builtin(L, fn))
+    LITH_FN_REGISTER(L, V, "print", builtin__print);
+    LITH_FN_REGISTER(L, V, "car", builtin__car);
+    LITH_FN_REGISTER(L, V, "cdr", builtin__cdr);
+    LITH_FN_REGISTER(L, V, "cons", builtin__cons);
+    LITH_FN_REGISTER(L, V, ":+", builtin__add);
+    LITH_FN_REGISTER(L, V, ":-", builtin__subtract);
+    LITH_FN_REGISTER(L, V, ":*", builtin__multiply);
+    LITH_FN_REGISTER(L, V, ":/", builtin__divide);
+    LITH_FN_REGISTER(L, V, ":%", builtin__modulus);
+    LITH_FN_REGISTER(L, V, ":<", builtin__less_than);
+    LITH_FN_REGISTER(L, V, ":==", builtin__equal);
+    LITH_FN_REGISTER(L, V, ":>", builtin__greater_than);
+    LITH_FN_REGISTER(L, V, "eq?", builtin__eq);
+    #undef LITH_FN_REGISTER
+}
+
+int lith_expect_nargs(lith_st *L, char *name, size_t expect, lith_value *args, int exact)
+{
+    size_t len;
+    struct lith_error_state *E;
+    E = &L->error_state;
+    len = list_length(args);
+    if (exact ? (len != expect) : (len < expect)) {
+        L->error = LITH_ERR_NARGS;
+        E->name = name;
+        E->nargs.expected = expect;
+        E->nargs.exact = exact;
+        E->nargs.got = len;
+        return 0;
+    } else {
+        return 1;
+    }
+}
+
+int lith_expect_type(lith_st *L, char *name, size_t narg, lith_valtype type, lith_value *val)
+{
+    struct lith_error_state *E;
+    E = &L->error_state;
+    if (LITH_IS(val, type))
+        return 1;
+    L->error = LITH_ERR_TYPE;
+    E->name = name;
+    E->type.expected = type;
+    E->type.got = val->type;
+    E->type.narg = narg;
+    return 0;
+}
+
+lith_value *lith_eval_expr(lith_st *L, lith_env *V, lith_value *expr)
+{
+    lith_value *f, *rest, *sym, *val, *args, *p, *q;
+    if (LITH_IS(expr, LITH_TYPE_SYMBOL)) {
+        return lith_copy_value(L, lith_env_get(L, V, expr));
+    } else if (!LITH_IS(expr, LITH_TYPE_PAIR)) {
+        return lith_copy_value(L, expr);
+    } else if (!is_proper_list(expr)) {
+        L->error = LITH_ERR_SYNTAX;
+        L->error_state.msg = "atom or proper list expected as expression";
+        return NULL;
+    }
+    f = LITH_CAR(expr);
+    rest = LITH_CDR(expr);
+    if (LITH_IS(f, LITH_TYPE_SYMBOL)) {
+       if (LITH_SYM_EQ(f, "quote")) {
+            if (!lith_expect_nargs(L, "quote", 1, rest, 1))
+                return NULL;
+            return lith_copy_value(L, LITH_CAR(rest));
+        } else if (LITH_SYM_EQ(f, "if")) {
+            if (!lith_expect_nargs(L, "if", 3, rest, 1)) return NULL;
+            val = lith_eval_expr(L, V, LITH_CAR(rest));
+            if (LITH_IS_ERR(L)) return NULL;
+            p = LITH_CDR(rest);
+            return lith_eval_expr(L, V, LITH_CAR(LITH_TO_BOOL(val) ? p : LITH_CDR(p)));
+        } else if (LITH_SYM_EQ(f, "define")) {
+            if (!lith_expect_nargs(L, "define", 2, rest, 0))
+                return NULL;
+            sym = LITH_CAR(rest);
+            p = LITH_CDR(rest);
+            if (!LITH_IS(sym, LITH_TYPE_SYMBOL)) {
+                if (!LITH_IS(sym, LITH_TYPE_PAIR)) {
+                    L->error = LITH_ERR_TYPE;
+                    L->error_state.name = "define";
+                    L->error_state.manual = 1;
+                    L->error_state.msg = "first argument must be a symbol or pair";
+                    return NULL;
+                }
+                args = LITH_CDR(sym);
+                sym = LITH_CAR(sym);
+                if (!lith_expect_type(L, "define", 1, LITH_TYPE_SYMBOL, sym)) return NULL;
+                val = lith_make_closure(L, V, args, p);
+            } else {
+                if (!lith_expect_nargs(L, "define", 2, rest, 1)) return NULL;
+                val = lith_eval_expr(L, V, LITH_CAR(p));
+            }
+            if (!val) return NULL;
+            lith_env_set(L, V, sym, val);
+            return L->nil;
+        } else if (LITH_SYM_EQ(f, "lambda")) {
+            if (!lith_expect_nargs(L, "{lambda}", 2, rest, 0))
+                return NULL;
+            args = LITH_CAR(rest);
+            p = LITH_CDR(rest);
+            if (!is_proper_list(p)) {
+                L->error = LITH_ERR_SYNTAX;
+                L->error_state.msg = "body of lambda expression must be proper list";
+                return NULL;
+            }
+            for (q = args; LITH_IS(q, LITH_TYPE_PAIR); q = LITH_CDR(q)) {
+                if (!LITH_IS(LITH_CAR(q), LITH_TYPE_SYMBOL)) {
+                    L->error = LITH_ERR_SYNTAX;
+                    L->error_state.msg = "arguments in lambda expression must be symbols";
+                    return NULL;
+                }
+            }
+            if (!LITH_IS_NIL(q) && !LITH_IS(q, LITH_TYPE_SYMBOL)) {
+                L->error = LITH_ERR_SYNTAX;
+                L->error_state.msg = "arguments in lambda expression must be symbols";
+                return NULL;
+            }
+            return lith_make_closure(L, V, args, p);
+        }
+    }
+    f = lith_eval_expr(L, V, f);
+    if (LITH_IS_NIL(rest)) args = L->nil;
+    else {
+        args = lith_copy_value(L, rest);
+        if (!args) return NULL;
+        rest = args;
+        val = lith_eval_expr(L, V, LITH_CAR(rest));
+        if (!val) return NULL;
+        args = LITH_CONS(L, val, L->nil);
+        if (!args) { lith_free_value(val); return NULL; }
+        rest = LITH_CDR(rest);
+        for (p = args; !LITH_IS_NIL(rest); p = LITH_CDR(p), rest = LITH_CDR(rest)) {
+            val = lith_eval_expr(L, V, LITH_CAR(rest));
+            if (!val) { lith_free_value(args); return NULL; }
+            q = LITH_CONS(L, val, L->nil);
+            if (!q) { lith_free_value(args); lith_free_value(val); return NULL; }
+            LITH_CDR(p) = q;
+        }
+    }
+    if (!f) return NULL;
+    return lith_apply(L, f, args);
+}
+
+lith_value *lith_apply(lith_st *L, lith_value *f, lith_value *args)
+{
+    int imgr, imer;
+    size_t gnargs, enargs;
+    lith_env *env;
+    lith_value *argn, *body, *r;
+    if (LITH_IS(f, LITH_TYPE_BUILTIN)) {
+        return (*f->value.function)(L, args);
+    } else if (!LITH_IS(f, LITH_TYPE_CLOSURE)) {
+        L->error = LITH_ERR_TYPE;
+        L->error_state.manual = 1;
+        L->error_state.msg = "can not call  non-callable";
+        L->error_state.name = "{apply}";
+        return NULL;
+    }
+    env = lith_new_env(L, LITH_CAR(f));
+    argn = LITH_CDR(f);
+    body = LITH_CDR(argn);
+    argn = LITH_CAR(argn);
+    gnargs = lamargs_length(args, &imgr);
+    enargs = lamargs_length(argn, &imer);
+    if (imer ? (gnargs < enargs) : (gnargs != enargs)) {
+        L->error = LITH_ERR_NARGS;
+        L->error_state.name = "{lambda}";
+        L->error_state.nargs.expected = enargs;
+        L->error_state.nargs.got = gnargs;
+        L->error_state.nargs.exact = !imer;
+        return NULL;
+    }
+    while (LITH_IS(argn, LITH_TYPE_PAIR)) {
+        lith_env_set(L, env, LITH_CAR(argn), LITH_CAR(args));
+        argn = LITH_CDR(argn);
+        args = LITH_CDR(args);
+    }
+    if (!LITH_IS_NIL(argn))
+        lith_env_set(L, env, argn, args);
+    r = NULL;
+    while (!LITH_IS_NIL(body)) {
+        if (r) lith_free_value(r);
+        r = lith_eval_expr(L, env, LITH_CAR(body));
+        body = LITH_CDR(body);
+    }
+    return r;
+}
+
+void lith_run_string(lith_st *L, lith_env *V, char *input)
+{
+    char *end;
+    lith_value *expr, *res;
+    end = input;
+    L->filename = "<<string>>";
+    
+    while (!LITH_IS_ERR(L)) {
+        if ((expr = lith_read_expr(L, end, &end))) {
+            printf(">> ");
+            lith_print_value(expr);
+            putchar('\n');
+            if ((res = lith_eval_expr(L, V, expr))) {
+                printf("-> ");
+                lith_print_value(res);
+                lith_free_value(res);
+                putchar('\n');
+            }
+            lith_free_value(expr);
+        }
+    }
+    lith_print_error(L, 1);
+}