diff options
| author | Joel Martin <github@martintribe.org> | 2014-03-24 16:32:24 -0500 |
|---|---|---|
| committer | Joel Martin <github@martintribe.org> | 2014-03-24 16:32:24 -0500 |
| commit | 3169070063b2cb877200117ebb384269d73bcb93 (patch) | |
| tree | 23de3db1ea5c37afd21a45b6ed7771f56a08c0c4 /c | |
| download | mal-3169070063b2cb877200117ebb384269d73bcb93.tar.gz mal-3169070063b2cb877200117ebb384269d73bcb93.zip | |
Current state of mal for Clojure West lighting talk.
Diffstat (limited to 'c')
| -rw-r--r-- | c/Makefile | 61 | ||||
| -rw-r--r-- | c/interop.c | 165 | ||||
| -rw-r--r-- | c/interop.h | 6 | ||||
| -rw-r--r-- | c/reader.c | 285 | ||||
| -rw-r--r-- | c/reader.h | 23 | ||||
| -rw-r--r-- | c/readline.c | 69 | ||||
| -rw-r--r-- | c/readline.h | 6 | ||||
| -rw-r--r-- | c/step0_repl.c | 44 | ||||
| -rw-r--r-- | c/step1_read_print.c | 81 | ||||
| -rw-r--r-- | c/step2_eval.c | 145 | ||||
| -rw-r--r-- | c/step3_env.c | 171 | ||||
| -rw-r--r-- | c/step4_if_fn_do.c | 215 | ||||
| -rw-r--r-- | c/step5_tco.c | 222 | ||||
| -rw-r--r-- | c/step6_file.c | 282 | ||||
| -rw-r--r-- | c/step7_quote.c | 318 | ||||
| -rw-r--r-- | c/step8_macros.c | 357 | ||||
| -rw-r--r-- | c/step9_interop.c | 362 | ||||
| -rw-r--r-- | c/stepA_more.c | 393 | ||||
| -rw-r--r-- | c/tests/step9_interop.mal | 23 | ||||
| -rw-r--r-- | c/types.c | 1038 | ||||
| -rw-r--r-- | c/types.h | 162 |
21 files changed, 4428 insertions, 0 deletions
diff --git a/c/Makefile b/c/Makefile new file mode 100644 index 0000000..397bcbf --- /dev/null +++ b/c/Makefile @@ -0,0 +1,61 @@ +USE_READLINE ?= +CFLAGS += -g +LDFLAGS += -g + +##################### + +TESTS = + +SOURCES = types.h types.c readline.h readline.c reader.h reader.c \ + interop.h interop.c stepA_more.c + +##################### + +SRCS = step0_repl.c step1_read_print.c step2_eval.c step3_env.c \ + step4_if_fn_do.c step5_tco.c step6_file.c step7_quote.c \ + step8_macros.c step9_interop.c stepA_more.c +OBJS = $(SRCS:%.c=%.o) +BINS = $(OBJS:%.o=%) +OTHER_OBJS = types.o readline.o reader.o interop.o +OTHER_HDRS = types.h readline.h reader.h interop.h + +GLIB_CFLAGS ?= $(shell pkg-config --cflags glib-2.0) +GLIB_LDFLAGS ?= $(shell pkg-config --libs glib-2.0) + +ifeq (,$(USE_READLINE)) +RL_LIBRARY ?= edit +else +RL_LIBRARY ?= readline +CFLAGS += -DUSE_READLINE=1 +endif + +CFLAGS += $(GLIB_CFLAGS) +LDFLAGS += -l$(RL_LIBRARY) $(GLIB_LDFLAGS) -ldl -lffi + +##################### + +all: $(BINS) mal + +mal: $(word $(words $(BINS)),$(BINS)) + cp $< $@ + +$(OBJS) $(OTHER_OBJS): %.o: %.c $(OTHER_HDRS) + gcc $(CFLAGS) -c $(@:%.o=%.c) -o $@ + +$(patsubst %.o,%,$(filter step%,$(OBJS))): $(OTHER_OBJS) +$(BINS): %: %.o + gcc $+ -o $@ $(LDFLAGS) + +clean: + rm -f $(OBJS) $(BINS) $(OTHER_OBJS) mal + +.PHONY: stats tests $(TESTS) + +stats: $(SOURCES) + @wc $^ + +tests: $(TESTS) + +$(TESTS): + @echo "Running $@"; \ + ./$@ || exit 1; \ diff --git a/c/interop.c b/c/interop.c new file mode 100644 index 0000000..276b99e --- /dev/null +++ b/c/interop.c @@ -0,0 +1,165 @@ +#include <dlfcn.h> +#include <ffi.h> +#include "types.h" + + +GHashTable *loaded_dls = NULL; + +int get_byte_size(char *type) { +} + +typedef struct Raw64 { + union { + gdouble floatnum; + gint64 integernum; + char *string; + } v; +} Raw64; + + +// obj must be a pointer to the object to store +ffi_type *_get_ffi_type(char *type) { + if ((strcmp("void", type) == 0)) { + return &ffi_type_void; + } else if ((strcmp("string", type) == 0) || + (strcmp("char*", type) == 0) || + (strcmp("char *", type) == 0)) { + return &ffi_type_pointer; + } else if ((strcmp("integer", type) == 0) || + (strcmp("int64", type) == 0)) { + return &ffi_type_sint64; + } else if ((strcmp("int32", type) == 0)) { + return &ffi_type_sint32; + } else if (strcmp("double", type) == 0) { + return &ffi_type_double; + } else if (strcmp("float", type) == 0) { + return &ffi_type_float; + } else { + abort("_get_ffi_type of unknown type '%s'", type); + } +} + +MalVal *_malval_new_by_type(char *type) { + if ((strcmp("void", type) == 0)) { + return NULL; + } else if ((strcmp("string", type) == 0) || + (strcmp("char*", type) == 0) || + (strcmp("char *", type) == 0)) { + return malval_new(MAL_STRING, NULL); + } else if ((strcmp("integer", type) == 0) || + (strcmp("int64", type) == 0)) { + return malval_new(MAL_INTEGER, NULL); + } else if ((strcmp("int32", type) == 0)) { + return malval_new(MAL_INTEGER, NULL); + } else if (strcmp("double", type) == 0) { + return malval_new(MAL_FLOAT, NULL); + } else if (strcmp("float", type) == 0) { + return malval_new(MAL_FLOAT, NULL); + } else { + abort("_malval_new_by_type of unknown type '%s'", type); + } +} + + + +// Mal syntax: +// (. {DYN_LIB_FILE|nil} RETURN_TYPE FUNC_NAME [ARG_TYPE ARG]...) +MalVal *invoke_native(MalVal *call_data) { + //g_print("invoke_native %s\n", pr_str(call_data)); + int cd_len = call_data->val.array->len; + int arg_len = (cd_len - 3)/2; + char *error; + void *dl_handle; + + assert_type(call_data, MAL_LIST, + "invoke_native called with non-list call_data: %s", + _pr_str(call_data,1)); + assert(cd_len >= 3, + "invoke_native called with %d args, needs at least 3", + cd_len); + assert((cd_len % 2) == 1, + "invoke_native called with an even number of args (%d)", + cd_len); + assert(arg_len <= 3, + "invoke_native called with more than 3 native args (%d)", + arg_len); + MalVal *dl_file = _nth(call_data, 0), + *ftype = _nth(call_data, 1), + *fname = _nth(call_data, 2); + assert_type(dl_file, MAL_STRING|MAL_NIL, + "invoke_native arg 1 (DYN_LIB_NAME) must be a string or nil"); + assert_type(ftype, MAL_STRING, + "invoke_native arg 2 (RETURN_TYPE) must be a string"); + assert_type(fname, MAL_STRING, + "invoke_native arg 3 (FUNC_NAME) must be a string"); + + // Cached load of the dynamic library handle + if (dl_file->type == MAL_NIL) { + dl_handle = dlopen(NULL, RTLD_LAZY); + } else { + // Load the library + if (loaded_dls == NULL) { + loaded_dls = g_hash_table_new(g_str_hash, g_str_equal); + } + dl_handle = g_hash_table_lookup(loaded_dls, dl_file->val.string); + dlerror(); // clear any existing error + if (!dl_handle) { + dl_handle = dlopen(dl_file->val.string, RTLD_LAZY); + } + if ((error = dlerror()) != NULL) { + abort("Could not dlopen '%s': %s", dl_file->val.string, error); + } + g_hash_table_insert(loaded_dls, dl_file->val.string, dl_handle); + } + + void * func = dlsym(dl_handle, fname->val.string); + if ((error = dlerror()) != NULL) { + abort("Could not dlsym '%s': %s", fname->val.string, error); + } + + + // + // Use FFI library to make a dynamic call + // + + // Based on: + // http://eli.thegreenplace.net/2013/03/04/flexible-runtime-interface-to-shared-libraries-with-libffi/ + ffi_cif cif; + ffi_type *ret_type; + ffi_type *arg_types[20]; + void *arg_vals[20]; + ffi_status status; + MalVal *ret_mv; + + // Set return type + ret_type = _get_ffi_type(ftype->val.string); + ret_mv = _malval_new_by_type(ftype->val.string); + if (mal_error) { return NULL; } + + // Set the argument types and values + int i; + for (i=0; i < arg_len; i++) { + arg_types[i] = _get_ffi_type(_nth(call_data, 3+i*2)->val.string); + if (arg_types[i] == NULL) { + return NULL; + } + arg_vals[i] = &_nth(call_data, 4+i*2)->val; + } + + status = ffi_prep_cif(&cif, FFI_DEFAULT_ABI, arg_len, + ret_type, arg_types); + if (status != FFI_OK) { + abort("ffi_prep_cif failed: %d\n", status); + } + + // Perform the call + //g_print("Calling %s[%p](%d)\n", fname->val.string, func, arg_len); + ffi_call(&cif, FFI_FN(func), &ret_mv->val, arg_vals); + + if (ret_type == &ffi_type_void) { + return &mal_nil; + } else { + return ret_mv; + } +} + diff --git a/c/interop.h b/c/interop.h new file mode 100644 index 0000000..bcb2350 --- /dev/null +++ b/c/interop.h @@ -0,0 +1,6 @@ +#ifndef __MAL_INTEROP__ +#define __MAL_INTEROP__ + +MalVal *invoke_native(MalVal *call_data); + +#endif diff --git a/c/reader.c b/c/reader.c new file mode 100644 index 0000000..044bb84 --- /dev/null +++ b/c/reader.c @@ -0,0 +1,285 @@ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include <glib/gregex.h> +#include <glib-object.h> + +#include "types.h" +#include "reader.h" + +// Declare +MalVal *read_form(Reader *reader); + +Reader *reader_new() { + Reader *reader = (Reader*)malloc(sizeof(Reader)); + reader->array = g_array_sized_new(TRUE, FALSE, sizeof(char *), 8); + reader->position = 0; + return reader; +} + +int reader_append(Reader *reader, char* token) { + g_array_append_val(reader->array, token); + return TRUE; +} + +char *reader_peek(Reader *reader) { + return g_array_index(reader->array, char*, reader->position); +} + +char *reader_next(Reader *reader) { + if (reader->position >= reader->array->len) { + return NULL; + } else { + return g_array_index(reader->array, char*, reader->position++); + } +} + +void reader_free(Reader *reader) { + int i; + for(i=0; i < reader->array->len; i++) { + free(g_array_index(reader->array, char*, i)); + } + g_array_free(reader->array, TRUE); + free(reader); +} + +Reader *tokenize(char *line) { + GRegex *regex; + GMatchInfo *matchInfo; + GError *err = NULL; + + Reader *reader = reader_new(); + + regex = g_regex_new ("[\\s ,]*(~@|[\\[\\]{}()'`~@]|\"(?:[\\\\].|[^\\\\\"])*\"|;.*|[^\\s \\[\\]{}()'\"`~@,;]*)", 0, 0, &err); + g_regex_match (regex, line, 0, &matchInfo); + + if (err != NULL) { + fprintf(stderr, "Tokenize error: %s\n", err->message); + return NULL; + } + + while (g_match_info_matches(matchInfo)) { + gchar *result = g_match_info_fetch(matchInfo, 1); + if (result[0] != '\0' && result[0] != ';') { + reader_append(reader, result); + } + g_match_info_next(matchInfo, &err); + } + g_match_info_free(matchInfo); + g_regex_unref(regex); + if (reader->array->len == 0) { + reader_free(reader); + return NULL; + } else { + return reader; + } +} + + +// From http://creativeandcritical.net/str-replace-c/ - Laird Shaw +char *replace_str(const char *str, const char *old, const char *new) +{ + char *ret, *r; + const char *p, *q; + size_t oldlen = strlen(old); + size_t count, retlen, newlen = strlen(new); + + if (oldlen != newlen) { + for (count = 0, p = str; (q = strstr(p, old)) != NULL; p = q + oldlen) + count++; + /* this is undefined if p - str > PTRDIFF_MAX */ + retlen = p - str + strlen(p) + count * (newlen - oldlen); + } else + retlen = strlen(str); + + if ((ret = malloc(retlen + 1)) == NULL) + return NULL; + + for (r = ret, p = str; (q = strstr(p, old)) != NULL; p = q + oldlen) { + /* this is undefined if q - p > PTRDIFF_MAX */ + ptrdiff_t l = q - p; + memcpy(r, p, l); + r += l; + memcpy(r, new, newlen); + r += newlen; + } + strcpy(r, p); + + return ret; +} + + +MalVal *read_atom(Reader *reader) { + char *token; + GRegex *regex; + GMatchInfo *matchInfo; + GError *err = NULL; + gint pos; + MalVal *atom; + + token = reader_next(reader); + //g_print("read_atom token: %s\n", token); + + regex = g_regex_new ("(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^\"(.*)\"$|(^[^\"]*$)", 0, 0, &err); + g_regex_match (regex, token, 0, &matchInfo); + + if (g_match_info_fetch_pos(matchInfo, 1, &pos, NULL) && pos != -1) { + //g_print("read_atom integer\n"); + atom = malval_new_integer(g_ascii_strtoll(token, NULL, 10)); + } else if (g_match_info_fetch_pos(matchInfo, 2, &pos, NULL) && pos != -1) { + //g_print("read_atom float\n"); + atom = malval_new_float(g_ascii_strtod(token, NULL)); + } else if (g_match_info_fetch_pos(matchInfo, 3, &pos, NULL) && pos != -1) { + //g_print("read_atom nil\n"); + atom = &mal_nil; + } else if (g_match_info_fetch_pos(matchInfo, 4, &pos, NULL) && pos != -1) { + //g_print("read_atom true\n"); + atom = &mal_true; + } else if (g_match_info_fetch_pos(matchInfo, 5, &pos, NULL) && pos != -1) { + //g_print("read_atom false\n"); + atom = &mal_false; + } else if (g_match_info_fetch_pos(matchInfo, 6, &pos, NULL) && pos != -1) { + //g_print("read_atom string: %s\n", token); + char *str_tmp = replace_str(g_match_info_fetch(matchInfo, 6), "\\\"", "\""); + atom = malval_new_string(str_tmp); + } else if (g_match_info_fetch_pos(matchInfo, 7, &pos, NULL) && pos != -1) { + //g_print("read_atom symbol\n"); + atom = malval_new_symbol(g_match_info_fetch(matchInfo, 7)); + } else { + malval_free(atom); + atom = NULL; + } + return atom; +} + +MalVal *read_list(Reader *reader, MalType type, char start, char end) { + MalVal *ast, *form; + char *token = reader_next(reader); + //g_print("read_list start token: %s\n", token); + if (token[0] != start) { abort("expected '(' or '['"); } + + ast = malval_new_list(type, g_array_new(TRUE, TRUE, sizeof(MalVal*))); + + while ((token = reader_peek(reader)) && + token[0] != end) { + //g_print("read_list internal token %s\n", token); + form = read_form(reader); + if (!form) { + if (!mal_error) { abort("unknown read_list failure"); } + g_array_free(ast->val.array, TRUE); + malval_free(ast); + return NULL; + } + g_array_append_val(ast->val.array, form); + } + if (!token) { abort("expected ')' or ']', got EOF"); } + reader_next(reader); + //g_print("read_list end token: %s\n", token); + return ast; +} + +MalVal *read_hash_map(Reader *reader) { + MalVal *lst = read_list(reader, MAL_LIST, '{', '}'); + MalVal *hm = hash_map(lst); + malval_free(lst); + return hm; +} + + +MalVal *read_form(Reader *reader) { + char *token; + MalVal *form = NULL, *tmp; + +// while(token = reader_next(reader)) { +// printf("token: %s\n", token); +// } +// return NULL; + + token = reader_peek(reader); + + if (!token) { return NULL; } + //g_print("read_form token: %s\n", token); + + switch (token[0]) { + case ';': + abort("comments not yet implemented"); + break; + case '\'': + reader_next(reader); + form = _list(2, malval_new_symbol("quote"), + read_form(reader)); + break; + case '`': + reader_next(reader); + form = _list(2, malval_new_symbol("quasiquote"), + read_form(reader)); + break; + case '~': + reader_next(reader); + if (token[1] == '@') { + form = _list(2, malval_new_symbol("splice-unquote"), + read_form(reader)); + } else { + form = _list(2, malval_new_symbol("unquote"), + read_form(reader)); + }; + break; + case '^': + reader_next(reader); + MalVal *meta = read_form(reader); + form = _list(3, malval_new_symbol("with-meta"), + read_form(reader), meta); + break; + case '@': + reader_next(reader); + form = _list(2, malval_new_symbol("deref"), + read_form(reader)); + break; + + + // list + case ')': + abort("unexpected ')'"); + break; + case '(': + form = read_list(reader, MAL_LIST, '(', ')'); + break; + + // vector + case ']': + abort("unexpected ']'"); + break; + case '[': + form = read_list(reader, MAL_VECTOR, '[', ']'); + break; + + // hash-map + case '}': + abort("unexpected '}'"); + break; + case '{': + form = read_hash_map(reader); + break; + + default: + form = read_atom(reader); + break; + } + return form; + +} + +MalVal *read_str (char *str) { + Reader *reader; + char *token; + MalVal *ast = NULL; + + reader = tokenize(str); + if (reader) { + ast = read_form(reader); + reader_free(reader); + } + + return ast; +} diff --git a/c/reader.h b/c/reader.h new file mode 100644 index 0000000..90f07ed --- /dev/null +++ b/c/reader.h @@ -0,0 +1,23 @@ +#ifndef __MAL_READER__ +#define __MAL_READER__ + +#include <glib.h> +#include <glib-object.h> + +#include "types.h" + +typedef struct { + GArray *array; + int position; +} Reader; + +Reader *reader_new(); +int reader_append(Reader *reader, char* token); +char *reader_peek(Reader *reader); +char *reader_next(Reader *reader); +void reader_free(Reader *reader); + +char *_readline (char prompt[]); +MalVal *read_str (); + +#endif diff --git a/c/readline.c b/c/readline.c new file mode 100644 index 0000000..b981ee7 --- /dev/null +++ b/c/readline.c @@ -0,0 +1,69 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#if USE_READLINE + #include <readline/readline.h> + #include <readline/history.h> + #include <readline/tilde.h> +#else + #include <editline/readline.h> + #include <editline/history.h> +#endif + +int history_loaded = 0; + +char HISTORY_FILE[] = "~/.mal-history"; + +int load_history() { + if (history_loaded) { return 0; } + int ret; + char *hf = tilde_expand(HISTORY_FILE); + if (access(hf, F_OK) != -1) { + // TODO: check if file exists first, use non-static path +#if USE_READLINE + ret = read_history(hf); +#else + FILE *fp = fopen(hf, "r"); + char *line = malloc(80); // getline reallocs as necessary + size_t sz = 80; + while ((ret = getline(&line, &sz, fp)) > 0) { + add_history(line); // Add line to in-memory history + } + free(line); + fclose(fp); +#endif + history_loaded = 1; + } + free(hf); +} + +int append_to_history() { + char *hf = tilde_expand(HISTORY_FILE); +#ifdef USE_READLINE + append_history(1, hf); +#else + HIST_ENTRY *he = history_get(history_length-1); + FILE *fp = fopen(hf, "a"); + fprintf(fp, "%s\n", he->line); + fclose(fp); +#endif + free(hf); +} + + +// line must be freed by caller +char *_readline (char prompt[]) { + char *line; + + load_history(); + + line = readline(prompt); + if (!line) return NULL; // EOF + add_history(line); // Add input to in-memory history + + append_to_history(); // Flush new line of history to disk + + return line; +} + diff --git a/c/readline.h b/c/readline.h new file mode 100644 index 0000000..d524f4a --- /dev/null +++ b/c/readline.h @@ -0,0 +1,6 @@ +#ifndef __MAL_READLINE__ +#define __MAL_READLINE__ + +char *_readline (char prompt[]); + +#endif diff --git a/c/step0_repl.c b/c/step0_repl.c new file mode 100644 index 0000000..f6d8048 --- /dev/null +++ b/c/step0_repl.c @@ -0,0 +1,44 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#ifdef USE_READLINE + #include <readline/readline.h> + #include <readline/history.h> +#else + #include <editline/readline.h> +#endif + +char *READ(char prompt[]) { + char *line; + line = readline(prompt); + if (!line) return NULL; // EOF + add_history(line); // Add input to history. + return line; +} + +char *EVAL(char *ast, void *env) { + return ast; +} + +char *PRINT(char *exp) { + return exp; +} + +int main() +{ + char *ast, *exp; + char prompt[100]; + + // Set the initial prompt + snprintf(prompt, sizeof(prompt), "user> "); + + for(;;) { + ast = READ(prompt); + if (!ast) return 0; + exp = EVAL(ast, NULL); + g_print("%s\n", PRINT(exp)); + + free(ast); // Free input string + } +} diff --git a/c/step1_read_print.c b/c/step1_read_print.c new file mode 100644 index 0000000..3612373 --- /dev/null +++ b/c/step1_read_print.c @@ -0,0 +1,81 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include "types.h" +#include "readline.h" +#include "reader.h" + +// read +MalVal *READ(char prompt[], char *str) { + char *line; + MalVal *ast; + if (str) { + line = str; + } else { + line = _readline(prompt); + if (!line) { + _error("EOF"); + return NULL; + } + } + ast = read_str(line); + if (!str) { free(line); } + return ast; +} + +// eval +MalVal *EVAL(MalVal *ast, GHashTable *env) { + if (!ast || mal_error) return NULL; + return ast; +} + +// print +char *PRINT(MalVal *exp) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", mal_error->val.string); + malval_free(mal_error); + mal_error = NULL; + return NULL; + } + return _pr_str(exp,1); +} + +// repl + +// read and eval +MalVal *RE(GHashTable *env, char *prompt, char *str) { + MalVal *ast, *exp; + ast = READ(prompt, str); + if (!ast || mal_error) return NULL; + exp = EVAL(ast, env); + if (ast != exp) { + malval_free(ast); // Free input structure + } + return exp; +} + +int main() +{ + MalVal *exp; + char *output; + char prompt[100]; + + // Set the initial prompt + snprintf(prompt, sizeof(prompt), "user> "); + + // REPL loop + for(;;) { + exp = RE(NULL, prompt, NULL); + if (mal_error && strcmp("EOF", mal_error) == 0) { + return 0; + } + output = PRINT(exp); + + if (output) { + g_print("%s\n", output); + free(output); // Free output string + } + + //malval_free(exp); // Free evaluated expression + } +} diff --git a/c/step2_eval.c b/c/step2_eval.c new file mode 100644 index 0000000..509e795 --- /dev/null +++ b/c/step2_eval.c @@ -0,0 +1,145 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include "types.h" +#include "readline.h" +#include "reader.h" + +// Declarations +MalVal *EVAL(MalVal *ast, GHashTable *env); + +// read +MalVal *READ(char prompt[], char *str) { + char *line; + MalVal *ast; + if (str) { + line = str; + } else { + line = _readline(prompt); + if (!line) { + _error("EOF"); + return NULL; + } + } + ast = read_str(line); + if (!str) { free(line); } + return ast; +} + +// eval +MalVal *eval_ast(MalVal *ast, GHashTable *env) { + if (!ast || mal_error) return NULL; + if (ast->type == MAL_SYMBOL) { + //g_print("EVAL symbol: %s\n", ast->val.string); + // TODO: check if not found + return g_hash_table_lookup(env, ast->val.string); + } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) { + //g_print("EVAL sequential: %s\n", _pr_str(ast,1)); + MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env); + if (!el || mal_error) return NULL; + el->type = ast->type; + return el; + } else if (ast->type == MAL_HASH_MAP) { + //g_print("EVAL hash_map: %s\n", _pr_str(ast,1)); + GHashTableIter iter; + gpointer key, value; + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + _count(ast))); + g_hash_table_iter_init (&iter, ast->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + MalVal *kname = malval_new_string((char *)key); + g_array_append_val(seq->val.array, kname); + MalVal *new_val = EVAL((MalVal *)value, env); + g_array_append_val(seq->val.array, new_val); + } + return hash_map(seq); + } else { + //g_print("EVAL scalar: %s\n", _pr_str(ast,1)); + return ast; + } +} + +MalVal *EVAL(MalVal *ast, GHashTable *env) { + //g_print("EVAL: %s\n", _pr_str(ast,1)); + if (!ast || mal_error) return NULL; + if (ast->type != MAL_LIST) { + return eval_ast(ast, env); + } + if (!ast || mal_error) return NULL; + + // apply list + //g_print("EVAL apply list: %s\n", _pr_str(ast,1)); + if (_count(ast) == 0) { return ast; } + MalVal *a0 = _nth(ast, 0); + assert_type(a0, MAL_SYMBOL, "Cannot invoke %s", _pr_str(a0,1)); + MalVal *el = eval_ast(ast, env); + if (!el || mal_error) { return NULL; } + MalVal *(*f)(void *, void*) = (MalVal *(*)(void*, void*))first(el); + //g_print("eval_invoke el: %s\n", _pr_str(el,1)); + return f(_nth(el, 1), _nth(el, 2)); +} + +// print +char *PRINT(MalVal *exp) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", mal_error->val.string); + malval_free(mal_error); + mal_error = NULL; + return NULL; + } + return _pr_str(exp,1); +} + +// repl + +// read and eval +MalVal *RE(GHashTable *env, char *prompt, char *str) { + MalVal *ast, *exp; + ast = READ(prompt, str); + if (!ast || mal_error) return NULL; + exp = EVAL(ast, env); + if (ast != exp) { + malval_free(ast); // Free input structure + } + return exp; +} + +// Setup the initial REPL environment +GHashTable *repl_env; + +void init_repl_env() { + repl_env = g_hash_table_new(g_str_hash, g_str_equal); + + g_hash_table_insert(repl_env, "+", int_plus); + g_hash_table_insert(repl_env, "-", int_minus); + g_hash_table_insert(repl_env, "*", int_multiply); + g_hash_table_insert(repl_env, "/", int_divide); +} + +int main() +{ + MalVal *exp; + char *output; + char prompt[100]; + + // Set the initial prompt and environment + snprintf(prompt, sizeof(prompt), "user> "); + init_repl_env(); + + // REPL loop + for(;;) { + exp = RE(repl_env, prompt, NULL); + if (mal_error && strcmp("EOF", mal_error->val.string) == 0) { + return 0; + } + output = PRINT(exp); + + if (output) { + g_print("%s\n", output); + free(output); // Free output string + } + + //malval_free(exp); // Free evaluated expression + } +} diff --git a/c/step3_env.c b/c/step3_env.c new file mode 100644 index 0000000..bc645b8 --- /dev/null +++ b/c/step3_env.c @@ -0,0 +1,171 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include "types.h" +#include "readline.h" +#include "reader.h" + +// Declarations +MalVal *EVAL(MalVal *ast, Env *env); + +// read +MalVal *READ(char prompt[], char *str) { + char *line; + MalVal *ast; + if (str) { + line = str; + } else { + line = _readline(prompt); + if (!line) { + _error("EOF"); + return NULL; + } + } + ast = read_str(line); + if (!str) { free(line); } + return ast; +} + +// eval +MalVal *eval_ast(MalVal *ast, Env *env) { + if (!ast || mal_error) return NULL; + if (ast->type == MAL_SYMBOL) { + //g_print("EVAL symbol: %s\n", ast->val.string); + return env_get(env, ast->val.string); + } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) { + //g_print("EVAL sequential: %s\n", _pr_str(ast,1)); + MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env); + if (!el || mal_error) return NULL; + el->type = ast->type; + return el; + } else if (ast->type == MAL_HASH_MAP) { + //g_print("EVAL hash_map: %s\n", _pr_str(ast,1)); + GHashTableIter iter; + gpointer key, value; + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + _count(ast))); + g_hash_table_iter_init (&iter, ast->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + MalVal *kname = malval_new_string((char *)key); + g_array_append_val(seq->val.array, kname); + MalVal *new_val = EVAL((MalVal *)value, env); + g_array_append_val(seq->val.array, new_val); + } + return hash_map(seq); + } else { + //g_print("EVAL scalar: %s\n", _pr_str(ast,1)); + return ast; + } +} + +MalVal *EVAL(MalVal *ast, Env *env) { + //g_print("EVAL: %s\n", _pr_str(ast,1)); + if (!ast || mal_error) return NULL; + if (ast->type != MAL_LIST) { + return eval_ast(ast, env); + } + if (!ast || mal_error) return NULL; + + // apply list + //g_print("EVAL apply list: %s\n", _pr_str(ast,1)); + int i, len; + if (_count(ast) == 0) { return ast; } + MalVal *a0 = _nth(ast, 0); + assert_type(a0, MAL_SYMBOL, "Cannot apply %s", _pr_str(a0,1)); + if (strcmp("def!", a0->val.string) == 0) { + //g_print("eval apply def!\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2); + MalVal *res = EVAL(a2, env); + env_set(env, a1->val.string, res); + return res; + } else if (strcmp("let*", a0->val.string) == 0) { + //g_print("eval apply let*\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2), + *key, *val; + assert_type(a1, MAL_LIST|MAL_VECTOR, + "let* bindings must be list or vector"); + len = _count(a1); + assert((len % 2) == 0, "odd number of let* bindings forms"); + Env *let_env = new_env(env, NULL, NULL); + for(i=0; i<len; i+=2) { + key = g_array_index(a1->val.array, MalVal*, i); + val = g_array_index(a1->val.array, MalVal*, i+1); + assert_type(key, MAL_SYMBOL, "let* bind to non-symbol"); + env_set(let_env, key->val.string, EVAL(val, let_env)); + } + return EVAL(a2, let_env); + } else { + //g_print("eval apply\n"); + MalVal *el = eval_ast(ast, env); + if (!el || mal_error) { return NULL; } + MalVal *(*f)(void *, void*) = (MalVal *(*)(void*, void*))first(el); + return f(_nth(el, 1), _nth(el, 2)); + } +} + +// print +char *PRINT(MalVal *exp) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", mal_error->val.string); + malval_free(mal_error); + mal_error = NULL; + return NULL; + } + return _pr_str(exp,1); +} + +// repl + +// read and eval +MalVal *RE(Env *env, char *prompt, char *str) { + MalVal *ast, *exp; + ast = READ(prompt, str); + if (!ast || mal_error) return NULL; + exp = EVAL(ast, env); + if (ast != exp) { + malval_free(ast); // Free input structure + } + return exp; +} + +// Setup the initial REPL environment +Env *repl_env; + +void init_repl_env() { + repl_env = new_env(NULL, NULL, NULL); + + env_set(repl_env, "+", (MalVal *)int_plus); + env_set(repl_env, "-", (MalVal *)int_minus); + env_set(repl_env, "*", (MalVal *)int_multiply); + env_set(repl_env, "/", (MalVal *)int_divide); +} + +int main() +{ + MalVal *exp; + char *output; + char prompt[100]; + + // Set the initial prompt and environment + snprintf(prompt, sizeof(prompt), "user> "); + init_repl_env(); + + // REPL loop + for(;;) { + exp = RE(repl_env, prompt, NULL); + if (mal_error && strcmp("EOF", mal_error->val.string) == 0) { + return 0; + } + output = PRINT(exp); + + if (output) { + g_print("%s\n", output); + free(output); // Free output string + } + + //malval_free(exp); // Free evaluated expression + } +} diff --git a/c/step4_if_fn_do.c b/c/step4_if_fn_do.c new file mode 100644 index 0000000..a96641e --- /dev/null +++ b/c/step4_if_fn_do.c @@ -0,0 +1,215 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include "types.h" +#include "readline.h" +#include "reader.h" + +// Declarations +MalVal *EVAL(MalVal *ast, Env *env); + +// read +MalVal *READ(char prompt[], char *str) { + char *line; + MalVal *ast; + if (str) { + line = str; + } else { + line = _readline(prompt); + if (!line) { + _error("EOF"); + return NULL; + } + } + ast = read_str(line); + if (!str) { free(line); } + return ast; +} + +// eval +MalVal *eval_ast(MalVal *ast, Env *env) { + if (!ast || mal_error) return NULL; + if (ast->type == MAL_SYMBOL) { + //g_print("EVAL symbol: %s\n", ast->val.string); + return env_get(env, ast->val.string); + } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) { + //g_print("EVAL sequential: %s\n", _pr_str(ast,1)); + MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env); + if (!el || mal_error) return NULL; + el->type = ast->type; + return el; + } else if (ast->type == MAL_HASH_MAP) { + //g_print("EVAL hash_map: %s\n", _pr_str(ast,1)); + GHashTableIter iter; + gpointer key, value; + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + _count(ast))); + g_hash_table_iter_init (&iter, ast->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + MalVal *kname = malval_new_string((char *)key); + g_array_append_val(seq->val.array, kname); + MalVal *new_val = EVAL((MalVal *)value, env); + g_array_append_val(seq->val.array, new_val); + } + return hash_map(seq); + } else { + //g_print("EVAL scalar: %s\n", _pr_str(ast,1)); + return ast; + } +} + +MalVal *EVAL(MalVal *ast, Env *env) { + //g_print("EVAL: %s\n", _pr_str(ast,1)); + if (!ast || mal_error) return NULL; + if (ast->type != MAL_LIST) { + return eval_ast(ast, env); + } + if (!ast || mal_error) return NULL; + + // apply list + //g_print("EVAL apply list: %s\n", _pr_str(ast,1)); + int i, len; + if (_count(ast) == 0) { return ast; } + MalVal *a0 = _nth(ast, 0); + if ((a0->type & MAL_SYMBOL) && + strcmp("def!", a0->val.string) == 0) { + //g_print("eval apply def!\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2); + MalVal *res = EVAL(a2, env); + env_set(env, a1->val.string, res); + return res; + } else if ((a0->type & MAL_SYMBOL) && + strcmp("let*", a0->val.string) == 0) { + //g_print("eval apply let*\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2), + *key, *val; + assert_type(a1, MAL_LIST|MAL_VECTOR, + "let* bindings must be list or vector"); + len = _count(a1); + assert((len % 2) == 0, "odd number of let* bindings forms"); + Env *let_env = new_env(env, NULL, NULL); + for(i=0; i<len; i+=2) { + key = g_array_index(a1->val.array, MalVal*, i); + val = g_array_index(a1->val.array, MalVal*, i+1); + assert_type(key, MAL_SYMBOL, "let* bind to non-symbol"); + env_set(let_env, key->val.string, EVAL(val, let_env)); + } + return EVAL(a2, let_env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("do", a0->val.string) == 0) { + //g_print("eval apply do\n"); + MalVal *el = eval_ast(rest(ast), env); + return last(el); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("if", a0->val.string) == 0) { + //g_print("eval apply if\n"); + MalVal *a1 = _nth(ast, 1); + MalVal *cond = EVAL(a1, env); + if (!ast || mal_error) return NULL; + if (cond->type & (MAL_FALSE|MAL_NIL)) { + // eval false slot form + MalVal *a3 = _nth(ast, 3); + if (a3) { + return EVAL(a3, env); + } else { + return &mal_nil; + } + } else { + // eval true slot form + MalVal *a2 = _nth(ast, 2); + return EVAL(a2, env); + } + } else if ((a0->type & MAL_SYMBOL) && + strcmp("fn*", a0->val.string) == 0) { + //g_print("eval apply fn*\n"); + MalVal *mf = malval_new(MAL_FUNCTION_MAL, NULL); + mf->val.func.evaluator = EVAL; + mf->val.func.args = _nth(ast, 1); + mf->val.func.body = _nth(ast, 2); + mf->val.func.env = env; + return mf; + } else { + //g_print("eval apply\n"); + MalVal *el = eval_ast(ast, env); + if (!el || mal_error) { return NULL; } + MalVal *f = first(el), + *args = rest(el); + assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL, + "cannot apply '%s'", _pr_str(f,1)); + return apply(f, args); + } +} + +// print +char *PRINT(MalVal *exp) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", mal_error->val.string); + malval_free(mal_error); + mal_error = NULL; + return NULL; + } + return _pr_str(exp,1); +} + +// repl + +// read and eval +MalVal *RE(Env *env, char *prompt, char *str) { + MalVal *ast, *exp; + ast = READ(prompt, str); + if (!ast || mal_error) return NULL; + exp = EVAL(ast, env); + if (ast != exp) { + malval_free(ast); // Free input structure + } + return exp; +} + +// Setup the initial REPL environment +Env *repl_env; + +void init_repl_env() { + void _ref(char *name, MalVal*(*func)(MalVal*), int arg_cnt) { + void *(*f)(void *) = (void*(*)(void*))func; + env_set(repl_env, name, malval_new_function(f, arg_cnt, NULL)); + } + repl_env = new_env(NULL, NULL, NULL); + + int i; + for(i=0; i< (sizeof(types_ns) / sizeof(types_ns[0])); i++) { + MalVal *(*f)(MalVal *) = (MalVal*(*)(MalVal*))types_ns[i].func; + _ref(types_ns[i].name, f, types_ns[i].arg_cnt); + } + + RE(repl_env, "", "(def! not (fn* (a) (if a false true)))"); +} + +int main() +{ + MalVal *exp; + char *output; + char prompt[100]; + + // Set the initial prompt and environment + snprintf(prompt, sizeof(prompt), "user> "); + init_repl_env(); + + // REPL loop + for(;;) { + exp = RE(repl_env, prompt, NULL); + if (mal_error && strcmp("EOF", mal_error->val.string) == 0) { + return 0; + } + output = PRINT(exp); + + if (output) { + g_print("%s\n", output); + free(output); // Free output string + } + + //malval_free(exp); // Free evaluated expression + } +} diff --git a/c/step5_tco.c b/c/step5_tco.c new file mode 100644 index 0000000..dc0b28e --- /dev/null +++ b/c/step5_tco.c @@ -0,0 +1,222 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include "types.h" +#include "readline.h" +#include "reader.h" + +// Declarations +MalVal *EVAL(MalVal *ast, Env *env); + +// read +MalVal *READ(char prompt[], char *str) { + char *line; + MalVal *ast; + if (str) { + line = str; + } else { + line = _readline(prompt); + if (!line) { + _error("EOF"); + return NULL; + } + } + ast = read_str(line); + if (!str) { free(line); } + return ast; +} + +// eval +MalVal *eval_ast(MalVal *ast, Env *env) { + if (!ast || mal_error) return NULL; + if (ast->type == MAL_SYMBOL) { + //g_print("EVAL symbol: %s\n", ast->val.string); + return env_get(env, ast->val.string); + } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) { + //g_print("EVAL sequential: %s\n", _pr_str(ast,1)); + MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env); + if (!el || mal_error) return NULL; + el->type = ast->type; + return el; + } else if (ast->type == MAL_HASH_MAP) { + //g_print("EVAL hash_map: %s\n", _pr_str(ast,1)); + GHashTableIter iter; + gpointer key, value; + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + _count(ast))); + g_hash_table_iter_init (&iter, ast->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + MalVal *kname = malval_new_string((char *)key); + g_array_append_val(seq->val.array, kname); + MalVal *new_val = EVAL((MalVal *)value, env); + g_array_append_val(seq->val.array, new_val); + } + return hash_map(seq); + } else { + //g_print("EVAL scalar: %s\n", _pr_str(ast,1)); + return ast; + } +} + +MalVal *EVAL(MalVal *ast, Env *env) { + while (TRUE) { + //g_print("EVAL: %s\n", _pr_str(ast,1)); + if (!ast || mal_error) return NULL; + if (ast->type != MAL_LIST) { + return eval_ast(ast, env); + } + if (!ast || mal_error) return NULL; + + // apply list + //g_print("EVAL apply list: %s\n", _pr_str(ast,1)); + int i, len; + if (_count(ast) == 0) { return ast; } + MalVal *a0 = _nth(ast, 0); + if ((a0->type & MAL_SYMBOL) && + strcmp("def!", a0->val.string) == 0) { + //g_print("eval apply def!\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2); + MalVal *res = EVAL(a2, env); + env_set(env, a1->val.string, res); + return res; + } else if ((a0->type & MAL_SYMBOL) && + strcmp("let*", a0->val.string) == 0) { + //g_print("eval apply let*\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2), + *key, *val; + assert_type(a1, MAL_LIST|MAL_VECTOR, + "let* bindings must be list or vector"); + len = _count(a1); + assert((len % 2) == 0, "odd number of let* bindings forms"); + Env *let_env = new_env(env, NULL, NULL); + for(i=0; i<len; i+=2) { + key = g_array_index(a1->val.array, MalVal*, i); + val = g_array_index(a1->val.array, MalVal*, i+1); + assert_type(key, MAL_SYMBOL, "let* bind to non-symbol"); + env_set(let_env, key->val.string, EVAL(val, let_env)); + } + return EVAL(a2, let_env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("do", a0->val.string) == 0) { + //g_print("eval apply do\n"); + eval_ast(_slice(ast, 1, _count(ast)-1), env); + ast = last(ast); + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("if", a0->val.string) == 0) { + //g_print("eval apply if\n"); + MalVal *a1 = _nth(ast, 1); + MalVal *cond = EVAL(a1, env); + if (!cond || mal_error) return NULL; + if (cond->type & (MAL_FALSE|MAL_NIL)) { + // eval false slot form + ast = _nth(ast, 3); + if (!ast) { + return &mal_nil; + } + } else { + // eval true slot form + ast = _nth(ast, 2); + } + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("fn*", a0->val.string) == 0) { + //g_print("eval apply fn*\n"); + MalVal *mf = malval_new(MAL_FUNCTION_MAL, NULL); + mf->val.func.evaluator = EVAL; + mf->val.func.args = _nth(ast, 1); + mf->val.func.body = _nth(ast, 2); + mf->val.func.env = env; + return mf; + } else { + //g_print("eval apply\n"); + MalVal *el = eval_ast(ast, env); + if (!el || mal_error) { return NULL; } + MalVal *f = first(el), + *args = rest(el); + assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL, + "cannot invoke '%s'", _pr_str(f,1)); + if (f->type & MAL_FUNCTION_MAL) { + ast = f->val.func.body; + env = new_env(f->val.func.env, f->val.func.args, args); + // Continue loop + } else { + return apply(f, args); + } + } + } +} + +// print +char *PRINT(MalVal *exp) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", mal_error->val.string); + malval_free(mal_error); + mal_error = NULL; + return NULL; + } + return _pr_str(exp,1); +} + +// repl + +// read and eval +MalVal *RE(Env *env, char *prompt, char *str) { + MalVal *ast, *exp; + ast = READ(prompt, str); + if (!ast || mal_error) return NULL; + exp = EVAL(ast, env); + if (ast != exp) { + malval_free(ast); // Free input structure + } + return exp; +} + +// Setup the initial REPL environment +Env *repl_env; + +void init_repl_env() { + void _ref(char *name, MalVal*(*func)(MalVal*), int arg_cnt) { + void *(*f)(void *) = (void*(*)(void*))func; + env_set(repl_env, name, malval_new_function(f, arg_cnt, NULL)); + } + repl_env = new_env(NULL, NULL, NULL); + + int i; + for(i=0; i< (sizeof(types_ns) / sizeof(types_ns[0])); i++) { + MalVal *(*f)(MalVal *) = (MalVal*(*)(MalVal*))types_ns[i].func; + _ref(types_ns[i].name, f, types_ns[i].arg_cnt); + } + + RE(repl_env, "", "(def! not (fn* (a) (if a false true)))"); +} + +int main() +{ + MalVal *exp; + char *output; + char prompt[100]; + + // Set the initial prompt and environment + snprintf(prompt, sizeof(prompt), "user> "); + init_repl_env(); + + // REPL loop + for(;;) { + exp = RE(repl_env, prompt, NULL); + if (mal_error && strcmp("EOF", mal_error->val.string) == 0) { + return 0; + } + output = PRINT(exp); + + if (output) { + g_print("%s\n", output); + free(output); // Free output string + } + + //malval_free(exp); // Free evaluated expression + } +} diff --git a/c/step6_file.c b/c/step6_file.c new file mode 100644 index 0000000..875c32c --- /dev/null +++ b/c/step6_file.c @@ -0,0 +1,282 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> +#include "types.h" +#include "readline.h" +#include "reader.h" +#include "interop.h" + +// Declarations +MalVal *EVAL(MalVal *ast, Env *env); + +// read +MalVal *READ(char prompt[], char *str) { + char *line; + MalVal *ast; + if (str) { + line = str; + } else { + line = _readline(prompt); + if (!line) { + _error("EOF"); + return NULL; + } + } + ast = read_str(line); + if (!str) { free(line); } + return ast; +} + +// eval +MalVal *eval_ast(MalVal *ast, Env *env) { + if (!ast || mal_error) return NULL; + if (ast->type == MAL_SYMBOL) { + //g_print("EVAL symbol: %s\n", ast->val.string); + return env_get(env, ast->val.string); + } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) { + //g_print("EVAL sequential: %s\n", _pr_str(ast,1)); + MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env); + if (!el || mal_error) return NULL; + el->type = ast->type; + return el; + } else if (ast->type == MAL_HASH_MAP) { + //g_print("EVAL hash_map: %s\n", _pr_str(ast,1)); + GHashTableIter iter; + gpointer key, value; + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + _count(ast))); + g_hash_table_iter_init (&iter, ast->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + MalVal *kname = malval_new_string((char *)key); + g_array_append_val(seq->val.array, kname); + MalVal *new_val = EVAL((MalVal *)value, env); + g_array_append_val(seq->val.array, new_val); + } + return hash_map(seq); + } else { + //g_print("EVAL scalar: %s\n", _pr_str(ast,1)); + return ast; + } +} + +MalVal *EVAL(MalVal *ast, Env *env) { + while (TRUE) { + //g_print("EVAL: %s\n", _pr_str(ast,1)); + if (!ast || mal_error) return NULL; + if (ast->type != MAL_LIST) { + return eval_ast(ast, env); + } + if (!ast || mal_error) return NULL; + + // apply list + //g_print("EVAL apply list: %s\n", _pr_str(ast,1)); + int i, len; + if (_count(ast) == 0) { return ast; } + MalVal *a0 = _nth(ast, 0); + if ((a0->type & MAL_SYMBOL) && + strcmp("def!", a0->val.string) == 0) { + //g_print("eval apply def!\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2); + MalVal *res = EVAL(a2, env); + env_set(env, a1->val.string, res); + return res; + } else if ((a0->type & MAL_SYMBOL) && + strcmp("let*", a0->val.string) == 0) { + //g_print("eval apply let*\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2), + *key, *val; + assert_type(a1, MAL_LIST|MAL_VECTOR, + "let* bindings must be list or vector"); + len = _count(a1); + assert((len % 2) == 0, "odd number of let* bindings forms"); + Env *let_env = new_env(env, NULL, NULL); + for(i=0; i<len; i+=2) { + key = g_array_index(a1->val.array, MalVal*, i); + val = g_array_index(a1->val.array, MalVal*, i+1); + assert_type(key, MAL_SYMBOL, "let* bind to non-symbol"); + env_set(let_env, key->val.string, EVAL(val, let_env)); + } + return EVAL(a2, let_env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("do", a0->val.string) == 0) { + //g_print("eval apply do\n"); + eval_ast(_slice(ast, 1, _count(ast)-1), env); + ast = last(ast); + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("if", a0->val.string) == 0) { + //g_print("eval apply if\n"); + MalVal *a1 = _nth(ast, 1); + MalVal *cond = EVAL(a1, env); + if (!cond || mal_error) return NULL; + if (cond->type & (MAL_FALSE|MAL_NIL)) { + // eval false slot form + ast = _nth(ast, 3); + if (!ast) { + return &mal_nil; + } + } else { + // eval true slot form + ast = _nth(ast, 2); + } + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("fn*", a0->val.string) == 0) { + //g_print("eval apply fn*\n"); + MalVal *mf = malval_new(MAL_FUNCTION_MAL, NULL); + mf->val.func.evaluator = EVAL; + mf->val.func.args = _nth(ast, 1); + mf->val.func.body = _nth(ast, 2); + mf->val.func.env = env; + return mf; + } else { + //g_print("eval apply\n"); + MalVal *el = eval_ast(ast, env); + if (!el || mal_error) { return NULL; } + MalVal *f = first(el), + *args = rest(el); + assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL, + "cannot invoke '%s'", _pr_str(f,1)); + if (f->type & MAL_FUNCTION_MAL) { + ast = f->val.func.body; + env = new_env(f->val.func.env, f->val.func.args, args); + // Continue loop + } else { + return apply(f, args); + } + } + } +} + +// print +char *PRINT(MalVal *exp) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", mal_error->val.string); + malval_free(mal_error); + mal_error = NULL; + return NULL; + } + return _pr_str(exp,1); +} + +// repl + +// read and eval +MalVal *RE(Env *env, char *prompt, char *str) { + MalVal *ast, *exp; + ast = READ(prompt, str); + if (!ast || mal_error) return NULL; + exp = EVAL(ast, env); + if (ast != exp) { + malval_free(ast); // Free input structure + } + return exp; +} + +// Setup the initial REPL environment +Env *repl_env; + +char *slurp_raw(char *path) { + char *data; + struct stat fst; + int fd = open(path, O_RDONLY), + sz; + if (fd < 0) { + abort("slurp failed to open '%s'", path); + } + if (fstat(fd, &fst) < 0) { + abort("slurp failed to stat '%s'", path); + } + data = malloc(fst.st_size+1); + sz = read(fd, data, fst.st_size); + if (sz < fst.st_size) { + abort("slurp failed to read '%s'", path); + } + data[sz] = '\0'; + return data; +} + +void init_repl_env() { + void _ref(char *name, MalVal*(*func)(MalVal*), int arg_cnt) { + void *(*f)(void *) = (void*(*)(void*))func; + env_set(repl_env, name, malval_new_function(f, arg_cnt, NULL)); + } + repl_env = new_env(NULL, NULL, NULL); + + int i; + for(i=0; i< (sizeof(types_ns) / sizeof(types_ns[0])); i++) { + MalVal *(*f)(MalVal *) = (MalVal*(*)(MalVal*))types_ns[i].func; + _ref(types_ns[i].name, f, types_ns[i].arg_cnt); + } + + MalVal *read_string(MalVal *str) { + assert_type(str, MAL_STRING, "read_string of non-string"); + return read_str(str->val.string); + } + _ref("read-string", read_string, 1); + + MalVal *do_eval(MalVal *ast) { + return EVAL(ast, repl_env); + } + _ref("eval", do_eval, 1); + + MalVal *slurp(MalVal *path) { + assert_type(path, MAL_STRING, "slurp of non-string"); + char *data = slurp_raw(path->val.string); + if (!data || mal_error) { return NULL; } + return malval_new_string(data); + } + _ref("slurp", slurp, 1); + + MalVal *slurp_do(MalVal *path) { + assert_type(path, MAL_STRING, "slurp of non-string"); + char *data = slurp_raw(path->val.string), + *wrapped_data; + if (!data || mal_error) { return NULL; } + wrapped_data = g_strdup_printf("(do %s)", data); + free(data); + return malval_new_string(wrapped_data); + } + _ref("slurp-do", slurp_do, 1); + + RE(repl_env, "", "(def! not (fn* (a) (if a false true)))"); + RE(repl_env, "", + "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); +} + +int main(int argc, char *argv[]) +{ + MalVal *exp; + char *output; + char prompt[100]; + + // Set the initial prompt and environment + snprintf(prompt, sizeof(prompt), "user> "); + init_repl_env(); + + if (argc > 1) { + char *cmd = g_strdup_printf("(load-file \"%s\")", argv[1]); + RE(repl_env, "", cmd); + } else { + // REPL loop + for(;;) { + exp = RE(repl_env, prompt, NULL); + if (mal_error && strcmp("EOF", mal_error->val.string) == 0) { + return 0; + } + output = PRINT(exp); + + if (output) { + g_print("%s\n", output); + free(output); // Free output string + } + + //malval_free(exp); // Free evaluated expression + } + } +} diff --git a/c/step7_quote.c b/c/step7_quote.c new file mode 100644 index 0000000..46ac6a9 --- /dev/null +++ b/c/step7_quote.c @@ -0,0 +1,318 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> +#include "types.h" +#include "readline.h" +#include "reader.h" +#include "interop.h" + +// Declarations +MalVal *EVAL(MalVal *ast, Env *env); + +// read +MalVal *READ(char prompt[], char *str) { + char *line; + MalVal *ast; + if (str) { + line = str; + } else { + line = _readline(prompt); + if (!line) { + _error("EOF"); + return NULL; + } + } + ast = read_str(line); + if (!str) { free(line); } + return ast; +} + +// eval +int is_pair(MalVal *x) { + return _sequential_Q(x) && (_count(x) > 0); +} + +MalVal *quasiquote(MalVal *ast) { + if (!is_pair(ast)) { + return _list(2, malval_new_symbol("quote"), ast); + } else { + MalVal *a0 = _nth(ast, 0); + if ((a0->type & MAL_SYMBOL) && + strcmp("unquote", a0->val.string) == 0) { + return _nth(ast, 1); + } else if (is_pair(a0)) { + MalVal *a00 = _nth(a0, 0); + if ((a00->type & MAL_SYMBOL) && + strcmp("splice-unquote", a00->val.string) == 0) { + return _list(3, malval_new_symbol("concat"), + _nth(a0, 1), + quasiquote(rest(ast))); + } + } + return _list(3, malval_new_symbol("cons"), + quasiquote(a0), + quasiquote(rest(ast))); + } +} + +MalVal *eval_ast(MalVal *ast, Env *env) { + if (!ast || mal_error) return NULL; + if (ast->type == MAL_SYMBOL) { + //g_print("EVAL symbol: %s\n", ast->val.string); + return env_get(env, ast->val.string); + } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) { + //g_print("EVAL sequential: %s\n", _pr_str(ast,1)); + MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env); + if (!el || mal_error) return NULL; + el->type = ast->type; + return el; + } else if (ast->type == MAL_HASH_MAP) { + //g_print("EVAL hash_map: %s\n", _pr_str(ast,1)); + GHashTableIter iter; + gpointer key, value; + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + _count(ast))); + g_hash_table_iter_init (&iter, ast->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + MalVal *kname = malval_new_string((char *)key); + g_array_append_val(seq->val.array, kname); + MalVal *new_val = EVAL((MalVal *)value, env); + g_array_append_val(seq->val.array, new_val); + } + return hash_map(seq); + } else { + //g_print("EVAL scalar: %s\n", _pr_str(ast,1)); + return ast; + } +} + +MalVal *EVAL(MalVal *ast, Env *env) { + while (TRUE) { + //g_print("EVAL: %s\n", _pr_str(ast,1)); + if (!ast || mal_error) return NULL; + if (ast->type != MAL_LIST) { + return eval_ast(ast, env); + } + if (!ast || mal_error) return NULL; + + // apply list + //g_print("EVAL apply list: %s\n", _pr_str(ast,1)); + int i, len; + if (_count(ast) == 0) { return ast; } + MalVal *a0 = _nth(ast, 0); + if ((a0->type & MAL_SYMBOL) && + strcmp("def!", a0->val.string) == 0) { + //g_print("eval apply def!\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2); + MalVal *res = EVAL(a2, env); + env_set(env, a1->val.string, res); + return res; + } else if ((a0->type & MAL_SYMBOL) && + strcmp("let*", a0->val.string) == 0) { + //g_print("eval apply let*\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2), + *key, *val; + assert_type(a1, MAL_LIST|MAL_VECTOR, + "let* bindings must be list or vector"); + len = _count(a1); + assert((len % 2) == 0, "odd number of let* bindings forms"); + Env *let_env = new_env(env, NULL, NULL); + for(i=0; i<len; i+=2) { + key = g_array_index(a1->val.array, MalVal*, i); + val = g_array_index(a1->val.array, MalVal*, i+1); + assert_type(key, MAL_SYMBOL, "let* bind to non-symbol"); + env_set(let_env, key->val.string, EVAL(val, let_env)); + } + return EVAL(a2, let_env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("quote", a0->val.string) == 0) { + //g_print("eval apply quote\n"); + return _nth(ast, 1); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("quasiquote", a0->val.string) == 0) { + //g_print("eval apply quasiquote\n"); + MalVal *a1 = _nth(ast, 1); + return EVAL(quasiquote(a1), env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("do", a0->val.string) == 0) { + //g_print("eval apply do\n"); + eval_ast(_slice(ast, 1, _count(ast)-1), env); + ast = last(ast); + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("if", a0->val.string) == 0) { + //g_print("eval apply if\n"); + MalVal *a1 = _nth(ast, 1); + MalVal *cond = EVAL(a1, env); + if (!cond || mal_error) return NULL; + if (cond->type & (MAL_FALSE|MAL_NIL)) { + // eval false slot form + ast = _nth(ast, 3); + if (!ast) { + return &mal_nil; + } + } else { + // eval true slot form + ast = _nth(ast, 2); + } + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("fn*", a0->val.string) == 0) { + //g_print("eval apply fn*\n"); + MalVal *mf = malval_new(MAL_FUNCTION_MAL, NULL); + mf->val.func.evaluator = EVAL; + mf->val.func.args = _nth(ast, 1); + mf->val.func.body = _nth(ast, 2); + mf->val.func.env = env; + return mf; + } else { + //g_print("eval apply\n"); + MalVal *el = eval_ast(ast, env); + if (!el || mal_error) { return NULL; } + MalVal *f = first(el), + *args = rest(el); + assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL, + "cannot invoke '%s'", _pr_str(f,1)); + if (f->type & MAL_FUNCTION_MAL) { + ast = f->val.func.body; + env = new_env(f->val.func.env, f->val.func.args, args); + // Continue loop + } else { + return apply(f, args); + } + } + } +} + +// print +char *PRINT(MalVal *exp) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", mal_error->val.string); + malval_free(mal_error); + mal_error = NULL; + return NULL; + } + return _pr_str(exp,1); +} + +// repl + +// read and eval +MalVal *RE(Env *env, char *prompt, char *str) { + MalVal *ast, *exp; + ast = READ(prompt, str); + if (!ast || mal_error) return NULL; + exp = EVAL(ast, env); + if (ast != exp) { + malval_free(ast); // Free input structure + } + return exp; +} + +// Setup the initial REPL environment +Env *repl_env; + +char *slurp_raw(char *path) { + char *data; + struct stat fst; + int fd = open(path, O_RDONLY), + sz; + if (fd < 0) { + abort("slurp failed to open '%s'", path); + } + if (fstat(fd, &fst) < 0) { + abort("slurp failed to stat '%s'", path); + } + data = malloc(fst.st_size+1); + sz = read(fd, data, fst.st_size); + if (sz < fst.st_size) { + abort("slurp failed to read '%s'", path); + } + data[sz] = '\0'; + return data; +} + +void init_repl_env() { + void _ref(char *name, MalVal*(*func)(MalVal*), int arg_cnt) { + void *(*f)(void *) = (void*(*)(void*))func; + env_set(repl_env, name, malval_new_function(f, arg_cnt, NULL)); + } + repl_env = new_env(NULL, NULL, NULL); + + int i; + for(i=0; i< (sizeof(types_ns) / sizeof(types_ns[0])); i++) { + MalVal *(*f)(MalVal *) = (MalVal*(*)(MalVal*))types_ns[i].func; + _ref(types_ns[i].name, f, types_ns[i].arg_cnt); + } + + MalVal *read_string(MalVal *str) { + assert_type(str, MAL_STRING, "read_string of non-string"); + return read_str(str->val.string); + } + _ref("read-string", read_string, 1); + + MalVal *do_eval(MalVal *ast) { + return EVAL(ast, repl_env); + } + _ref("eval", do_eval, 1); + + MalVal *slurp(MalVal *path) { + assert_type(path, MAL_STRING, "slurp of non-string"); + char *data = slurp_raw(path->val.string); + if (!data || mal_error) { return NULL; } + return malval_new_string(data); + } + _ref("slurp", slurp, 1); + + MalVal *slurp_do(MalVal *path) { + assert_type(path, MAL_STRING, "slurp of non-string"); + char *data = slurp_raw(path->val.string), + *wrapped_data; + if (!data || mal_error) { return NULL; } + wrapped_data = g_strdup_printf("(do %s)", data); + free(data); + return malval_new_string(wrapped_data); + } + _ref("slurp-do", slurp_do, 1); + + RE(repl_env, "", "(def! not (fn* (a) (if a false true)))"); + RE(repl_env, "", + "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); +} + +int main(int argc, char *argv[]) +{ + MalVal *exp; + char *output; + char prompt[100]; + + // Set the initial prompt and environment + snprintf(prompt, sizeof(prompt), "user> "); + init_repl_env(); + + if (argc > 1) { + char *cmd = g_strdup_printf("(load-file \"%s\")", argv[1]); + RE(repl_env, "", cmd); + } else { + // REPL loop + for(;;) { + exp = RE(repl_env, prompt, NULL); + if (mal_error && strcmp("EOF", mal_error->val.string) == 0) { + return 0; + } + output = PRINT(exp); + + if (output) { + g_print("%s\n", output); + free(output); // Free output string + } + + //malval_free(exp); // Free evaluated expression + } + } +} diff --git a/c/step8_macros.c b/c/step8_macros.c new file mode 100644 index 0000000..23afc33 --- /dev/null +++ b/c/step8_macros.c @@ -0,0 +1,357 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> +#include "types.h" +#include "readline.h" +#include "reader.h" +#include "interop.h" + +// Declarations +MalVal *EVAL(MalVal *ast, Env *env); +MalVal *macroexpand(MalVal *ast, Env *env); + +// read +MalVal *READ(char prompt[], char *str) { + char *line; + MalVal *ast; + if (str) { + line = str; + } else { + line = _readline(prompt); + if (!line) { + _error("EOF"); + return NULL; + } + } + ast = read_str(line); + if (!str) { free(line); } + return ast; +} + +// eval +int is_pair(MalVal *x) { + return _sequential_Q(x) && (_count(x) > 0); +} + +MalVal *quasiquote(MalVal *ast) { + if (!is_pair(ast)) { + return _list(2, malval_new_symbol("quote"), ast); + } else { + MalVal *a0 = _nth(ast, 0); + if ((a0->type & MAL_SYMBOL) && + strcmp("unquote", a0->val.string) == 0) { + return _nth(ast, 1); + } else if (is_pair(a0)) { + MalVal *a00 = _nth(a0, 0); + if ((a00->type & MAL_SYMBOL) && + strcmp("splice-unquote", a00->val.string) == 0) { + return _list(3, malval_new_symbol("concat"), + _nth(a0, 1), + quasiquote(rest(ast))); + } + } + return _list(3, malval_new_symbol("cons"), + quasiquote(a0), + quasiquote(rest(ast))); + } +} + +int is_macro_call(MalVal *ast, Env *env) { + if (!ast || ast->type != MAL_LIST) { return 0; } + MalVal *a0 = _nth(ast, 0); + return (a0->type & MAL_SYMBOL) && + env_find(env, a0->val.string) && + env_get(env, a0->val.string)->ismacro; +} + +MalVal *macroexpand(MalVal *ast, Env *env) { + if (!ast || mal_error) return NULL; + while (is_macro_call(ast, env)) { + MalVal *a0 = _nth(ast, 0); + MalVal *mac = env_get(env, a0->val.string); + // TODO: this is weird and limits it to 20. FIXME + ast = apply(mac, rest(ast)); + } + return ast; +} + +MalVal *eval_ast(MalVal *ast, Env *env) { + if (!ast || mal_error) return NULL; + if (ast->type == MAL_SYMBOL) { + //g_print("EVAL symbol: %s\n", ast->val.string); + return env_get(env, ast->val.string); + } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) { + //g_print("EVAL sequential: %s\n", _pr_str(ast,1)); + MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env); + if (!el || mal_error) return NULL; + el->type = ast->type; + return el; + } else if (ast->type == MAL_HASH_MAP) { + //g_print("EVAL hash_map: %s\n", _pr_str(ast,1)); + GHashTableIter iter; + gpointer key, value; + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + _count(ast))); + g_hash_table_iter_init (&iter, ast->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + MalVal *kname = malval_new_string((char *)key); + g_array_append_val(seq->val.array, kname); + MalVal *new_val = EVAL((MalVal *)value, env); + g_array_append_val(seq->val.array, new_val); + } + return hash_map(seq); + } else { + //g_print("EVAL scalar: %s\n", _pr_str(ast,1)); + return ast; + } +} + +MalVal *EVAL(MalVal *ast, Env *env) { + while (TRUE) { + if (!ast || mal_error) return NULL; + //g_print("EVAL: %s\n", _pr_str(ast,1)); + if (ast->type != MAL_LIST) { + return eval_ast(ast, env); + } + if (!ast || mal_error) return NULL; + + // apply list + //g_print("EVAL apply list: %s\n", _pr_str(ast,1)); + ast = macroexpand(ast, env); + if (!ast || mal_error) return NULL; + if (ast->type != MAL_LIST) { return ast; } + if (_count(ast) == 0) { return ast; } + + int i, len; + MalVal *a0 = _nth(ast, 0); + if ((a0->type & MAL_SYMBOL) && + strcmp("def!", a0->val.string) == 0) { + //g_print("eval apply def!\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2); + MalVal *res = EVAL(a2, env); + env_set(env, a1->val.string, res); + return res; + } else if ((a0->type & MAL_SYMBOL) && + strcmp("let*", a0->val.string) == 0) { + //g_print("eval apply let*\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2), + *key, *val; + assert_type(a1, MAL_LIST|MAL_VECTOR, + "let* bindings must be list or vector"); + len = _count(a1); + assert((len % 2) == 0, "odd number of let* bindings forms"); + Env *let_env = new_env(env, NULL, NULL); + for(i=0; i<len; i+=2) { + key = g_array_index(a1->val.array, MalVal*, i); + val = g_array_index(a1->val.array, MalVal*, i+1); + assert_type(key, MAL_SYMBOL, "let* bind to non-symbol"); + env_set(let_env, key->val.string, EVAL(val, let_env)); + } + return EVAL(a2, let_env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("quote", a0->val.string) == 0) { + //g_print("eval apply quote\n"); + return _nth(ast, 1); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("quasiquote", a0->val.string) == 0) { + //g_print("eval apply quasiquote\n"); + MalVal *a1 = _nth(ast, 1); + return EVAL(quasiquote(a1), env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("defmacro!", a0->val.string) == 0) { + //g_print("eval apply defmacro!\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2); + MalVal *res = EVAL(a2, env); + res->ismacro = TRUE; + env_set(env, a1->val.string, res); + return res; + } else if ((a0->type & MAL_SYMBOL) && + strcmp("macroexpand", a0->val.string) == 0) { + //g_print("eval apply macroexpand\n"); + MalVal *a1 = _nth(ast, 1); + return macroexpand(a1, env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("do", a0->val.string) == 0) { + //g_print("eval apply do\n"); + eval_ast(_slice(ast, 1, _count(ast)-1), env); + ast = last(ast); + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("if", a0->val.string) == 0) { + //g_print("eval apply if\n"); + MalVal *a1 = _nth(ast, 1); + MalVal *cond = EVAL(a1, env); + if (!cond || mal_error) return NULL; + if (cond->type & (MAL_FALSE|MAL_NIL)) { + // eval false slot form + ast = _nth(ast, 3); + if (!ast) { + return &mal_nil; + } + } else { + // eval true slot form + ast = _nth(ast, 2); + } + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("fn*", a0->val.string) == 0) { + //g_print("eval apply fn*\n"); + MalVal *mf = malval_new(MAL_FUNCTION_MAL, NULL); + mf->ismacro = FALSE; + mf->val.func.evaluator = EVAL; + mf->val.func.args = _nth(ast, 1); + mf->val.func.body = _nth(ast, 2); + mf->val.func.env = env; + return mf; + } else { + //g_print("eval apply\n"); + MalVal *el = eval_ast(ast, env); + if (!el || mal_error) { return NULL; } + MalVal *f = first(el), + *args = rest(el); + assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL, + "cannot invoke '%s'", _pr_str(f,1)); + if (f->type & MAL_FUNCTION_MAL) { + ast = f->val.func.body; + env = new_env(f->val.func.env, f->val.func.args, args); + // Continue loop + } else { + return apply(f, args); + } + } + } +} + +// print +char *PRINT(MalVal *exp) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", mal_error->val.string); + malval_free(mal_error); + mal_error = NULL; + return NULL; + } + return _pr_str(exp,1); +} + +// repl + +// read and eval +MalVal *RE(Env *env, char *prompt, char *str) { + MalVal *ast, *exp; + ast = READ(prompt, str); + if (!ast || mal_error) return NULL; + exp = EVAL(ast, env); + if (ast != exp) { + malval_free(ast); // Free input structure + } + return exp; +} + +// Setup the initial REPL environment +Env *repl_env; + +char *slurp_raw(char *path) { + char *data; + struct stat fst; + int fd = open(path, O_RDONLY), + sz; + if (fd < 0) { + abort("slurp failed to open '%s'", path); + } + if (fstat(fd, &fst) < 0) { + abort("slurp failed to stat '%s'", path); + } + data = malloc(fst.st_size+1); + sz = read(fd, data, fst.st_size); + if (sz < fst.st_size) { + abort("slurp failed to read '%s'", path); + } + data[sz] = '\0'; + return data; +} + +void init_repl_env() { + void _ref(char *name, MalVal*(*func)(MalVal*), int arg_cnt) { + void *(*f)(void *) = (void*(*)(void*))func; + env_set(repl_env, name, malval_new_function(f, arg_cnt, NULL)); + } + repl_env = new_env(NULL, NULL, NULL); + + int i; + for(i=0; i< (sizeof(types_ns) / sizeof(types_ns[0])); i++) { + MalVal *(*f)(MalVal *) = (MalVal*(*)(MalVal*))types_ns[i].func; + _ref(types_ns[i].name, f, types_ns[i].arg_cnt); + } + + MalVal *read_string(MalVal *str) { + assert_type(str, MAL_STRING, "read_string of non-string"); + return read_str(str->val.string); + } + _ref("read-string", read_string, 1); + + MalVal *do_eval(MalVal *ast) { + return EVAL(ast, repl_env); + } + _ref("eval", do_eval, 1); + + MalVal *slurp(MalVal *path) { + assert_type(path, MAL_STRING, "slurp of non-string"); + char *data = slurp_raw(path->val.string); + if (!data || mal_error) { return NULL; } + return malval_new_string(data); + } + _ref("slurp", slurp, 1); + + MalVal *slurp_do(MalVal *path) { + assert_type(path, MAL_STRING, "slurp of non-string"); + char *data = slurp_raw(path->val.string), + *wrapped_data; + if (!data || mal_error) { return NULL; } + wrapped_data = g_strdup_printf("(do %s)", data); + free(data); + return malval_new_string(wrapped_data); + } + _ref("slurp-do", slurp_do, 1); + + RE(repl_env, "", "(def! not (fn* (a) (if a false true)))"); + RE(repl_env, "", + "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); +} + +int main(int argc, char *argv[]) +{ + MalVal *exp; + char *output; + char prompt[100]; + + // Set the initial prompt and environment + snprintf(prompt, sizeof(prompt), "user> "); + init_repl_env(); + + if (argc > 1) { + char *cmd = g_strdup_printf("(load-file \"%s\")", argv[1]); + RE(repl_env, "", cmd); + } else { + // REPL loop + for(;;) { + exp = RE(repl_env, prompt, NULL); + if (mal_error && strcmp("EOF", mal_error->val.string) == 0) { + return 0; + } + output = PRINT(exp); + + if (output) { + g_print("%s\n", output); + free(output); // Free output string + } + + //malval_free(exp); // Free evaluated expression + } + } +} diff --git a/c/step9_interop.c b/c/step9_interop.c new file mode 100644 index 0000000..2a98dd8 --- /dev/null +++ b/c/step9_interop.c @@ -0,0 +1,362 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> +#include "types.h" +#include "readline.h" +#include "reader.h" +#include "interop.h" + +// Declarations +MalVal *EVAL(MalVal *ast, Env *env); +MalVal *macroexpand(MalVal *ast, Env *env); + +// read +MalVal *READ(char prompt[], char *str) { + char *line; + MalVal *ast; + if (str) { + line = str; + } else { + line = _readline(prompt); + if (!line) { + _error("EOF"); + return NULL; + } + } + ast = read_str(line); + if (!str) { free(line); } + return ast; +} + +// eval +int is_pair(MalVal *x) { + return _sequential_Q(x) && (_count(x) > 0); +} + +MalVal *quasiquote(MalVal *ast) { + if (!is_pair(ast)) { + return _list(2, malval_new_symbol("quote"), ast); + } else { + MalVal *a0 = _nth(ast, 0); + if ((a0->type & MAL_SYMBOL) && + strcmp("unquote", a0->val.string) == 0) { + return _nth(ast, 1); + } else if (is_pair(a0)) { + MalVal *a00 = _nth(a0, 0); + if ((a00->type & MAL_SYMBOL) && + strcmp("splice-unquote", a00->val.string) == 0) { + return _list(3, malval_new_symbol("concat"), + _nth(a0, 1), + quasiquote(rest(ast))); + } + } + return _list(3, malval_new_symbol("cons"), + quasiquote(a0), + quasiquote(rest(ast))); + } +} + +int is_macro_call(MalVal *ast, Env *env) { + if (!ast || ast->type != MAL_LIST) { return 0; } + MalVal *a0 = _nth(ast, 0); + return (a0->type & MAL_SYMBOL) && + env_find(env, a0->val.string) && + env_get(env, a0->val.string)->ismacro; +} + +MalVal *macroexpand(MalVal *ast, Env *env) { + if (!ast || mal_error) return NULL; + while (is_macro_call(ast, env)) { + MalVal *a0 = _nth(ast, 0); + MalVal *mac = env_get(env, a0->val.string); + // TODO: this is weird and limits it to 20. FIXME + ast = apply(mac, rest(ast)); + } + return ast; +} + +MalVal *eval_ast(MalVal *ast, Env *env) { + if (!ast || mal_error) return NULL; + if (ast->type == MAL_SYMBOL) { + //g_print("EVAL symbol: %s\n", ast->val.string); + return env_get(env, ast->val.string); + } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) { + //g_print("EVAL sequential: %s\n", _pr_str(ast,1)); + MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env); + if (!el || mal_error) return NULL; + el->type = ast->type; + return el; + } else if (ast->type == MAL_HASH_MAP) { + //g_print("EVAL hash_map: %s\n", _pr_str(ast,1)); + GHashTableIter iter; + gpointer key, value; + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + _count(ast))); + g_hash_table_iter_init (&iter, ast->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + MalVal *kname = malval_new_string((char *)key); + g_array_append_val(seq->val.array, kname); + MalVal *new_val = EVAL((MalVal *)value, env); + g_array_append_val(seq->val.array, new_val); + } + return hash_map(seq); + } else { + //g_print("EVAL scalar: %s\n", _pr_str(ast,1)); + return ast; + } +} + +MalVal *EVAL(MalVal *ast, Env *env) { + while (TRUE) { + if (!ast || mal_error) return NULL; + //g_print("EVAL: %s\n", _pr_str(ast,1)); + if (ast->type != MAL_LIST) { + return eval_ast(ast, env); + } + if (!ast || mal_error) return NULL; + + // apply list + //g_print("EVAL apply list: %s\n", _pr_str(ast,1)); + ast = macroexpand(ast, env); + if (!ast || mal_error) return NULL; + if (ast->type != MAL_LIST) { return ast; } + if (_count(ast) == 0) { return ast; } + + int i, len; + MalVal *a0 = _nth(ast, 0); + if ((a0->type & MAL_SYMBOL) && + strcmp("def!", a0->val.string) == 0) { + //g_print("eval apply def!\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2); + MalVal *res = EVAL(a2, env); + env_set(env, a1->val.string, res); + return res; + } else if ((a0->type & MAL_SYMBOL) && + strcmp("let*", a0->val.string) == 0) { + //g_print("eval apply let*\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2), + *key, *val; + assert_type(a1, MAL_LIST|MAL_VECTOR, + "let* bindings must be list or vector"); + len = _count(a1); + assert((len % 2) == 0, "odd number of let* bindings forms"); + Env *let_env = new_env(env, NULL, NULL); + for(i=0; i<len; i+=2) { + key = g_array_index(a1->val.array, MalVal*, i); + val = g_array_index(a1->val.array, MalVal*, i+1); + assert_type(key, MAL_SYMBOL, "let* bind to non-symbol"); + env_set(let_env, key->val.string, EVAL(val, let_env)); + } + return EVAL(a2, let_env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("quote", a0->val.string) == 0) { + //g_print("eval apply quote\n"); + return _nth(ast, 1); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("quasiquote", a0->val.string) == 0) { + //g_print("eval apply quasiquote\n"); + MalVal *a1 = _nth(ast, 1); + return EVAL(quasiquote(a1), env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("defmacro!", a0->val.string) == 0) { + //g_print("eval apply defmacro!\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2); + MalVal *res = EVAL(a2, env); + res->ismacro = TRUE; + env_set(env, a1->val.string, res); + return res; + } else if ((a0->type & MAL_SYMBOL) && + strcmp("macroexpand", a0->val.string) == 0) { + //g_print("eval apply macroexpand\n"); + MalVal *a1 = _nth(ast, 1); + return macroexpand(a1, env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp(".", a0->val.string) == 0) { + //g_print("eval apply .\n"); + MalVal *el = eval_ast(_slice(ast, 1, _count(ast)), env); + return invoke_native(el); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("do", a0->val.string) == 0) { + //g_print("eval apply do\n"); + eval_ast(_slice(ast, 1, _count(ast)-1), env); + ast = last(ast); + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("if", a0->val.string) == 0) { + //g_print("eval apply if\n"); + MalVal *a1 = _nth(ast, 1); + MalVal *cond = EVAL(a1, env); + if (!cond || mal_error) return NULL; + if (cond->type & (MAL_FALSE|MAL_NIL)) { + // eval false slot form + ast = _nth(ast, 3); + if (!ast) { + return &mal_nil; + } + } else { + // eval true slot form + ast = _nth(ast, 2); + } + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("fn*", a0->val.string) == 0) { + //g_print("eval apply fn*\n"); + MalVal *mf = malval_new(MAL_FUNCTION_MAL, NULL); + mf->ismacro = FALSE; + mf->val.func.evaluator = EVAL; + mf->val.func.args = _nth(ast, 1); + mf->val.func.body = _nth(ast, 2); + mf->val.func.env = env; + return mf; + } else { + //g_print("eval apply\n"); + MalVal *el = eval_ast(ast, env); + if (!el || mal_error) { return NULL; } + MalVal *f = first(el), + *args = rest(el); + assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL, + "cannot invoke '%s'", _pr_str(f,1)); + if (f->type & MAL_FUNCTION_MAL) { + ast = f->val.func.body; + env = new_env(f->val.func.env, f->val.func.args, args); + // Continue loop + } else { + return apply(f, args); + } + } + } +} + +// print +char *PRINT(MalVal *exp) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", mal_error->val.string); + malval_free(mal_error); + mal_error = NULL; + return NULL; + } + return _pr_str(exp,1); +} + +// repl + +// read and eval +MalVal *RE(Env *env, char *prompt, char *str) { + MalVal *ast, *exp; + ast = READ(prompt, str); + if (!ast || mal_error) return NULL; + exp = EVAL(ast, env); + if (ast != exp) { + malval_free(ast); // Free input structure + } + return exp; +} + +// Setup the initial REPL environment +Env *repl_env; + +char *slurp_raw(char *path) { + char *data; + struct stat fst; + int fd = open(path, O_RDONLY), + sz; + if (fd < 0) { + abort("slurp failed to open '%s'", path); + } + if (fstat(fd, &fst) < 0) { + abort("slurp failed to stat '%s'", path); + } + data = malloc(fst.st_size+1); + sz = read(fd, data, fst.st_size); + if (sz < fst.st_size) { + abort("slurp failed to read '%s'", path); + } + data[sz] = '\0'; + return data; +} + +void init_repl_env() { + void _ref(char *name, MalVal*(*func)(MalVal*), int arg_cnt) { + void *(*f)(void *) = (void*(*)(void*))func; + env_set(repl_env, name, malval_new_function(f, arg_cnt, NULL)); + } + repl_env = new_env(NULL, NULL, NULL); + + int i; + for(i=0; i< (sizeof(types_ns) / sizeof(types_ns[0])); i++) { + MalVal *(*f)(MalVal *) = (MalVal*(*)(MalVal*))types_ns[i].func; + _ref(types_ns[i].name, f, types_ns[i].arg_cnt); + } + + MalVal *read_string(MalVal *str) { + assert_type(str, MAL_STRING, "read_string of non-string"); + return read_str(str->val.string); + } + _ref("read-string", read_string, 1); + + MalVal *do_eval(MalVal *ast) { + return EVAL(ast, repl_env); + } + _ref("eval", do_eval, 1); + + MalVal *slurp(MalVal *path) { + assert_type(path, MAL_STRING, "slurp of non-string"); + char *data = slurp_raw(path->val.string); + if (!data || mal_error) { return NULL; } + return malval_new_string(data); + } + _ref("slurp", slurp, 1); + + MalVal *slurp_do(MalVal *path) { + assert_type(path, MAL_STRING, "slurp of non-string"); + char *data = slurp_raw(path->val.string), + *wrapped_data; + if (!data || mal_error) { return NULL; } + wrapped_data = g_strdup_printf("(do %s)", data); + free(data); + return malval_new_string(wrapped_data); + } + _ref("slurp-do", slurp_do, 1); + + RE(repl_env, "", "(def! not (fn* (a) (if a false true)))"); + RE(repl_env, "", + "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); +} + +int main(int argc, char *argv[]) +{ + MalVal *exp; + char *output; + char prompt[100]; + + // Set the initial prompt and environment + snprintf(prompt, sizeof(prompt), "user> "); + init_repl_env(); + + if (argc > 1) { + char *cmd = g_strdup_printf("(load-file \"%s\")", argv[1]); + RE(repl_env, "", cmd); + } else { + // REPL loop + for(;;) { + exp = RE(repl_env, prompt, NULL); + if (mal_error && strcmp("EOF", mal_error->val.string) == 0) { + return 0; + } + output = PRINT(exp); + + if (output) { + g_print("%s\n", output); + free(output); // Free output string + } + + //malval_free(exp); // Free evaluated expression + } + } +} diff --git a/c/stepA_more.c b/c/stepA_more.c new file mode 100644 index 0000000..037848a --- /dev/null +++ b/c/stepA_more.c @@ -0,0 +1,393 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> +#include "types.h" +#include "readline.h" +#include "reader.h" +#include "interop.h" + +// Declarations +MalVal *EVAL(MalVal *ast, Env *env); +MalVal *macroexpand(MalVal *ast, Env *env); + +// read +MalVal *READ(char prompt[], char *str) { + char *line; + MalVal *ast; + if (str) { + line = str; + } else { + line = _readline(prompt); + if (!line) { + _error("EOF"); + return NULL; + } + } + ast = read_str(line); + if (!str) { free(line); } + return ast; +} + +// eval +int is_pair(MalVal *x) { + return _sequential_Q(x) && (_count(x) > 0); +} + +MalVal *quasiquote(MalVal *ast) { + if (!is_pair(ast)) { + return _list(2, malval_new_symbol("quote"), ast); + } else { + MalVal *a0 = _nth(ast, 0); + if ((a0->type & MAL_SYMBOL) && + strcmp("unquote", a0->val.string) == 0) { + return _nth(ast, 1); + } else if (is_pair(a0)) { + MalVal *a00 = _nth(a0, 0); + if ((a00->type & MAL_SYMBOL) && + strcmp("splice-unquote", a00->val.string) == 0) { + return _list(3, malval_new_symbol("concat"), + _nth(a0, 1), + quasiquote(rest(ast))); + } + } + return _list(3, malval_new_symbol("cons"), + quasiquote(a0), + quasiquote(rest(ast))); + } +} + +int is_macro_call(MalVal *ast, Env *env) { + if (!ast || ast->type != MAL_LIST) { return 0; } + MalVal *a0 = _nth(ast, 0); + return (a0->type & MAL_SYMBOL) && + env_find(env, a0->val.string) && + env_get(env, a0->val.string)->ismacro; +} + +MalVal *macroexpand(MalVal *ast, Env *env) { + if (!ast || mal_error) return NULL; + while (is_macro_call(ast, env)) { + MalVal *a0 = _nth(ast, 0); + MalVal *mac = env_get(env, a0->val.string); + // TODO: this is weird and limits it to 20. FIXME + ast = apply(mac, rest(ast)); + } + return ast; +} + +MalVal *eval_ast(MalVal *ast, Env *env) { + if (!ast || mal_error) return NULL; + if (ast->type == MAL_SYMBOL) { + //g_print("EVAL symbol: %s\n", ast->val.string); + return env_get(env, ast->val.string); + } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) { + //g_print("EVAL sequential: %s\n", _pr_str(ast,1)); + MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env); + if (!el || mal_error) return NULL; + el->type = ast->type; + return el; + } else if (ast->type == MAL_HASH_MAP) { + //g_print("EVAL hash_map: %s\n", _pr_str(ast,1)); + GHashTableIter iter; + gpointer key, value; + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + _count(ast))); + g_hash_table_iter_init (&iter, ast->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + MalVal *kname = malval_new_string((char *)key); + g_array_append_val(seq->val.array, kname); + MalVal *new_val = EVAL((MalVal *)value, env); + g_array_append_val(seq->val.array, new_val); + } + return hash_map(seq); + } else { + //g_print("EVAL scalar: %s\n", _pr_str(ast,1)); + return ast; + } +} + +MalVal *EVAL(MalVal *ast, Env *env) { + while (TRUE) { + if (!ast || mal_error) return NULL; + //g_print("EVAL: %s\n", _pr_str(ast,1)); + if (ast->type != MAL_LIST) { + return eval_ast(ast, env); + } + if (!ast || mal_error) return NULL; + + // apply list + //g_print("EVAL apply list: %s\n", _pr_str(ast,1)); + ast = macroexpand(ast, env); + if (!ast || mal_error) return NULL; + if (ast->type != MAL_LIST) { return ast; } + if (_count(ast) == 0) { return ast; } + + int i, len; + MalVal *a0 = _nth(ast, 0); + if ((a0->type & MAL_SYMBOL) && + strcmp("def!", a0->val.string) == 0) { + //g_print("eval apply def!\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2); + MalVal *res = EVAL(a2, env); + env_set(env, a1->val.string, res); + return res; + } else if ((a0->type & MAL_SYMBOL) && + strcmp("let*", a0->val.string) == 0) { + //g_print("eval apply let*\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2), + *key, *val; + assert_type(a1, MAL_LIST|MAL_VECTOR, + "let* bindings must be list or vector"); + len = _count(a1); + assert((len % 2) == 0, "odd number of let* bindings forms"); + Env *let_env = new_env(env, NULL, NULL); + for(i=0; i<len; i+=2) { + key = g_array_index(a1->val.array, MalVal*, i); + val = g_array_index(a1->val.array, MalVal*, i+1); + assert_type(key, MAL_SYMBOL, "let* bind to non-symbol"); + env_set(let_env, key->val.string, EVAL(val, let_env)); + } + return EVAL(a2, let_env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("quote", a0->val.string) == 0) { + //g_print("eval apply quote\n"); + return _nth(ast, 1); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("quasiquote", a0->val.string) == 0) { + //g_print("eval apply quasiquote\n"); + MalVal *a1 = _nth(ast, 1); + return EVAL(quasiquote(a1), env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("defmacro!", a0->val.string) == 0) { + //g_print("eval apply defmacro!\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2); + MalVal *res = EVAL(a2, env); + res->ismacro = TRUE; + env_set(env, a1->val.string, res); + return res; + } else if ((a0->type & MAL_SYMBOL) && + strcmp("macroexpand", a0->val.string) == 0) { + //g_print("eval apply macroexpand\n"); + MalVal *a1 = _nth(ast, 1); + return macroexpand(a1, env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp(".", a0->val.string) == 0) { + //g_print("eval apply .\n"); + MalVal *el = eval_ast(_slice(ast, 1, _count(ast)), env); + return invoke_native(el); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("try*", a0->val.string) == 0) { + //g_print("eval apply try*\n"); + MalVal *a1 = _nth(ast, 1); + MalVal *a2 = _nth(ast, 2); + MalVal *res = EVAL(a1, env); + if (!mal_error) { return res; } + MalVal *a20 = _nth(a2, 0); + if (strcmp("catch*", a20->val.string) == 0) { + MalVal *a21 = _nth(a2, 1); + MalVal *a22 = _nth(a2, 2); + Env *catch_env = new_env(env, + _list(1, a21), + _list(1, mal_error)); + //malval_free(mal_error); + mal_error = NULL; + res = EVAL(a22, catch_env); + return res; + } else { + return &mal_nil; + } + } else if ((a0->type & MAL_SYMBOL) && + strcmp("do", a0->val.string) == 0) { + //g_print("eval apply do\n"); + eval_ast(_slice(ast, 1, _count(ast)-1), env); + ast = last(ast); + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("if", a0->val.string) == 0) { + //g_print("eval apply if\n"); + MalVal *a1 = _nth(ast, 1); + MalVal *cond = EVAL(a1, env); + if (!cond || mal_error) return NULL; + if (cond->type & (MAL_FALSE|MAL_NIL)) { + // eval false slot form + ast = _nth(ast, 3); + if (!ast) { + return &mal_nil; + } + } else { + // eval true slot form + ast = _nth(ast, 2); + } + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("fn*", a0->val.string) == 0) { + //g_print("eval apply fn*\n"); + MalVal *mf = malval_new(MAL_FUNCTION_MAL, NULL); + mf->ismacro = FALSE; + mf->val.func.evaluator = EVAL; + mf->val.func.args = _nth(ast, 1); + mf->val.func.body = _nth(ast, 2); + mf->val.func.env = env; + return mf; + } else { + //g_print("eval apply\n"); + MalVal *el = eval_ast(ast, env); + if (!el || mal_error) { return NULL; } + MalVal *f = first(el), + *args = rest(el); + assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL, + "cannot invoke '%s'", _pr_str(f,1)); + if (f->type & MAL_FUNCTION_MAL) { + ast = f->val.func.body; + env = new_env(f->val.func.env, f->val.func.args, args); + // Continue loop + } else { + return apply(f, args); + } + } + } +} + +// print +char *PRINT(MalVal *exp) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", mal_error->val.string); + malval_free(mal_error); + mal_error = NULL; + return NULL; + } + return _pr_str(exp,1); +} + +// repl + +// read and eval +MalVal *RE(Env *env, char *prompt, char *str) { + MalVal *ast, *exp; + ast = READ(prompt, str); + if (!ast || mal_error) return NULL; + exp = EVAL(ast, env); + if (ast != exp) { + malval_free(ast); // Free input structure + } + return exp; +} + +// Setup the initial REPL environment +Env *repl_env; + +char *slurp_raw(char *path) { + char *data; + struct stat fst; + int fd = open(path, O_RDONLY), + sz; + if (fd < 0) { + abort("slurp failed to open '%s'", path); + } + if (fstat(fd, &fst) < 0) { + abort("slurp failed to stat '%s'", path); + } + data = malloc(fst.st_size+1); + sz = read(fd, data, fst.st_size); + if (sz < fst.st_size) { + abort("slurp failed to read '%s'", path); + } + data[sz] = '\0'; + return data; +} + +void init_repl_env() { + void _ref(char *name, MalVal*(*func)(MalVal*), int arg_cnt) { + void *(*f)(void *) = (void*(*)(void*))func; + env_set(repl_env, name, malval_new_function(f, arg_cnt, NULL)); + } + repl_env = new_env(NULL, NULL, NULL); + + int i; + for(i=0; i< (sizeof(types_ns) / sizeof(types_ns[0])); i++) { + MalVal *(*f)(MalVal *) = (MalVal*(*)(MalVal*))types_ns[i].func; + _ref(types_ns[i].name, f, types_ns[i].arg_cnt); + } + + MalVal *readline(MalVal *str) { + assert_type(str, MAL_STRING, "readline of non-string"); + char * line = _readline(str->val.string); + if (line) { return malval_new_string(line); } + else { return &mal_nil; } + } + _ref("readline", readline, 1); + + MalVal *read_string(MalVal *str) { + assert_type(str, MAL_STRING, "read_string of non-string"); + return read_str(str->val.string); + } + _ref("read-string", read_string, 1); + + MalVal *do_eval(MalVal *ast) { + return EVAL(ast, repl_env); + } + _ref("eval", do_eval, 1); + + MalVal *slurp(MalVal *path) { + assert_type(path, MAL_STRING, "slurp of non-string"); + char *data = slurp_raw(path->val.string); + if (!data || mal_error) { return NULL; } + return malval_new_string(data); + } + _ref("slurp", slurp, 1); + + MalVal *slurp_do(MalVal *path) { + assert_type(path, MAL_STRING, "slurp of non-string"); + char *data = slurp_raw(path->val.string), + *wrapped_data; + if (!data || mal_error) { return NULL; } + wrapped_data = g_strdup_printf("(do %s)", data); + free(data); + return malval_new_string(wrapped_data); + } + _ref("slurp-do", slurp_do, 1); + + RE(repl_env, "", "(def! not (fn* (a) (if a false true)))"); + RE(repl_env, "", "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))"); + RE(repl_env, "", "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))"); + RE(repl_env, "", + "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); +} + +int main(int argc, char *argv[]) +{ + MalVal *exp; + char *output; + char prompt[100]; + + // Set the initial prompt and environment + snprintf(prompt, sizeof(prompt), "user> "); + init_repl_env(); + + if (argc > 1) { + char *cmd = g_strdup_printf("(load-file \"%s\")", argv[1]); + RE(repl_env, "", cmd); + } else { + // REPL loop + for(;;) { + exp = RE(repl_env, prompt, NULL); + if (mal_error && strcmp("EOF", mal_error->val.string) == 0) { + return 0; + } + output = PRINT(exp); + + if (output) { + g_print("%s\n", output); + free(output); // Free output string + } + + //malval_free(exp); // Free evaluated expression + } + } +} diff --git a/c/tests/step9_interop.mal b/c/tests/step9_interop.mal new file mode 100644 index 0000000..657e3e7 --- /dev/null +++ b/c/tests/step9_interop.mal @@ -0,0 +1,23 @@ + +;; Testing FFI of "strlen" +(. nil "int32" "strlen" "string" "abcde") +;=>5 +(. nil "int32" "strlen" "string" "") +;=>0 + +;; Testing FFI of "strcmp" + +(. nil "int32" "strcmp" "string" "abc" "string" "abcA") +;=>-65 +(. nil "int32" "strcmp" "string" "abcA" "string" "abc") +;=>65 +(. nil "int32" "strcmp" "string" "abc" "string" "abc") +;=>0 + + +;; Testing FFI of "pow" (libm.so) + +(. "libm.so" "double" "pow" "double" 2.0 "double" 3.0) +;=>8.000000 +(. "libm.so" "double" "pow" "double" 3.0 "double" 2.0) +;=>9.000000 diff --git a/c/types.c b/c/types.c new file mode 100644 index 0000000..1308aac --- /dev/null +++ b/c/types.c @@ -0,0 +1,1038 @@ +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "types.h" + +// State + +MalVal *mal_error = NULL; + + +// Constant atomic values + +MalVal mal_nil = {MAL_NIL, NULL, {0}, 0}; +MalVal mal_true = {MAL_TRUE, NULL, {0}, 0}; +MalVal mal_false = {MAL_FALSE, NULL, {0}, 0}; + + +// Pre-declarations + +MalVal *cons(MalVal *x, MalVal *seq); + +// General Functions + +// Print a hash table +#include <glib-object.h> +void g_hash_table_print(GHashTable *hash_table) { + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + g_print ("%s/%p ", (const char *) key, (void *) value); + //g_print ("%s ", (const char *) key); + } +} + +GHashTable *g_hash_table_copy(GHashTable *src_table) { + GHashTable *new_table = g_hash_table_new(g_str_hash, g_str_equal); + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, src_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + g_hash_table_insert(new_table, key, value); + } + return new_table; +} + +int min(int a, int b) { return a < b ? a : b; } +int max(int a, int b) { return a > b ? a : b; } + +int _count(MalVal *obj) { + switch (obj->type) { + case MAL_NIL: return 0; + case MAL_LIST: return obj->val.array->len; + case MAL_VECTOR: return obj->val.array->len; + case MAL_HASH_MAP: return g_hash_table_size(obj->val.hash_table); + case MAL_STRING: return strlen(obj->val.string); + default: + _error("count unsupported for type %d\n", obj->type); + return 0; + } +} + +// Allocate a malval and set its type and value +MalVal *malval_new(MalType type, MalVal *metadata) { + MalVal *mv = (MalVal*)malloc(sizeof(MalVal)); + mv->type = type; + mv->metadata = metadata; + return mv; +} + +// +int malval_free(MalVal *mv) { + // TODO: free collection items + if (!(mv->type & (MAL_NIL|MAL_TRUE|MAL_FALSE))) { + free(mv); + } +} + +MalVal *malval_new_integer(gint64 val) { + MalVal *mv = malval_new(MAL_INTEGER, NULL); + mv->val.intnum = val; + return mv; +} + +MalVal *malval_new_float(gdouble val) { + MalVal *mv = malval_new(MAL_FLOAT, NULL); + mv->val.floatnum = val; + return mv; +} + +MalVal *malval_new_string(char *val) { + MalVal *mv = malval_new(MAL_STRING, NULL); + mv->val.string = val; + return mv; +} + +MalVal *malval_new_symbol(char *val) { + MalVal *mv = malval_new(MAL_SYMBOL, NULL); + mv->val.string = val; + return mv; +} + +MalVal *malval_new_hash_map(GHashTable *val) { + MalVal *mv = malval_new(MAL_HASH_MAP, NULL); + mv->val.hash_table = val; + return mv; +} + +MalVal *malval_new_list(MalType type, GArray *val) { + MalVal *mv = malval_new(type, NULL); + mv->val.array = val; + return mv; +} + +MalVal *malval_new_atom(MalVal *val) { + MalVal *mv = malval_new(MAL_ATOM, NULL); + mv->val.atom_val = val; + return mv; +} + + +MalVal *malval_new_function(void *(*func)(void *), int arg_cnt, MalVal* metadata) { + MalVal *mv = malval_new(MAL_FUNCTION_C, metadata); + mv->func_arg_cnt = arg_cnt; + assert(mv->func_arg_cnt <= 20, + "native function restricted to 20 args (%d given)", + mv->func_arg_cnt); + mv->ismacro = FALSE; + switch (arg_cnt) { + case -1: mv->val.f1 = (void *(*)(void*))func; break; + case 0: mv->val.f0 = (void *(*)())func; break; + case 1: mv->val.f1 = (void *(*)(void*))func; break; + case 2: mv->val.f2 = (void *(*)(void*,void*))func; break; + case 3: mv->val.f3 = (void *(*)(void*,void*,void*))func; break; + case 4: mv->val.f4 = (void *(*)(void*,void*,void*,void*))func; break; + case 5: mv->val.f5 = (void *(*)(void*,void*,void*,void*,void*))func; break; + case 6: mv->val.f6 = (void *(*)(void*,void*,void*,void*,void*, + void*))func; break; + case 7: mv->val.f7 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*))func; break; + case 8: mv->val.f8 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*))func; break; + case 9: mv->val.f9 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*,void*))func; break; + case 10: mv->val.f10 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*))func; break; + case 11: mv->val.f11 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*))func; break; + case 12: mv->val.f12 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*))func; break; + case 13: mv->val.f13 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*,void*))func; break; + case 14: mv->val.f14 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*,void*,void*))func; break; + case 15: mv->val.f15 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*))func; break; + case 16: mv->val.f16 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*))func; break; + case 17: mv->val.f17 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*))func; break; + case 18: mv->val.f18 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*,void*))func; break; + case 19: mv->val.f19 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*,void*,void*))func; break; + case 20: mv->val.f20 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*))func; break; + } + return mv; +} + +MalVal *apply(MalVal *f, MalVal *args) { + MalVal *res; + assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL, + "Cannot invoke %s", _pr_str(f,1)); + if (f->type & MAL_FUNCTION_MAL) { + Env *fn_env = new_env(f->val.func.env, f->val.func.args, args); + res = f->val.func.evaluator(f->val.func.body, fn_env); + return res; + } else { + MalVal *a = args; + assert((f->func_arg_cnt == -1) || + (f->func_arg_cnt == _count(args)), + "Length of formal params (%d) does not match actual parameters (%d)", + f->func_arg_cnt, _count(args)); + switch (f->func_arg_cnt) { + case -1: res=f->val.f1 (a); break; + case 0: res=f->val.f0 (); break; + case 1: res=f->val.f1 (_nth(a,0)); break; + case 2: res=f->val.f2 (_nth(a,0),_nth(a,1)); break; + case 3: res=f->val.f3 (_nth(a,0),_nth(a,1),_nth(a,2)); break; + case 4: res=f->val.f4 (_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3)); break; + case 5: res=f->val.f5 (_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4)); break; + case 6: res=f->val.f6 (_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5)); break; + case 7: res=f->val.f7 (_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6)); break; + case 8: res=f->val.f8 (_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7)); break; + case 9: res=f->val.f9 (_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8)); break; + case 10: res=f->val.f10(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9)); break; + case 11: res=f->val.f11(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9), + _nth(a,10)); break; + case 12: res=f->val.f12(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9), + _nth(a,10),_nth(a,11)); break; + case 13: res=f->val.f13(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9), + _nth(a,10),_nth(a,11),_nth(a,12)); break; + case 14: res=f->val.f14(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9), + _nth(a,10),_nth(a,11),_nth(a,12),_nth(a,13)); break; + case 15: res=f->val.f15(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9), + _nth(a,10),_nth(a,11),_nth(a,12),_nth(a,13),_nth(a,14)); break; + case 16: res=f->val.f16(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9), + _nth(a,10),_nth(a,11),_nth(a,12),_nth(a,13),_nth(a,14), + _nth(a,15)); break; + case 17: res=f->val.f17(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9), + _nth(a,10),_nth(a,11),_nth(a,12),_nth(a,13),_nth(a,14), + _nth(a,15),_nth(a,16)); break; + case 18: res=f->val.f18(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9), + _nth(a,10),_nth(a,11),_nth(a,12),_nth(a,13),_nth(a,14), + _nth(a,15),_nth(a,16),_nth(a,17)); break; + case 19: res=f->val.f19(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9), + _nth(a,10),_nth(a,11),_nth(a,12),_nth(a,13),_nth(a,14), + _nth(a,15),_nth(a,16),_nth(a,17),_nth(a,18)); break; + case 20: res=f->val.f20(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9), + _nth(a,10),_nth(a,11),_nth(a,12),_nth(a,13),_nth(a,14), + _nth(a,15),_nth(a,16),_nth(a,17),_nth(a,18),_nth(a,19)); break; + } + return res; + } +} + + +char *_pr_str_hash_map(MalVal *obj, int print_readably) { + int start = 1; + char *repr = NULL, *repr_tmp1 = NULL, *repr_tmp2 = NULL; + GHashTableIter iter; + gpointer key, value; + + repr = g_strdup_printf("{"); + + g_hash_table_iter_init (&iter, obj->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + //g_print ("%s/%p ", (const char *) key, (void *) value); + + repr_tmp1 = _pr_str((MalVal*)value, print_readably); + if (start) { + start = 0; + repr = g_strdup_printf("{\"%s\" %s", (char *)key, repr_tmp1); + } else { + repr_tmp2 = repr; + repr = g_strdup_printf("%s \"%s\" %s", repr_tmp2, (char *)key, repr_tmp1); + free(repr_tmp2); + } + free(repr_tmp1); + } + repr_tmp2 = repr; + repr = g_strdup_printf("%s}", repr_tmp2); + free(repr_tmp2); + return repr; +} + +char *_pr_str_list(MalVal *obj, int print_readably, char start, char end) { + int i; + char *repr = NULL, *repr_tmp1 = NULL, *repr_tmp2 = NULL; + repr = g_strdup_printf("%c", start); + for (i=0; i<_count(obj); i++) { + repr_tmp1 = _pr_str(g_array_index(obj->val.array, MalVal*, i), + print_readably); + if (i == 0) { + repr = g_strdup_printf("%c%s", start, repr_tmp1); + } else { + repr_tmp2 = repr; + repr = g_strdup_printf("%s %s", repr_tmp2, repr_tmp1); + free(repr_tmp2); + } + free(repr_tmp1); + } + repr_tmp2 = repr; + repr = g_strdup_printf("%s%c", repr_tmp2, end); + free(repr_tmp2); + return repr; +} + +// Return a string representation of the MalVal object. Returned string must +// be freed by caller. +char *_pr_str(MalVal *obj, int print_readably) { + char *repr = NULL; + if (obj == NULL) { return NULL; } + switch (obj->type) { + case MAL_NIL: + repr = g_strdup_printf("nil"); + break; + case MAL_TRUE: + repr = g_strdup_printf("true"); + break; + case MAL_FALSE: + repr = g_strdup_printf("false"); + break; + case MAL_STRING: + if (print_readably) { + char *repr_tmp = g_strescape(obj->val.string, ""); + repr = g_strdup_printf("\"%s\"", repr_tmp); + free(repr_tmp); + } else { + repr = g_strdup_printf("%s", obj->val.string); + } + break; + case MAL_SYMBOL: + repr = g_strdup_printf("%s", obj->val.string); + break; + case MAL_INTEGER: + repr = g_strdup_printf("%" G_GINT64_FORMAT, obj->val.intnum); + break; + case MAL_FLOAT: + repr = g_strdup_printf("%f", obj->val.floatnum); + break; + case MAL_HASH_MAP: + repr = _pr_str_hash_map(obj, print_readably); + break; + case MAL_LIST: + repr = _pr_str_list(obj, print_readably, '(', ')'); + break; + case MAL_VECTOR: + repr = _pr_str_list(obj, print_readably, '[', ']'); + break; + case MAL_ATOM: + repr = g_strdup_printf("(atom %s)", + _pr_str(obj->val.atom_val, print_readably)); + break; + case MAL_FUNCTION_C: + repr = g_strdup_printf("#<function@%p>", obj->val.f0); + break; + case MAL_FUNCTION_MAL: + repr = g_strdup_printf("#<Function: (fn* %s %s)>", + _pr_str(obj->val.func.args, print_readably), + _pr_str(obj->val.func.body, print_readably)); + break; + default: + printf("pr_str unknown type %d\n", obj->type); + repr = g_strdup_printf("<unknown>"); + } + return repr; +} + +// Return a string representation of the MalVal arguments. Returned string must +// be freed by caller. +char *_pr_str_args(MalVal *args, char *sep, int print_readably) { + assert_type(args, MAL_LIST|MAL_VECTOR, + "_pr_str called with non-sequential args"); + int i; + char *repr = g_strdup_printf(""), + *repr2 = NULL; + for (i=0; i<_count(args); i++) { + MalVal *obj = g_array_index(args->val.array, MalVal*, i); + if (i != 0) { + repr2 = repr; + repr = g_strdup_printf("%s%s", repr2, sep); + free(repr2); + } + repr2 = repr; + repr = g_strdup_printf("%s%s", + repr2, _pr_str(obj, print_readably)); + free(repr2); + } + return repr; +} + +// Return a string representation of a MalVal sequence (in a format that can +// be read by the reader). Returned string must be freed by caller. +MalVal *pr_str(MalVal *args) { + assert_type(args, MAL_LIST|MAL_VECTOR, + "pr_str called with non-sequential args"); + return malval_new_string(_pr_str_args(args, " ", 1)); +} + +// Return a string representation of a MalVal sequence with every item +// concatenated together. Returned string must be freed by caller. +MalVal *str(MalVal *args) { + assert_type(args, MAL_LIST|MAL_VECTOR, + "str called with non-sequential args"); + return malval_new_string(_pr_str_args(args, "", 0)); +} + +// Print a string representation of a MalVal sequence (in a format that can +// be read by the reader) followed by a newline. Returns nil. +MalVal *prn(MalVal *args) { + assert_type(args, MAL_LIST|MAL_VECTOR, + "prn called with non-sequential args"); + char *repr = _pr_str_args(args, " ", 1); + g_print("%s\n", repr); + free(repr); + return &mal_nil; +} + +// Print a string representation of a MalVal sequence (for human consumption) +// followed by a newline. Returns nil. +MalVal *println(MalVal *args) { + assert_type(args, MAL_LIST|MAL_VECTOR, + "println called with non-sequential args"); + char *repr = _pr_str_args(args, " ", 0); + g_print("%s\n", repr); + free(repr); + return &mal_nil; +} + +MalVal *with_meta(MalVal *obj, MalVal *meta) { + MalVal *new_obj = malval_new(obj->type, meta); + new_obj->val = obj->val; + return new_obj; +} + +MalVal *meta(MalVal *obj) { + assert_type(obj, MAL_LIST|MAL_VECTOR|MAL_HASH_MAP|MAL_FUNCTION_C|MAL_FUNCTION_MAL, + "attempt to get metadata from non-collection type"); + if (obj->metadata == NULL) { + return &mal_nil; + } else { + return obj->metadata; + } +} + + +int _equal_Q(MalVal *a, MalVal *b) { + if (a == NULL || b == NULL) { return FALSE; } + + // If types are the same or both are sequential then they might be equal + if (!((a->type == b->type) || + (_sequential_Q(a) && _sequential_Q(b)))) { + return FALSE; + } + switch (a->type) { + case MAL_NIL: + case MAL_TRUE: + case MAL_FALSE: + return a->type == b->type; + case MAL_INTEGER: + return a->val.intnum == b->val.intnum; + case MAL_FLOAT: + return a->val.floatnum == b->val.floatnum; + case MAL_SYMBOL: + case MAL_STRING: + if (strcmp(a->val.string, b->val.string) == 0) { + return TRUE; + } else { + return FALSE; + } + case MAL_LIST: + case MAL_VECTOR: + if (a->val.array->len != b->val.array->len) { + return FALSE; + } + int i; + for (i=0; i<a->val.array->len; i++) { + if (! _equal_Q(g_array_index(a->val.array, MalVal*, i), + g_array_index(b->val.array, MalVal*, i))) { + return FALSE; + } + } + return TRUE; + case MAL_HASH_MAP: + _error("_equal_Q does not support hash-maps yet"); + return FALSE; + case MAL_FUNCTION_C: + case MAL_FUNCTION_MAL: + return a->val.f0 == b->val.f0; + default: + _error("_equal_Q unsupported comparison type %d\n", a->type); + return FALSE; + } +} + +MalVal *equal_Q(MalVal *a, MalVal *b) { + if (_equal_Q(a, b)) { return &mal_true; } + else { return &mal_false; } +} + +// +// nil, true, false, string +MalVal *nil_Q(MalVal *seq) { return seq->type & MAL_NIL ? &mal_true : &mal_false; } +MalVal *true_Q(MalVal *seq) { return seq->type & MAL_TRUE ? &mal_true : &mal_false; } +MalVal *false_Q(MalVal *seq) { return seq->type & MAL_FALSE ? &mal_true : &mal_false; } +MalVal *string_Q(MalVal *seq) { return seq->type & MAL_STRING ? &mal_true : &mal_false; } + +// +// Numbers +#define WRAP_INTEGER_OP(name, op) \ + MalVal *int_ ## name(MalVal *a, MalVal *b) { \ + return malval_new_integer(a->val.intnum op b->val.intnum); \ + } +#define WRAP_INTEGER_CMP_OP(name, op) \ + MalVal *int_ ## name(MalVal *a, MalVal *b) { \ + return a->val.intnum op b->val.intnum ? &mal_true : &mal_false; \ + } +WRAP_INTEGER_OP(plus,+) +WRAP_INTEGER_OP(minus,-) +WRAP_INTEGER_OP(multiply,*) +WRAP_INTEGER_OP(divide,/) +WRAP_INTEGER_CMP_OP(gt,>) +WRAP_INTEGER_CMP_OP(gte,>=) +WRAP_INTEGER_CMP_OP(lt,<) +WRAP_INTEGER_CMP_OP(lte,<=) + + +// +// Symbols +MalVal *symbol_Q(MalVal *seq) { return seq->type & MAL_SYMBOL ? &mal_true : &mal_false; } + + +// Hash maps +// +MalVal *_hash_map(int count, ...) { + assert((count % 2) == 0, + "odd number of parameters to hash-map"); + GHashTable *htable = g_hash_table_new(g_str_hash, g_str_equal); + MalVal *hm = malval_new_hash_map(htable); + char *k; + MalVal *v; + va_list ap; + va_start(ap, count); + while (count > 0) { + k = va_arg(ap, char*); + v = va_arg(ap, MalVal*); + g_hash_table_insert(htable, k, v); + count = count - 2; + } + va_end(ap); + return hm; +} + +MalVal *hash_map(MalVal *args) { + assert_type(args, MAL_LIST|MAL_VECTOR, + "hash-map called with non-sequential arguments"); + assert((args->val.array->len % 2) == 0, + "odd number of parameters to hash-map"); + GHashTable *htable = g_hash_table_new(g_str_hash, g_str_equal); + MalVal *hm = malval_new_hash_map(htable); + int i; + MalVal *k, *v; + for(i=0; i< args->val.array->len; i+=2) { + k = g_array_index(args->val.array, MalVal*, i); + assert_type(k, MAL_STRING, + "hash-map called with non-string key"); + v = g_array_index(args->val.array, MalVal*, i+1); + g_hash_table_insert(htable, k->val.string, v); + } + return hm; +} + +int _hash_map_Q(MalVal *seq) { + return seq->type & MAL_HASH_MAP; +} +MalVal *hash_map_Q(MalVal *seq) { return _hash_map_Q(seq) ? &mal_true : &mal_false; } + +// TODO: support multiple key/values +MalVal *assoc(MalVal *hm, MalVal *key, MalVal *val) { + GHashTable *htable = g_hash_table_copy(hm->val.hash_table); + MalVal *new_hm = malval_new_hash_map(htable); + g_hash_table_insert(htable, key->val.string, val); + return new_hm; +} + +// TODO: support multiple keys +MalVal *dissoc(MalVal *hm, MalVal *key) { + GHashTable *htable = g_hash_table_copy(hm->val.hash_table); + MalVal *new_hm = malval_new_hash_map(htable); + g_hash_table_remove(htable, key->val.string); + return new_hm; +} + +MalVal *keys(MalVal *obj) { + assert_type(obj, MAL_HASH_MAP, + "keys called on non-hash-map"); + + GHashTableIter iter; + gpointer key, value; + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + _count(obj))); + g_hash_table_iter_init (&iter, obj->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + MalVal *kname = malval_new_string((char *)key); + g_array_append_val(seq->val.array, kname); + } + return seq; +} + +MalVal *vals(MalVal *obj) { + assert_type(obj, MAL_HASH_MAP, + "vals called on non-hash-map"); + + GHashTableIter iter; + gpointer key, value; + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + _count(obj))); + g_hash_table_iter_init (&iter, obj->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + g_array_append_val(seq->val.array, value); + } + return seq; +} + + +// Errors/Exceptions +void _error(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + mal_error = malval_new_string(g_strdup_vprintf(fmt, args)); +} +void throw(MalVal *obj) { + mal_error = obj; +} + + +// Lists + +MalVal *_list(int count, ...) { + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + count)); + MalVal *v; + va_list ap; + va_start(ap, count); + while (count-- > 0) { + v = va_arg(ap, MalVal*); + g_array_append_val(seq->val.array, v); + } + va_end(ap); + return seq; +} +MalVal *list(MalVal *args) { + assert_type(args, MAL_LIST|MAL_VECTOR, + "list called with invalid arguments"); + args->type = MAL_LIST; + return args; +} + +int _list_Q(MalVal *seq) { + return seq->type & MAL_LIST; +} +MalVal *list_Q(MalVal *seq) { return _list_Q(seq) ? &mal_true : &mal_false; } + + +// Vectors + +MalVal *_vector(int count, ...) { + MalVal *seq = malval_new_list(MAL_VECTOR, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + count)); + MalVal *v; + va_list ap; + va_start(ap, count); + while (count-- > 0) { + v = va_arg(ap, MalVal*); + g_array_append_val(seq->val.array, v); + } + va_end(ap); + return seq; +} +MalVal *vector(MalVal *args) { + assert_type(args, MAL_LIST|MAL_VECTOR, + "vector called with invalid arguments"); + args->type = MAL_VECTOR; + return args; +} + + +int _vector_Q(MalVal *seq) { + return seq->type & MAL_VECTOR; +} +MalVal *vector_Q(MalVal *seq) { return _vector_Q(seq) ? &mal_true : &mal_false; } + + +// hash map and vector functions +MalVal *get(MalVal *obj, MalVal *key) { + MalVal *val; + switch (obj->type) { + case MAL_VECTOR: + return _nth(obj, key->val.intnum); + case MAL_HASH_MAP: + if (g_hash_table_lookup_extended(obj->val.hash_table, + key->val.string, + NULL, (gpointer*)&val)) { + return val; + } else { + return &mal_nil; + } + default: + abort("get called on unsupported type %d", obj->type); + } +} + +MalVal *contains_Q(MalVal *obj, MalVal *key) { + switch (obj->type) { + case MAL_VECTOR: + if (key->val.intnum < obj->val.array->len) { + return &mal_true; + } else { + return &mal_false; + } + case MAL_HASH_MAP: + if (g_hash_table_contains(obj->val.hash_table, key->val.string)) { + return &mal_true; + } else { + return &mal_false; + } + default: + abort("contains? called on unsupported type %d", obj->type); + } +} + + +// Atoms +MalVal *atom(MalVal *val) { + return malval_new_atom(val); +} + +int _atom_Q(MalVal *exp) { + return exp->type & MAL_ATOM; +} +MalVal *atom_Q(MalVal *exp) { return _atom_Q(exp) ? &mal_true : &mal_false; } + +MalVal *deref(MalVal *atm) { + assert_type(atm, MAL_ATOM, + "deref called on non-atom"); + return atm->val.atom_val; +} + +MalVal *reset_BANG(MalVal *atm, MalVal *val) { + assert_type(atm, MAL_ATOM, + "reset! called with non-atom"); + atm->val.atom_val = val; + return val; +} + +MalVal *swap_BANG(MalVal *args) { + assert_type(args, MAL_LIST|MAL_VECTOR, + "swap! called with invalid arguments"); + assert(_count(args) >= 2, + "swap! called with %d args, needs at least 2", _count(args)); + MalVal *atm = _nth(args, 0), + *f = _nth(args, 1), + *sargs = _slice(args, 2, _count(args)), + *fargs = cons(atm->val.atom_val, sargs), + *new_val = apply(f, fargs); + if (mal_error) { return NULL; } + atm->val.atom_val = new_val; + return new_val; +} + + + +// Sequence functions +MalVal *_slice(MalVal *seq, int start, int end) { + int i, new_len = max(0, min(end-start, + _count(seq)-start)); + GArray *new_arr = g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + new_len); + for (i=start; i<start+new_len; i++) { + g_array_append_val(new_arr, g_array_index(seq->val.array, MalVal*, i)); + } + return malval_new_list(MAL_LIST, new_arr); +} + + +int _sequential_Q(MalVal *seq) { + return seq->type & (MAL_LIST|MAL_VECTOR); +} +MalVal *sequential_Q(MalVal *seq) { + return _sequential_Q(seq) ? &mal_true : &mal_false; +} + +MalVal *cons(MalVal *x, MalVal *seq) { + assert_type(seq, MAL_LIST|MAL_VECTOR, + "second argument to cons is non-sequential"); + int i, len = _count(seq); + GArray *new_arr = g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + len+1); + g_array_append_val(new_arr, x); + for (i=0; i<len; i++) { + g_array_append_val(new_arr, g_array_index(seq->val.array, MalVal*, i)); + } + return malval_new_list(MAL_LIST, new_arr); +} + +MalVal *count(MalVal *seq) { + return malval_new_integer(_count(seq)); +} + +MalVal *empty_Q(MalVal *seq) { + assert_type(seq, MAL_LIST|MAL_VECTOR, + "empty? called with non-sequential"); + return (seq->val.array->len == 0) ? &mal_true : &mal_false; +} + +MalVal *concat(MalVal *args) { + MalVal *arg, *e, *lst; + int i, j, arg_cnt = _count(args); + lst = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), arg_cnt)); + for (i=0; i<arg_cnt; i++) { + arg = g_array_index(args->val.array, MalVal*, i); + assert_type(arg, MAL_LIST|MAL_VECTOR, + "concat called with non-sequential"); + for (j=0; j<_count(arg); j++) { + e = g_array_index(arg->val.array, MalVal*, j); + g_array_append_val(lst->val.array, e); + } + } + + return lst; +} + +MalVal *sconj(MalVal *args) { + assert_type(args, MAL_LIST|MAL_VECTOR, + "conj called with non-sequential"); + MalVal *src_lst = _nth(args, 0); + assert_type(args, MAL_LIST|MAL_VECTOR, + "first argument to conj is non-sequential"); + int i, len = _count(src_lst) + _count(args) - 1; + GArray *new_arr = g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + len); + for (i=1; i<len; i++) { + g_array_append_val(new_arr, g_array_index(args->val.array, MalVal*, i)); + } + return malval_new_list(MAL_LIST, new_arr); +} + +MalVal *first(MalVal *seq) { + assert_type(seq, MAL_LIST|MAL_VECTOR, + "first called with non-sequential"); + if (_count(seq) == 0) { + return &mal_nil; + } + return g_array_index(seq->val.array, MalVal*, 0); +} + +MalVal *last(MalVal *seq) { + assert_type(seq, MAL_LIST|MAL_VECTOR, + "last called with non-sequential"); + if (_count(seq) == 0) { + return &mal_nil; + } + return g_array_index(seq->val.array, MalVal*, _count(seq)-1); +} + +MalVal *rest(MalVal *seq) { + return _slice(seq, 1, _count(seq)); +} + +MalVal *_nth(MalVal *seq, int idx) { + assert_type(seq, MAL_LIST|MAL_VECTOR, + "nth called with non-sequential"); + if (idx >= _count(seq)) { + return &mal_nil; + } + return g_array_index(seq->val.array, MalVal*, idx); +} +MalVal *nth(MalVal *seq, MalVal *idx) { + return _nth(seq, idx->val.intnum); +} + +MalVal *_map2(MalVal *(*func)(void*, void*), MalVal *lst, void *arg2) { + MalVal *e, *el; + assert_type(lst, MAL_LIST|MAL_VECTOR, + "_map called with non-sequential"); + int i, len = _count(lst); + el = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), len)); + for (i=0; i<len; i++) { + e = func(g_array_index(lst->val.array, MalVal*, i), arg2); + if (!e || mal_error) return NULL; + g_array_append_val(el->val.array, e); + } + return el; +} + +MalVal *map(MalVal *mvf, MalVal *lst) { + MalVal *res, *el; + assert_type(mvf, MAL_FUNCTION_C|MAL_FUNCTION_MAL, + "map called with non-function"); + assert_type(lst, MAL_LIST|MAL_VECTOR, + "map called with non-sequential"); + int i, len = _count(lst); + el = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), len)); + for (i=0; i<len; i++) { + // TODO: this is replicating some of apply functionality + if (mvf->type & MAL_FUNCTION_MAL) { + Env *fn_env = new_env(mvf->val.func.env, + mvf->val.func.args, + _slice(lst, i, i+1)); + res = mvf->val.func.evaluator(mvf->val.func.body, fn_env); + } else { + res = mvf->val.f1(g_array_index(lst->val.array, MalVal*, i)); + } + if (!res || mal_error) return NULL; + g_array_append_val(el->val.array, res); + } + return el; +} + + +// Env + +Env *new_env(Env *outer, MalVal* binds, MalVal *exprs) { + Env *e = malloc(sizeof(Env)); + e->table = g_hash_table_new(g_str_hash, g_str_equal); + e->outer = outer; + + if (binds && exprs) { + assert_type(binds, MAL_LIST|MAL_VECTOR, + "new_env called with non-sequential bindings"); + assert_type(exprs, MAL_LIST|MAL_VECTOR, + "new_env called with non-sequential expressions"); + int binds_len = _count(binds), + exprs_len = _count(exprs), + varargs = 0, i; + for (i=0; i<binds_len; i++) { + if (i > exprs_len) { break; } + if (_nth(binds, i)->val.string[0] == '&') { + varargs = 1; + env_set(e, _nth(binds, i+1)->val.string, _slice(exprs, i, _count(exprs))); + break; + } else { + env_set(e, _nth(binds, i)->val.string, _nth(exprs, i)); + } + } + assert(varargs || (binds_len == exprs_len), + "Arity mismatch: %d formal params vs %d actual params", + binds_len, exprs_len); + + } + return e; +} + +Env *env_find(Env *env, char *key) { + void *val = g_hash_table_lookup(env->table, key); + if (val) { + return env; + } else if (env->outer) { + return env_find(env->outer, key); + } else { + return NULL; + } +} + +MalVal *env_get(Env *env, char *key) { + Env *e = env_find(env, key); + assert(e, "'%s' not found", key); + return g_hash_table_lookup(e->table, key); +} + +Env *env_set(Env *env, char *key, MalVal *val) { + g_hash_table_insert(env->table, key, val); + return env; +} + +types_ns_entry types_ns[49] = { + {"pr-str", (void*(*)(void*))pr_str, -1}, + {"str", (void*(*)(void*))str, -1}, + {"prn", (void*(*)(void*))prn, -1}, + {"println", (void*(*)(void*))println, -1}, + {"with-meta", (void*(*)(void*))with_meta, 2}, + {"meta", (void*(*)(void*))meta, 1}, + {"=", (void*(*)(void*))equal_Q, 2}, + {"symbol?", (void*(*)(void*))symbol_Q, 1}, + {"nil?", (void*(*)(void*))nil_Q, 1}, + {"true?", (void*(*)(void*))true_Q, 1}, + {"false?", (void*(*)(void*))false_Q, 1}, + {"+", (void*(*)(void*))int_plus, 2}, + {"-", (void*(*)(void*))int_minus, 2}, + {"*", (void*(*)(void*))int_multiply, 2}, + {"/", (void*(*)(void*))int_divide, 2}, + {">", (void*(*)(void*))int_gt, 2}, + {">=", (void*(*)(void*))int_gte, 2}, + {"<", (void*(*)(void*))int_lt, 2}, + {"<=", (void*(*)(void*))int_lte, 2}, + {"hash-map", (void*(*)(void*))hash_map, -1}, + {"map?", (void*(*)(void*))hash_map_Q, 1}, + {"assoc", (void*(*)(void*))assoc, 3}, + {"dissoc", (void*(*)(void*))dissoc, 2}, + {"get", (void*(*)(void*))get, 2}, + {"contains?", (void*(*)(void*))contains_Q, 2}, + {"keys", (void*(*)(void*))keys, 1}, + {"vals", (void*(*)(void*))vals, 1}, + {"throw", (void*(*)(void*))throw, 1}, + {"list", (void*(*)(void*))list, -1}, + {"list?", (void*(*)(void*))list_Q, 1}, + {"vector", (void*(*)(void*))vector, -1}, + {"vector?", (void*(*)(void*))vector_Q, 1}, + {"atom", (void*(*)(void*))atom, 1}, + {"atom?", (void*(*)(void*))atom_Q, 1}, + {"deref", (void*(*)(void*))deref, 1}, + {"reset!", (void*(*)(void*))reset_BANG, 2}, + {"swap!", (void*(*)(void*))swap_BANG, -1}, + {"sequential?", (void*(*)(void*))sequential_Q, 1}, + {"cons", (void*(*)(void*))cons, 2}, + {"count", (void*(*)(void*))count, 1}, + {"empty?", (void*(*)(void*))empty_Q, 1}, + {"concat", (void*(*)(void*))concat, -1}, + {"conj", (void*(*)(void*))sconj, -1}, + {"first", (void*(*)(void*))first, 1}, + {"last", (void*(*)(void*))last, 1}, + {"rest", (void*(*)(void*))rest, 1}, + {"nth", (void*(*)(void*))nth, 2}, + {"apply", (void*(*)(void*))apply, 2}, + {"map", (void*(*)(void*))map, 2}, + }; diff --git a/c/types.h b/c/types.h new file mode 100644 index 0000000..271a899 --- /dev/null +++ b/c/types.h @@ -0,0 +1,162 @@ +#ifndef __MAL_TYPES__ +#define __MAL_TYPES__ + +#include <glib.h> + +// State + +struct MalVal; // pre-declare +extern struct MalVal *mal_error; + +#define abort(format, ...) \ + { _error(format, ##__VA_ARGS__); return NULL; } + +#define assert(test, format, ...) \ + if (!(test)) { \ + _error(format, ##__VA_ARGS__); \ + return NULL; \ + } + +#define assert_type(mv, typ, format, ...) \ + if (!(mv->type & (typ))) { \ + _error(format, ##__VA_ARGS__); \ + return NULL; \ + } + +typedef enum { + MAL_NIL = 1, + MAL_TRUE = 2, + MAL_FALSE = 4, + MAL_INTEGER = 8, + MAL_FLOAT = 16, + MAL_SYMBOL = 32, + MAL_STRING = 64, + MAL_LIST = 128, + MAL_VECTOR = 256, + MAL_HASH_MAP = 512, + MAL_ATOM = 1024, + MAL_FUNCTION_C = 2048, + MAL_FUNCTION_MAL = 4096, +} MalType; + + +// Predeclare Env +typedef struct Env Env; + +typedef struct MalVal { + MalType type; + struct MalVal *metadata; + union { + gint64 intnum; + gdouble floatnum; + char *string; + GArray *array; + GHashTable *hash_table; + struct MalVal *atom_val; + void *(*f0) (); + void *(*f1) (void*); + void *(*f2) (void*,void*); + void *(*f3) (void*,void*,void*); + void *(*f4) (void*,void*,void*,void*); + void *(*f5) (void*,void*,void*,void*,void*); + void *(*f6) (void*,void*,void*,void*,void*,void*); + void *(*f7) (void*,void*,void*,void*,void*,void*,void*); + void *(*f8) (void*,void*,void*,void*,void*,void*,void*,void*); + void *(*f9) (void*,void*,void*,void*,void*,void*,void*,void*,void*); + void *(*f10)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*); + void *(*f11)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*, + void*); + void *(*f12)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*, + void*,void*); + void *(*f13)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*, + void*,void*,void*); + void *(*f14)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*, + void*,void*,void*,void*); + void *(*f15)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*); + void *(*f16)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*,void*); + void *(*f17)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*,void*,void*); + void *(*f18)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*,void*,void*,void*); + void *(*f19)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*,void*,void*,void*,void*); + void *(*f20)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*,void*,void*,void*,void*,void*); + struct { + struct MalVal *(*evaluator)(struct MalVal *, Env *); + struct MalVal *args; + struct MalVal *body; + struct Env *env; + } func; + } val; + int func_arg_cnt; + int ismacro; +} MalVal; + +// Constants + +extern MalVal mal_nil; +extern MalVal mal_true; +extern MalVal mal_false; + + +// Declare functions used internally (by other C code). +// Mal visible functions are "exported" in types_ns + +MalVal *malval_new(MalType type, MalVal *metadata); +int malval_free(MalVal *mv); +MalVal *malval_new_integer(gint64 val); +MalVal *malval_new_float(gdouble val); +MalVal *malval_new_string(char *val); +MalVal *malval_new_symbol(char *val); +MalVal *malval_new_list(MalType type, GArray *val); +MalVal *malval_new_function(void *(*func)(void *), int arg_cnt, MalVal* metadata); + +MalVal *hash_map(MalVal *args); +void _error(const char *fmt, ...); +MalVal *_list(int count, ...); + +MalVal *apply(MalVal *f, MalVal *el); + +char *_pr_str(MalVal *args, int print_readably); + +MalVal *first(MalVal* seq); +MalVal *last(MalVal* seq); +MalVal *_slice(MalVal *seq, int start, int end); +MalVal *_nth(MalVal *seq, int idx); +MalVal *rest(MalVal *seq); + +MalVal *_map2(MalVal *(*func)(void*, void*), MalVal *lst, void *arg2); + +// These are just used by step2 and step3 before then type_ns environment is +// imported + +MalVal *int_plus(MalVal *a, MalVal *b); +MalVal *int_minus(MalVal *a, MalVal *b); +MalVal *int_multiply(MalVal *a, MalVal *b); +MalVal *int_divide(MalVal *a, MalVal *b); + +// Env + +typedef struct Env { + struct Env *outer; + GHashTable *table; +} Env; + +Env *new_env(Env *outer, MalVal* binds, MalVal *exprs); +Env *env_find(Env *env, char *key); +MalVal *env_get(Env *env, char *key); +Env *env_set(Env *env, char *key, MalVal *val); + +// namespace of type functions +typedef struct { + char *name; + void *(*func)(void*); + int arg_cnt; +} types_ns_entry; + +extern types_ns_entry types_ns[49]; + +#endif |
