mirror of https://github.com/smlckz/lith
1337 lines
38 KiB
C
1337 lines
38 KiB
C
/* 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, FILE *file)
|
|
{
|
|
size_t i;
|
|
char *s;
|
|
s = string.buf;
|
|
fputc('"', file);
|
|
for (i = 0; i < string.len; s++, i++) {
|
|
if ((*s == '\\') || (*s == '"')) {
|
|
fputc('\\', file);
|
|
fputc(*s, file);
|
|
} else if (*s == '\n') {
|
|
fprintf(file, "\\n");
|
|
} else if (*s == '\t') {
|
|
fprintf(file, "\\t");
|
|
} else if (*s == '\0') {
|
|
fprintf(file, "\\0");
|
|
} else if ((*s < 32) || (*s > 126)) {
|
|
fprintf(file, "\\x%02X", (unsigned char)(*s));
|
|
} else {
|
|
fputc(*s, file);
|
|
}
|
|
}
|
|
fputc('"', file);
|
|
}
|
|
|
|
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)))) {
|
|
lith_simple_error(L, LITH_ERR_SYNTAX,
|
|
"Invalid character escape literal, "
|
|
"expecting two hexadecimal characters");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!**end) {
|
|
lith_simple_error(L, LITH_ERR_EOF, "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 ((*input == '(') || (*input == ')') || (*input == '\'')
|
|
|| (*input == '@') || (*input == '`')) {
|
|
*end = input + 1;
|
|
} else if (*input == ',') {
|
|
*end = input + ((input[1] == '@') ? 2 : 1);
|
|
} else if (*input == '"') {
|
|
/* +1 to 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 == '"') {
|
|
/* -1 to skip the string ending " character */
|
|
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 == '#') && ((end - start) == 2)
|
|
&& ((start[1] == 't') || (start[1] == 'f'))) {
|
|
return (start[1] == 'f') ? L->False : L->True;
|
|
}
|
|
sign = (*start == '-') ? -1 : 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 = 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, *expr, *list;
|
|
char *t;
|
|
*end = start;
|
|
list = p = L->nil;
|
|
for (;;) {
|
|
lex(L, *end, &t, end);
|
|
if (LITH_IS_ERR(L)) return NULL;
|
|
if (*t == ')') return list;
|
|
if (*t == '.' && (*end - t == 1)) {
|
|
if (LITH_IS_NIL(p)) {
|
|
lith_simple_error(L, LITH_ERR_SYNTAX,
|
|
"improper lists do not start with '.'");
|
|
lith_free_value(list);
|
|
return NULL;
|
|
}
|
|
expr = read_expr(L, *end, end);
|
|
if (LITH_IS_ERR(L)) {
|
|
lith_free_value(list);
|
|
return NULL;
|
|
}
|
|
LITH_CDR(p) = expr;
|
|
lex(L, *end, &t, end);
|
|
if (LITH_IS_ERR(L) || (*t != ')')) {
|
|
lith_simple_error(L, LITH_ERR_SYNTAX,
|
|
"expecting ')' at the end of this improper list");
|
|
lith_free_value(list);
|
|
return NULL;
|
|
}
|
|
return list;
|
|
}
|
|
expr = read_expr(L, t, end);
|
|
if (LITH_IS_ERR(L)) {
|
|
lith_free_value(list);
|
|
return NULL;
|
|
}
|
|
if (LITH_IS_NIL(p)) {
|
|
list = LITH_CONS(L, expr, L->nil);
|
|
p = list;
|
|
} else {
|
|
LITH_CDR(p) = LITH_CONS(L, expr, L->nil);
|
|
p = LITH_CDR(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
static lith_value *read_expr(lith_st *L, char *start, char **end)
|
|
{
|
|
lith_value *p, *q, *v;
|
|
char *t, *s;
|
|
lex(L, start, &t, end);
|
|
if (LITH_IS_ERR(L)) return NULL;
|
|
if (*t == '(') {
|
|
return read_list_expr(L, *end, end);
|
|
} else if (*t == ')') {
|
|
lith_simple_error(L, LITH_ERR_SYNTAX,
|
|
"unbalanced parenthesis, expected an expression");
|
|
return NULL;
|
|
} else if ((*t == '\'') || (*t == '@') || (*t == ',') || (*t == '`')) {
|
|
if (*t == '\'')
|
|
s = "quote";
|
|
else if ((*t == '@') || (*t == '`'))
|
|
s = "quasiquote";
|
|
else if (*t == ',')
|
|
s = (t[1] == '@')
|
|
? "unquote-splicing"
|
|
: "unquote";
|
|
p = LITH_CONS(L, lith_get_symbol(L, s), L->nil);
|
|
v = read_expr(L, *end, end);
|
|
if (!v) { lith_free_value(p); return NULL; }
|
|
q = LITH_CONS(L, v, L->nil);
|
|
if (!q) { lith_free_value(v); lith_free_value(p); return NULL; }
|
|
LITH_CDR(p) = q;
|
|
return p;
|
|
} 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;
|
|
}
|
|
|
|
/* builtin functions of lith */
|
|
|
|
/* car[1] :: (car '(a . b)) -> a */
|
|
static lith_value *builtin__car(lith_st *L, lith_value *args)
|
|
{
|
|
lith_value *list;
|
|
list = LITH_CAR(args);
|
|
if (!lith_expect_type(L, "car", 1, LITH_TYPE_PAIR, list)) return NULL;
|
|
return LITH_CAR(list);
|
|
}
|
|
|
|
/* cdr[1] :: (cdr '(a . b)) -> b */
|
|
static lith_value *builtin__cdr(lith_st *L, lith_value *args)
|
|
{
|
|
lith_value *pair;
|
|
pair = LITH_CAR(args);
|
|
if (!lith_expect_type(L, "cdr", 1, LITH_TYPE_PAIR, pair)) return NULL;
|
|
return LITH_CDR(pair);
|
|
}
|
|
|
|
/* cons[2] :: (cons a b) -> (a . b) */
|
|
static lith_value *builtin__cons(lith_st *L, lith_value *args)
|
|
{
|
|
lith_value *head, *tail;
|
|
head = LITH_CAR(args);
|
|
tail = LITH_CAR(LITH_CDR(args));
|
|
return LITH_CONS(L, head, tail);
|
|
}
|
|
|
|
static void lith__print(lith_st *L, lith_value *v)
|
|
{
|
|
if (LITH_IS(v, LITH_TYPE_STRING)) {
|
|
fwrite(v->value.string.buf, 1, v->value.string.len, stdout);
|
|
} else {
|
|
lith_print_value(L, v, stdout);
|
|
}
|
|
}
|
|
|
|
/* print[1+] ::
|
|
* (print ...) -> ()
|
|
* and prints the values
|
|
* separated by ' '
|
|
* and a newline ('\n')
|
|
*/
|
|
static lith_value *builtin__print(lith_st *L, lith_value *args)
|
|
{
|
|
lith_value *v;
|
|
v = args;
|
|
lith__print(L, LITH_CAR(v));
|
|
v = LITH_CDR(v);
|
|
while (!LITH_IS_NIL(v)) {
|
|
putchar(' ');
|
|
lith__print(L, LITH_CAR(v));
|
|
v = LITH_CDR(v);
|
|
}
|
|
putchar('\n');
|
|
return L->nil;
|
|
}
|
|
|
|
#define COMMON1(fname) \
|
|
int n1_is_integer, n1_is_number, \
|
|
n2_is_integer, n2_is_number, \
|
|
n1_is_numeric, n2_is_numeric; \
|
|
lith_value *arg1, *arg2; \
|
|
arg1 = LITH_CAR(args); \
|
|
arg2 = LITH_CAR(LITH_CDR(args)); \
|
|
n1_is_integer = LITH_IS(arg1, LITH_TYPE_INTEGER); \
|
|
n1_is_number = LITH_IS(arg1, LITH_TYPE_NUMBER); \
|
|
n1_is_numeric = n1_is_integer || n1_is_number; \
|
|
n2_is_integer = LITH_IS(arg2, LITH_TYPE_INTEGER); \
|
|
n2_is_number = LITH_IS(arg2, LITH_TYPE_NUMBER); \
|
|
n2_is_numeric = n2_is_integer || n2_is_number; \
|
|
if (!n1_is_numeric || !n2_is_numeric) { \
|
|
lith_simple_error(L, LITH_ERR_TYPE, \
|
|
"expected numeric types (integers or numbers) as argument"); \
|
|
return NULL; \
|
|
}
|
|
|
|
#define COMMON2(op) \
|
|
if (n1_is_integer && n2_is_integer) { \
|
|
return lith_make_integer(L, arg1->value.integer op arg2->value.integer); \
|
|
} else { \
|
|
return lith_make_number(L, \
|
|
(n1_is_integer \
|
|
? ((double) (arg1->value.integer)) \
|
|
: arg1->value.number) \
|
|
op \
|
|
(n2_is_integer \
|
|
? ((double) (arg2->value.integer)) \
|
|
: arg2->value.number)); \
|
|
}
|
|
|
|
/* op1[2] ::: op1 <- (:+), (:-), (:*)
|
|
* (op1 int int) -> int
|
|
* (op1 int num) -> num
|
|
* (op1 num int) -> num
|
|
* (op1 num num) -> num
|
|
*/
|
|
|
|
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)) { \
|
|
lith_simple_error(L, LITH_ERR_TYPE, "cannot " op " by zero!!"); \
|
|
return NULL; \
|
|
}
|
|
|
|
/* type int_n0 = int \ {0} ; hah!
|
|
* :/[2] ::
|
|
* (:/ int int_n0) -> int
|
|
* (:/ int num) -> num
|
|
* (:/ num int) -> num
|
|
* (:/ num num) -> num
|
|
*/
|
|
|
|
static lith_value *builtin__divide(lith_st *L, lith_value *args)
|
|
{
|
|
COMMON1(":/")
|
|
COMMON3("divide", n2_is_integer)
|
|
COMMON2(/)
|
|
}
|
|
|
|
/* :%[2] (:% int int) -> int */
|
|
static lith_value *builtin__modulus(lith_st *L, lith_value *args)
|
|
{
|
|
lith_value *arg1, *arg2;
|
|
arg1 = LITH_CAR(args);
|
|
arg2 = LITH_CAR(LITH_CDR(args));
|
|
if (!LITH_IS(arg1, LITH_TYPE_INTEGER) || !LITH_IS(arg2, LITH_TYPE_INTEGER)) {
|
|
lith_simple_error(L, LITH_ERR_TYPE, "can calculate modulus with integral arguments only");
|
|
return NULL;
|
|
}
|
|
COMMON3("mod", 1)
|
|
return lith_make_integer(L, arg1->value.integer % arg2->value.integer);
|
|
}
|
|
|
|
#define COMMON4(op) \
|
|
if (n1_is_integer && n2_is_integer) { \
|
|
return LITH_IN_BOOL(arg1->value.integer op arg2->value.integer); \
|
|
} else { \
|
|
return LITH_IN_BOOL( \
|
|
(n1_is_integer \
|
|
? ((double) (arg1->value.integer)) \
|
|
: arg1->value.number) \
|
|
op \
|
|
(n2_is_integer \
|
|
? ((double) (arg2->value.integer)) \
|
|
: arg2->value.number) \
|
|
); \
|
|
}
|
|
|
|
/* type numeric = int U num ; huh!
|
|
* op2[2] :: op2 <- (:<, :==, :>)
|
|
* (op2 numeric numeric) -> bool
|
|
*/
|
|
|
|
static lith_value *builtin__is_less_than(lith_st *L, lith_value *args)
|
|
{
|
|
COMMON1(":<")
|
|
COMMON4(<)
|
|
}
|
|
|
|
static lith_value *builtin__is_num_equal(lith_st *L, lith_value *args)
|
|
{
|
|
COMMON1(":==")
|
|
COMMON4(==)
|
|
}
|
|
|
|
static lith_value *builtin__is_greater_than(lith_st *L, lith_value *args)
|
|
{
|
|
COMMON1(":>")
|
|
COMMON4(>)
|
|
}
|
|
|
|
#undef COMMON4
|
|
#undef COMMON3
|
|
#undef COMMON2
|
|
#undef COMMON1
|
|
|
|
/* eq?[2] :: (eq? a b) -> bool */
|
|
static lith_value *builtin__is_eq(lith_st *L, lith_value *args)
|
|
{
|
|
int eq;
|
|
lith_value *arg1, *arg2;
|
|
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);
|
|
}
|
|
|
|
/* typeof[1] :: (typeof a) -> sym */
|
|
static lith_value *builtin__typeof(lith_st *L, lith_value *args)
|
|
{
|
|
lith_value *val;
|
|
val = LITH_CAR(args);
|
|
return lith_get_symbol(L, L->types[val->type]);
|
|
}
|
|
|
|
/* nil?[1] :: (nil? a) -> bool */
|
|
static lith_value *builtin__is_nil(lith_st *L, lith_value *args)
|
|
{
|
|
return LITH_IN_BOOL(LITH_IS_NIL(LITH_CAR(args)));
|
|
}
|
|
|
|
/* list?[1] :: (list? a) -> bool */
|
|
static lith_value *builtin__is_list(lith_st *L, lith_value *args)
|
|
{
|
|
lith_value *val;
|
|
val = LITH_CAR(args);
|
|
return LITH_IN_BOOL(LITH_IS(val, LITH_TYPE_PAIR)
|
|
&& is_proper_list(val));
|
|
}
|
|
|
|
/* apply[2] :: (apply (i... -> a) (i...)) -> a */
|
|
static lith_value *builtin__apply(lith_st *L, lith_value *args)
|
|
{
|
|
lith_value *f, *aargs, *cargs;
|
|
f = LITH_CAR(args);
|
|
aargs = LITH_CAR(LITH_CDR(args));
|
|
cargs = lith_copy_value(L, aargs);
|
|
if (!cargs) return NULL;
|
|
return lith_apply(L, f, cargs);
|
|
}
|
|
|
|
/* error[1] :: (error str) -> _|_ */
|
|
static lith_value *builtin__error(lith_st *L, lith_value *args)
|
|
{
|
|
lith_value *arg;
|
|
arg = LITH_CAR(args);
|
|
if (!lith_expect_type(L, "error", 1, LITH_TYPE_STRING, arg)) return NULL;
|
|
L->error = LITH_ERR_CUSTOM;
|
|
L->error_state.msg = arg->value.string.buf;
|
|
return NULL;
|
|
}
|
|
|
|
/* load[1] :: (load str) -> ()
|
|
* the contents of the file given by
|
|
* the string containing the path of that file is executed
|
|
*/
|
|
|
|
static lith_value *builtin__load(lith_st *L, lith_value *args)
|
|
{
|
|
lith_value *filename;
|
|
filename = LITH_CAR(args);
|
|
if (!lith_expect_type(L, "load", 1, LITH_TYPE_STRING, filename)) return NULL;
|
|
lith_run_file(L, L->global, filename->value.string.buf);
|
|
if (LITH_IS_ERR(L))
|
|
return NULL;
|
|
else
|
|
return L->nil;
|
|
}
|
|
|
|
/* some more utilities */
|
|
|
|
static char *slurp(lith_st *L, char *filename)
|
|
{
|
|
FILE *file;
|
|
char *buffer;
|
|
long length;
|
|
|
|
file = fopen(filename, "r");
|
|
if (!file) {
|
|
lith_simple_error(L, LITH_ERR_CUSTOM, "could not open the file to be read");
|
|
return NULL;
|
|
}
|
|
|
|
fseek(file, 0, SEEK_END);
|
|
length = ftell(file);
|
|
fseek(file, 0, SEEK_SET);
|
|
|
|
buffer = emalloc(L, length + 1);
|
|
if (!buffer) return NULL;
|
|
|
|
fread(buffer, 1, length, file);
|
|
buffer[length] = '\0';
|
|
fclose(file);
|
|
|
|
return buffer;
|
|
}
|
|
|
|
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";
|
|
types[LITH_TYPE_MACRO] = "macro";
|
|
}
|
|
|
|
struct lith_lib_fn lith_builtins[] = {
|
|
{"car", 1, 1, builtin__car},
|
|
{"cdr", 1, 1, builtin__cdr},
|
|
{"cons", 2, 1, builtin__cons},
|
|
{"typeof", 1, 1, builtin__typeof},
|
|
{"print", 1, 0, builtin__print},
|
|
{":+", 2, 1, builtin__add},
|
|
{":-", 2, 1, builtin__subtract},
|
|
{":*", 2, 1, builtin__multiply},
|
|
{":/", 2, 1, builtin__divide},
|
|
{":%", 2, 1, builtin__modulus},
|
|
{":<", 2, 1, builtin__is_less_than},
|
|
{":==", 2, 1, builtin__is_num_equal},
|
|
{":>", 2, 1, builtin__is_greater_than},
|
|
{"eq?", 2, 1, builtin__is_eq},
|
|
{"nil?", 1, 1, builtin__is_nil},
|
|
{"list?", 1, 1, builtin__is_list},
|
|
{"apply", 2, 1, builtin__apply},
|
|
{"error", 1, 1, builtin__error},
|
|
{"load", 1, 1, builtin__load},
|
|
{NULL, 0, 0, NULL}
|
|
};
|
|
|
|
/* 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->error_state.expr = 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, lith_builtins);
|
|
}
|
|
|
|
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);
|
|
}
|
|
if (L->error_state.expr)
|
|
lith_free_value(L->error_state.expr);
|
|
free(L->False);
|
|
free(L->True);
|
|
free(L->nil);
|
|
}
|
|
|
|
void lith_clear_error_state(lith_st *L)
|
|
{
|
|
L->error = LITH_ERR_OK;
|
|
L->error_state.success = 1;
|
|
L->error_state.manual = 0;
|
|
L->error_state.msg = L->error_state.sym = L->error_state.name = NULL;
|
|
if (L->error_state.expr) {
|
|
lith_free_value(L->error_state.expr);
|
|
L->error_state.expr = NULL;
|
|
}
|
|
}
|
|
|
|
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_value *name, lith_builtin_function function,
|
|
size_t expect, int exact)
|
|
{
|
|
lith_value *val;
|
|
lith_callable *f;
|
|
val = lith_new_value(L);
|
|
if (!val) return NULL;
|
|
f = emalloc(L, sizeof(*f));
|
|
if (!f) { free(val); return NULL; }
|
|
f->name = name;
|
|
f->function = function;
|
|
f->expect = expect;
|
|
f->exact = exact;
|
|
val->type = LITH_TYPE_BUILTIN;
|
|
val->value.callable = f;
|
|
return val;
|
|
}
|
|
|
|
lith_value *lith_make_closure(lith_st *L, lith_env *parent_env,
|
|
lith_value *name, lith_value *arg_names, lith_value *body,
|
|
size_t expect, int exact
|
|
)
|
|
{
|
|
lith_value *val;
|
|
lith_callable *f;
|
|
val = lith_new_value(L);
|
|
if (!val) return NULL;
|
|
f = emalloc(L, sizeof(*f));
|
|
if (!f) { free(val); return NULL; }
|
|
arg_names = lith_copy_value(L, arg_names);
|
|
if (!arg_names) { free(val); free(f); return NULL; }
|
|
body = lith_copy_value(L, body);
|
|
if (!body) { free(val); free(f); lith_free_value(arg_names); return NULL; }
|
|
f->name = name;
|
|
f->parent = parent_env;
|
|
f->args = arg_names;
|
|
f->body = body;
|
|
f->expect = expect;
|
|
f->exact = exact;
|
|
val->type = LITH_TYPE_CLOSURE;
|
|
val->value.callable = f;
|
|
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_BUILTIN)) {
|
|
free(val->value.callable);
|
|
} else if (LITH_IS(val, LITH_TYPE_CLOSURE) || LITH_IS(val, LITH_TYPE_MACRO)) {
|
|
lith_free_value(val->value.callable->args);
|
|
lith_free_value(val->value.callable->body);
|
|
free(val->value.callable);
|
|
} 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_st *L, lith_value *val, FILE *file)
|
|
{
|
|
lith_callable *fn;
|
|
if (LITH_IS_NIL(val)) {
|
|
fprintf(file, "()");
|
|
} else if (LITH_IS(val, LITH_TYPE_SYMBOL)) {
|
|
fprintf(file, "%s", val->value.symbol);
|
|
} else if (LITH_IS(val, LITH_TYPE_STRING)) {
|
|
print_string(val->value.string, file);
|
|
} else if (LITH_IS(val, LITH_TYPE_BOOLEAN)) {
|
|
fprintf(file, "#%c", val->value.boolean ? 't' : 'f');
|
|
} else if (LITH_IS(val, LITH_TYPE_INTEGER)) {
|
|
fprintf(file, "%ld", val->value.integer);
|
|
} else if (LITH_IS(val, LITH_TYPE_NUMBER)) {
|
|
fprintf(file, "%.15g", val->value.number);
|
|
} else if (LITH_IS_CALLABLE(val)) {
|
|
fn = val->value.callable;
|
|
fprintf(file, "#<%s ", L->types[val->type]);
|
|
if (fn->name)
|
|
lith_print_value(L, fn->name, file);
|
|
else
|
|
fprintf(file, "[anon]");
|
|
fprintf(file, "[%zu%s]", fn->expect, fn->exact ? "" : "+");
|
|
fprintf(file, " at %p>", (void *)fn);
|
|
} else if (!LITH_IS(val, LITH_TYPE_PAIR)) {
|
|
fprintf(file, "#<unknown object at %p>", (void *)val);
|
|
} else {
|
|
fputc('(', file);
|
|
lith_print_value(L, LITH_CAR(val), file);
|
|
val = LITH_CDR(val);
|
|
while (!LITH_IS_NIL(val)) {
|
|
if (LITH_IS(val, LITH_TYPE_PAIR)) {
|
|
fputc(' ', file);
|
|
lith_print_value(L, LITH_CAR(val), file);
|
|
val = LITH_CDR(val);
|
|
} else {
|
|
fprintf(file, " . ");
|
|
lith_print_value(L, val, file);
|
|
break;
|
|
}
|
|
}
|
|
fputc(')', file);
|
|
}
|
|
}
|
|
|
|
lith_value *lith_copy_value(lith_st *L, lith_value *val)
|
|
{
|
|
lith_value *head, *pair, *p, *v, *w;
|
|
lith_callable *f;
|
|
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:
|
|
f = val->value.callable;
|
|
return lith_make_builtin(L, lith_copy_value(L, f->name), f->function, f->expect, f->exact);
|
|
case LITH_TYPE_MACRO:
|
|
case LITH_TYPE_CLOSURE:
|
|
f = val->value.callable;
|
|
v = lith_make_closure(L, f->parent, lith_copy_value(L, f->name),
|
|
f->args, f->body, f->expect, f->exact);
|
|
if (LITH_IS(val, LITH_TYPE_MACRO))
|
|
v->type = LITH_TYPE_MACRO;
|
|
return v;
|
|
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_simple_error(lith_st *L, enum lith_error errtype, char *msg)
|
|
{
|
|
L->error = errtype;
|
|
L->error_state.msg = msg;
|
|
if (errtype == LITH_ERR_EOF)
|
|
L->error_state.success = 0;
|
|
else if (errtype == LITH_ERR_TYPE)
|
|
L->error_state.manual = 1;
|
|
}
|
|
|
|
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, "out of memory");
|
|
break;
|
|
case LITH_ERR_UNBOUND:
|
|
fprintf(stderr, "unbound symbol: '%s'", E.sym);
|
|
break;
|
|
case LITH_ERR_REDEFINE:
|
|
fprintf(stderr, "trying to redefine already defined 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, "%s", 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;
|
|
case LITH_ERR_CUSTOM:
|
|
fprintf(stderr, "error: %s", E.msg);
|
|
break;
|
|
}
|
|
if (E.name)
|
|
fprintf(stderr, " [in '%s']", E.name);
|
|
if (E.expr) {
|
|
fprintf(stderr, "\noccured in: ");
|
|
lith_print_value(L, E.expr, stderr);
|
|
}
|
|
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_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) {
|
|
LITH_CDR(kv) = value;
|
|
return;
|
|
}
|
|
kvs = LITH_CDR(kvs);
|
|
}
|
|
} while (!LITH_IS_NIL(parent));
|
|
L->error = LITH_ERR_UNBOUND;
|
|
L->error_state.sym = name->value.symbol;
|
|
}
|
|
|
|
void lith_env_put(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)) {
|
|
L->error = LITH_ERR_REDEFINE;
|
|
L->error_state.sym = name->value.symbol;
|
|
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_lib lib)
|
|
{
|
|
lith_env *V;
|
|
lith_value *name;
|
|
struct lith_lib_fn *fns;
|
|
V = L->global;
|
|
for (fns = lib; fns->name; ++fns) {
|
|
name = lith_get_symbol(L, fns->name);
|
|
if (!name) return;
|
|
lith_env_put(L, V, name,
|
|
lith_make_builtin(L, name, fns->fn, fns->expect, fns->exact));
|
|
}
|
|
}
|
|
|
|
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;
|
|
E->expr = lith_copy_value(L, args);
|
|
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;
|
|
E->expr = lith_copy_value(L, val);
|
|
return 0;
|
|
}
|
|
|
|
lith_value *lith_eval_expr(lith_st *L, lith_env *V, lith_value *expr)
|
|
{
|
|
size_t i;
|
|
lith_value *f, *rest, *sym, *val, *args, *p, *q, *r;
|
|
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)) {
|
|
lith_simple_error(L, LITH_ERR_SYNTAX,
|
|
"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, "eval!")) {
|
|
if (!lith_expect_nargs(L, "eval!", 1, rest, 1))
|
|
return NULL;
|
|
val = lith_eval_expr(L, V, LITH_CAR(rest));
|
|
if (!val) return NULL;
|
|
return lith_eval_expr(L, V, val);
|
|
} 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, "def")) {
|
|
if (!lith_expect_nargs(L, "def", 2, rest, 1))
|
|
return NULL;
|
|
sym = LITH_CAR(rest);
|
|
p = LITH_CDR(rest);
|
|
if (!lith_expect_type(L, "def", 1, LITH_TYPE_SYMBOL, sym)) return NULL;
|
|
val = lith_eval_expr(L, V, LITH_CAR(p));
|
|
if (LITH_IS_CALLABLE(val))
|
|
val->value.callable->name = sym;
|
|
if (!val) return NULL;
|
|
lith_env_put(L, V, sym, val);
|
|
return L->nil;
|
|
} else if (LITH_SYM_EQ(f, "set!")) {
|
|
if (!lith_expect_nargs(L, "set!", 2, rest, 1))
|
|
return NULL;
|
|
sym = LITH_CAR(rest);
|
|
val = LITH_CAR(LITH_CDR(rest));
|
|
if (!lith_expect_type(L, "set!", 1, LITH_TYPE_SYMBOL, sym))
|
|
return NULL;
|
|
val = lith_eval_expr(L, V, val);
|
|
if (!val) return NULL;
|
|
lith_env_set(L, V, sym, val);
|
|
if (LITH_IS_CALLABLE(val))
|
|
val->value.callable->name = sym;
|
|
return L->nil;
|
|
} else if (LITH_SYM_EQ(f, "macro")) {
|
|
if (!lith_expect_nargs(L, "macro", 2, rest, 0))
|
|
return NULL;
|
|
args = LITH_CAR(rest);
|
|
p = LITH_CDR(rest);
|
|
if (!lith_expect_type(L, "macro", 1, LITH_TYPE_PAIR, args))
|
|
return NULL;
|
|
sym = LITH_CAR(args);
|
|
if (!lith_expect_type(L, "macro", 1, LITH_TYPE_SYMBOL, sym))
|
|
return NULL;
|
|
q = LITH_CONS(L, LITH_CDR(args), p);
|
|
if (!q) return NULL;
|
|
r = LITH_CONS(L, lith_get_symbol(L, "lambda"), q);
|
|
if (!r) { lith_free_value(q); return NULL; }
|
|
val = lith_eval_expr(L, V, r);
|
|
if (!val) return NULL;
|
|
val->type = LITH_TYPE_MACRO;
|
|
val->value.callable->name = sym;
|
|
lith_env_put(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)) {
|
|
lith_simple_error(L, LITH_ERR_SYNTAX,
|
|
"body of lambda expression must be proper list");
|
|
return NULL;
|
|
}
|
|
for (i = 0, q = args; LITH_IS(q, LITH_TYPE_PAIR); q = LITH_CDR(q), i++) {
|
|
if (!LITH_IS(LITH_CAR(q), LITH_TYPE_SYMBOL)) {
|
|
lith_simple_error(L, LITH_ERR_SYNTAX,
|
|
"arguments in lambda expression must be symbols");
|
|
return NULL;
|
|
}
|
|
}
|
|
if (!LITH_IS_NIL(q) && !LITH_IS(q, LITH_TYPE_SYMBOL)) {
|
|
lith_simple_error(L, LITH_ERR_SYNTAX,
|
|
"arguments in lambda expression must be symbols");
|
|
return NULL;
|
|
}
|
|
return lith_make_closure(L, V, NULL, args, p, i, LITH_IS_NIL(q));
|
|
}
|
|
}
|
|
f = lith_eval_expr(L, V, f);
|
|
if (!f) return NULL;
|
|
args = lith_copy_value(L, rest);
|
|
if (!args) return NULL;
|
|
if (LITH_IS(f, LITH_TYPE_MACRO)) {
|
|
val = lith_apply(L, f, args);
|
|
if (!val) return NULL;
|
|
return lith_eval_expr(L, V, val);
|
|
}
|
|
if (!LITH_IS_NIL(args)) {
|
|
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;
|
|
}
|
|
}
|
|
return lith_apply(L, f, args);
|
|
}
|
|
|
|
lith_value *lith_apply(lith_st *L, lith_value *f, lith_value *args)
|
|
{
|
|
lith_env *env;
|
|
lith_value *expected_args, *body, *r;
|
|
lith_callable *fn;
|
|
|
|
if (!LITH_IS_CALLABLE(f)) {
|
|
lith_simple_error(L, LITH_ERR_TYPE, "can not call non-callable");
|
|
L->error_state.name = "{apply}";
|
|
return NULL;
|
|
}
|
|
fn = f->value.callable;
|
|
if (!lith_expect_nargs(L,
|
|
fn->name ? fn->name->value.symbol : "{lambda}",
|
|
fn->expect, args, fn->exact)) return NULL;
|
|
if (LITH_IS(f, LITH_TYPE_BUILTIN))
|
|
return (*fn->function)(L, args);
|
|
env = lith_new_env(L, fn->parent);
|
|
body = fn->body;
|
|
expected_args = fn->args;
|
|
while (LITH_IS(expected_args, LITH_TYPE_PAIR)) {
|
|
lith_env_put(L, env, LITH_CAR(expected_args), LITH_CAR(args));
|
|
expected_args = LITH_CDR(expected_args);
|
|
args = LITH_CDR(args);
|
|
}
|
|
if (!LITH_IS_NIL(expected_args))
|
|
lith_env_put(L, env, expected_args, 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, int repl)
|
|
{
|
|
char *end;
|
|
lith_value *expr, *res;
|
|
end = input;
|
|
L->filename = repl ? "<<stdin>>" : "<<string>>";
|
|
|
|
while (!LITH_IS_ERR(L)) {
|
|
if ((expr = lith_read_expr(L, end, &end))) {
|
|
if (!repl) {
|
|
printf(">> ");
|
|
lith_print_value(L, expr, stdout);
|
|
putchar('\n');
|
|
}
|
|
if ((res = lith_eval_expr(L, V, expr))) {
|
|
printf("-> ");
|
|
lith_print_value(L, res, stdout);
|
|
lith_free_value(res);
|
|
putchar('\n');
|
|
}
|
|
lith_free_value(expr);
|
|
}
|
|
}
|
|
|
|
if (LITH_AT_END_NO_ERR(L))
|
|
lith_clear_error_state(L);
|
|
else
|
|
lith_print_error(L, 1);
|
|
}
|
|
|
|
void lith_run_file(lith_st *L, lith_env *V, char *filename)
|
|
{
|
|
char *contents, *end;
|
|
lith_value *expr, *result;
|
|
L->filename = filename;
|
|
contents = slurp(L, filename);
|
|
if (!contents) {
|
|
lith_print_error(L, 1);
|
|
return;
|
|
}
|
|
end = contents;
|
|
while (!LITH_IS_ERR(L)) {
|
|
if ((expr = lith_read_expr(L, end, &end))) {
|
|
if ((result = lith_eval_expr(L, V, expr))) {
|
|
lith_free_value(result);
|
|
} else {
|
|
break;
|
|
}
|
|
lith_free_value(expr);
|
|
}
|
|
}
|
|
free(contents);
|
|
if (LITH_AT_END_NO_ERR(L)) {
|
|
lith_clear_error_state(L);
|
|
return;
|
|
}
|
|
|
|
lith_print_error(L, 1);
|
|
if (expr) {
|
|
fprintf(stderr, "error occurred when evaluating the expression:\n\t");
|
|
lith_print_value(L, expr, stderr);
|
|
fputc('\n', stderr);
|
|
lith_free_value(expr);
|
|
}
|
|
}
|