044858c2d8d40183cf781bc1723ea329067a5dd5 — Stephen Brennan a month ago c2cd4cb + 9e597f3
Merge branch 'feature_modules'
M Makefile => Makefile +5 -4
@@ 6,7 6,8 @@ include Makefile.conf
  
  OBJS=src/builtins.o src/charbuf.o src/gc.o src/hashtable.o src/iter.o \
-      src/parse.o src/ringbuf.o src/types.o src/util.o src/textcache.o
+      src/parse.o src/ringbuf.o src/types.o src/util.o src/textcache.o \
+      src/module.o
  
  # https://semver.org
  VERSION=1.1.0


@@ 39,7 40,7 @@ $(CC) $(CFLAGS) $^ -o $@
  
  clean: FORCE
- 	rm -rf bin/* src/*.o tools/*.o src/*.gcda src/*.gcno
+ 	rm -rf bin/* {src,tools}/*.{o,gcda,gcno}
  
  # Meant to be run after downloading the source tarball. This has an un-expressed
  # dependency on `man/funlisp.3`, since the source tarball includes generated


@@ 82,7 83,7 @@   test: all FORCE
  	rm -f cov*.html src/*.gcda
- 	@python test.py scripts/tests
+ 	@cd scripts && python ../test.py tests -r ../bin/funlisp
  	gcovr -r src --html --html-details -o cov.html
  
  clean_doc:


@@ 103,7 104,7 @@ inc/*.h src/*.c src/*.h tools/*.c scripts/*.lisp bin/.gitkeep
  
  serve_doc: doc
- 	cd doc/_build/html; python -m http.server --bind 0.0.0.0 8080
+ 	cd html; python -m http.server --bind 0.0.0.0 8080
  
  # Named differently from Makefile.dep to avoid implicit rules for inclusion.
  depend: FORCE

M Makefile.dep => Makefile.dep +2 -0
@@ 5,6 5,8 @@ src/ringbuf.h src/hashtable.h
  hashtable.o: src/hashtable.c src/iter.h src/hashtable.h
  iter.o: src/iter.c src/iter.h
+ module.o: src/module.c src/funlisp_internal.h inc/funlisp.h src/iter.h \
+  src/ringbuf.h src/hashtable.h
  parse.o: src/parse.c src/funlisp_internal.h inc/funlisp.h src/iter.h \
   src/ringbuf.h src/hashtable.h src/charbuf.h
  ringbuf.o: src/ringbuf.c src/ringbuf.h

M doc/api.rst => doc/api.rst +6 -0
@@ 39,6 39,12 @@ .. doxygengroup:: builtins
     :content-only:
  
+ Modules
+ -------
+ 
+ .. doxygengroup:: modules
+    :content-only:
+ 
  Embedding Tools
  ---------------
  

M inc/funlisp.h => inc/funlisp.h +84 -2
@@ 197,6 197,12 @@ typedef struct lisp_list lisp_list;
  
  /**
+  * Data structure representing a module.
+  * @ingroup types
+  */
+ typedef struct lisp_module lisp_module;
+ 
+ /**
   * @defgroup value Lisp Values
   * @{
   */


@@ 508,6 514,11 @@ extern lisp_type *type_lambda;
  
  /**
+  * Type object of ::lisp_module
+  */
+ extern lisp_type *type_module;
+ 
+ /**
   * Flag instructing string/symbol creation routines that they should copy the
   * string buffer itself, and use the copy rather than the original argument.
   * This could be useful in case callers would like to free the string after


@@ 713,6 724,76 @@   /**
   * @}
+  * @defgroup modules Modules
+  * @{
+  */
+ 
+ /**
+  * @brief Create a new, empty module object.
+  *
+  * The module object represents a separate namespace which can contain symbols
+  * mapped to builtins, lambdas, constants, and more. It is a great place to
+  * store related functionality. The module can represent all of the symbols
+  * defined in a single lisp file. It can also represent a collection of
+  * resources created by C code.
+  *
+  * C code which wishes to create a module should start here, access the scope
+  * from the resulting module using lisp_module_get_scope(), and populate the
+  * scope with any builtins or other data necessary. Finally, modules should be
+  * registered with the runtime using lisp_register_module().
+  *
+  * @param rt runtime
+  * @param name the name of the module, used for imports
+  * @param file filename for the module object. This file is not actually loaded,
+  * simply stored as part of the module metadata. If you would like to load a
+  * file as a module, see lisp_import_file().
+  * @return a new, empty module object
+  */
+ lisp_module *lisp_new_module(lisp_runtime *rt, lisp_string *name, lisp_string *file);
+ 
+ /**
+  * @brief Given a module returned by lisp_new_module(), access its scope
+  * @param module A module to get the scope of
+  * @return the scope of that module
+  */
+ lisp_scope *lisp_module_get_scope(lisp_module *module);
+ 
+ /**
+  * @brief Given a module object, register it with the runtime
+  *
+  * The module is mapped using the name given at creation time. For example, if a
+  * module was given the name ``foo``, then it can be imported with the statement
+  * ``(import foo)``.
+  *
+  * @param rt runtime
+  * @param module module to register
+  */
+ void lisp_register_module(lisp_runtime *rt, lisp_module *module);
+ 
+ /**
+  * @brief Load a file of lisp code and return it as a module object.
+  * @param name Name for the resulting module
+  * @param file Filename to read and load as a module
+  * @return the resulting module
+  */
+ lisp_module *lisp_import_file(lisp_runtime *rt, lisp_string *name, lisp_string *file);
+ 
+ /**
+  * @brief Given a module name, import and return it if possible, or raise error
+  *
+  * This process works as follows. First, check whether an existing module has
+  * already been registered by that name. If so, return it. Second, attempt to
+  * read ``./NAME.lisp`` as a lisp file, and return that as a module. Raise error
+  * on failure.
+  *
+  * @param rt runtime
+  * @param name name of module to import
+  * @return the resulting module
+  */
+ lisp_module *lisp_do_import(lisp_runtime *rt, lisp_symbol *name);
+ 
+ /**
+  * @}
   * @defgroup embed Embedding API
   * @{
   */


@@ 864,6 945,7 @@ LE_EXIT,     /* exit the interpreter */
  	LE_ASSERT,   /* assertion error */
  	LE_VALUE,    /* invalid argument */
+ 	LE_ERRNO,    /* used for C library errors, does perror() */
  
  	LE_MAX_ERR   /* not a real error, don't use */
  };


@@ 882,11 964,11 @@ * @endcode
   *
   * @param rt runtime
-  * @param errno error number, for easy programatic acccess
+  * @param err_num error number, for easy programatic acccess
   * @param message message to show the user
   * @return NULL
   */
- lisp_value *lisp_error(lisp_runtime *rt, enum lisp_errno errno, char *message);
+ lisp_value *lisp_error(lisp_runtime *rt, enum lisp_errno err_num, char *message);
  
  /**
   * Dump the execution stack to a file. This is useful if you want to print a

A scripts/tests/modules.lisp => scripts/tests/modules.lisp +12 -0
@@ 0,0 1,12 @@
+ (assert (equal? '(getattr a 'b) 'a.b))
+ (assert (equal? '(getattr (getattr one 'two) 'three) 'one.two.three))
+ 
+ (import os)
+ 
+ (import filter)
+ 
+ (assert (equal?
+           (filter.filter (lambda (v) (< v 3)) '(1 2 3 4 5))
+           '(1 2)))
+ 
+ ; OUTPUT(0)

M src/builtins.c => src/builtins.c +45 -5
@@ 619,7 619,7 @@ /* args are NOT evaluated, to avoid error handling short circuit */
  	lisp_value *sym, *expr;
  	lisp_symbol *sym_evald;
- 	enum lisp_errno errno;
+ 	enum lisp_errno err_num;
  	(void) user;
  
  	if (!lisp_get_args(rt, arglist, "**", &sym, &expr))


@@ 629,19 629,19 @@ lisp_error_check(sym_evald);
  	if (sym_evald->type != type_symbol)
  		return lisp_error(rt, LE_TYPE, "error type must be symbol");
- 	errno = lisp_sym_to_errno(sym_evald);
- 	if (errno == LE_MAX_ERR)
+ 	err_num = lisp_sym_to_errno(sym_evald);
+ 	if (err_num == LE_MAX_ERR)
  		return lisp_error(rt, LE_VALUE, "unrecognized error type");
  
  	lisp_eval(rt, scope, expr); /* we don't care, cause we expect error */
  	/* NO ERROR CHECK HERE */
  
- 	if (errno == lisp_get_errno(rt)) {
+ 	if (err_num == lisp_get_errno(rt)) {
  		lisp_clear_error(rt);
  		return (lisp_value*) sym_evald;
  	} else {
  		fprintf(stderr, "Assertion error! Expected %s\n",
- 			lisp_error_name[errno]);
+ 			lisp_error_name[err_num]);
  		fprintf(stderr, "This was the actual error encountered: ");
  		lisp_print_error(rt, stderr);
  		fprintf(stderr, "\nBelow should be the assertion error stack trace.\n");


@@ 742,6 742,44 @@ return lisp_progn(rt, new_scope, expressions);
  }
  
+ static lisp_value *lisp_builtin_import(
+ 		lisp_runtime *rt, lisp_scope *scope, lisp_list *arglist, void *user)
+ {
+ 	/*
+ 	 * args are NOT evaluated
+ 	 * (import symbol)  ->  looks up module "symbol" and inserts it into
+ 	 * current scope
+ 	 */
+ 	lisp_symbol *sym;
+ 	lisp_module *mod;
+ 
+ 	(void) user; /* unused */
+ 
+ 	if (!lisp_get_args(rt, arglist, "s", &sym))
+ 		return NULL;
+ 
+ 	mod = lisp_do_import(rt, sym);
+ 	lisp_error_check(mod);
+ 
+ 	lisp_scope_bind(scope, sym, (lisp_value*)mod);
+ 	return (lisp_value*)mod;
+ }
+ 
+ static lisp_value *lisp_builtin_getattr(
+ 		lisp_runtime *rt, lisp_scope *scope, lisp_list *arglist, void *user)
+ {
+ 	lisp_module *mod;
+ 	lisp_symbol *sym;
+ 
+ 	(void) user;
+ 	(void) scope; /* unused */
+ 
+ 	if (!lisp_get_args(rt, arglist, "*s", &mod, &sym))
+ 		return NULL;
+ 
+ 	return lisp_scope_lookup(rt, mod->contents, sym);
+ }
+ 
  void lisp_scope_populate_builtins(lisp_runtime *rt, lisp_scope *scope)
  {
  	lisp_scope_add_builtin(rt, scope, "eval", lisp_builtin_eval, NULL, 1);


@@ 779,4 817,6 @@ lisp_scope_add_builtin(rt, scope, "cond", lisp_builtin_cond, NULL, 0);
  	lisp_scope_add_builtin(rt, scope, "list", lisp_builtin_list, NULL, 1);
  	lisp_scope_add_builtin(rt, scope, "let", lisp_builtin_let, NULL, 0);
+ 	lisp_scope_add_builtin(rt, scope, "import", lisp_builtin_import, NULL, 0);
+ 	lisp_scope_add_builtin(rt, scope, "getattr", lisp_builtin_getattr, NULL, 1);
  }

M src/funlisp_internal.h => src/funlisp_internal.h +17 -4
@@ 64,15 64,15 @@ /* Data we use for reporting errors. This is very single-threaded of me,
  	 * but it works for now.
  	 * REQUIREMENTS:
- 	 *   - error and errno must both always be non-NULL and non-0
+ 	 *   - error and err_num must both always be non-NULL and non-0
  	 *     respectively when an error occurs
  	 *   - error_line and error_stack may optionally be set
- 	 * You can use lisp_error() to set error, errno, and error_stack.
- 	 * Parsing functions typically use a macro to set error, errno, and
+ 	 * You can use lisp_error() to set error, err_num, and error_stack.
+ 	 * Parsing functions typically use a macro to set error, err_num, and
  	 * error_line.
  	 */
  	char *error;
- 	enum lisp_errno errno;
+ 	enum lisp_errno err_num;
  	unsigned int error_line;
  	lisp_list *error_stack;
  


@@ 84,6 84,8 @@ struct hashtable *symcache;
  	/* Maintain cache of lisp_string */
  	struct hashtable *strcache;
+ 	/* Maintain builtin module list */
+ 	lisp_scope *modules;
  };
  
  /* The below ARE lisp_values! */


@@ 143,6 145,13 @@ int lambda_type;
  };
  
+ struct lisp_module {
+ 	LISP_VALUE_HEAD;
+ 	lisp_scope *contents;
+ 	lisp_string *name;
+ 	lisp_string *file;
+ };
+ 
  /**
   * A function which consumes a single ::lisp_value and produces a new one as a
   * result.


@@ 186,4 195,8 @@ void lisp_textcache_remove(struct hashtable *cache, struct lisp_text *t);
  
  int lisp_truthy(lisp_value *v);
+ 
+ lisp_module *create_os_module(lisp_runtime *rt);
+ lisp_module *lisp_lookup_module(lisp_runtime *rt, lisp_symbol *name);
+ 
  #endif

M src/gc.c => src/gc.c +4 -0
@@ 24,6 24,9 @@ rt->stack_depth = 0;
  	rt->symcache = NULL;
  	rt->strcache = NULL;
+ 	rt->modules = lisp_new_empty_scope(rt);
+ 
+ 	lisp_register_module(rt, create_os_module(rt));
  }
  
  void lisp_destroy(lisp_runtime *rt)


@@ 70,6 73,7 @@ if (rt->error_stack)
  		lisp_mark(rt, (lisp_value *) rt->error_stack);
  	lisp_mark(rt, (lisp_value *) rt->stack);
+ 	lisp_mark(rt, (lisp_value *) rt->modules);
  }
  
  void lisp_sweep(lisp_runtime *rt)

M src/iter.c => src/iter.c +45 -1
@@ 7,6 7,7 @@ #include <stdlib.h>
  #include <stddef.h>
  #include <stdint.h>
+ #include <stdarg.h>
  
  #include "iter.h"
  


@@ 94,7 95,7 @@ }
  
  struct iterator iterator_concat3(struct iterator a, struct iterator b,
- 																 struct iterator c)
+ 				 struct iterator c)
  {
  	struct iterator *arr = calloc(sizeof(struct iterator), 3);
  	arr[0] = a;


@@ 124,3 125,46 @@ it.close=iterator_close_noop;
  	return it;
  }
+ 
+ static bool array_has_next(struct iterator *iter)
+ {
+ 	return iter->index < iter->state_int;
+ }
+ 
+ static void *array_next(struct iterator *iter)
+ {
+ 	return ((void**)iter->ds)[iter->index++];
+ }
+ 
+ static void array_close(struct iterator *iter)
+ {
+ 	if ((bool) iter->state_ptr)
+ 		free(iter->ds);
+ }
+ 
+ struct iterator iterator_array(void **array, int len, bool own)
+ {
+ 	struct iterator it = {0};
+ 	it.index = 0;
+ 	it.ds = array;
+ 	it.state_int = len;
+ 	it.state_ptr = (void*) own;
+ 	it.has_next = array_has_next;
+ 	it.next = array_next;
+ 	it.close = array_close;
+ 	return it;
+ }
+ 
+ struct iterator iterator_from_args(int n, ...)
+ {
+ 	void **array = calloc(sizeof(void*), n);
+ 	va_list va;
+ 	int i;
+ 
+ 	va_start(va, n);
+ 	for (i = 0; i < n; i++)
+ 		array[i] = va_arg(va, void*);
+ 	va_end(va);
+ 
+ 	return iterator_array(array, n, true);
+ }

M src/iter.h => src/iter.h +4 -0
@@ 36,4 36,8 @@ struct iterator iterator_concat3(
  	struct iterator a, struct iterator b, struct iterator c);
  
+ /* return an iterator that yields an array */
+ struct iterator iterator_array(void **array, int len, bool own);
+ struct iterator iterator_from_args(int n, ...);
+ 
  #endif

A src/module.c => src/module.c +112 -0
@@ 0,0 1,112 @@
+ /*
+  * Module-related functionality. Includes builtin modules.
+  */
+ #include <stdlib.h>
+ #include <string.h>
+ 
+ #include "funlisp_internal.h"
+ 
+ static lisp_value *lisp_os_getenv(lisp_runtime *rt, lisp_scope *scope,
+                                   lisp_list *arguments, void *user)
+ {
+ 	lisp_string *str;
+ 	char *res;
+ 
+ 	/* args evaluated */
+ 	(void) user;
+ 	(void) scope;
+ 
+ 	if (!lisp_get_args(rt, arguments, "S", &str))
+ 		return NULL;
+ 
+ 	res = getenv(str->s);
+ 	if (res)
+ 		return (lisp_value*) lisp_string_new(rt, res, LS_CPY | LS_OWN);
+ 	else
+ 		return lisp_nil_new(rt);
+ }
+ 
+ lisp_module *create_os_module(lisp_runtime *rt)
+ {
+ 
+ 	lisp_module *m = lisp_new_module(rt, lisp_string_new(rt, "os", 0),
+ 		lisp_string_new(rt, __FILE__, 0));
+ 	lisp_scope_add_builtin(rt, m->contents, "getenv", lisp_os_getenv, NULL, 1);
+ 	return m;
+ }
+ 
+ void lisp_register_module(lisp_runtime *rt, lisp_module *m)
+ {
+ 	/* TODO this depends on symbols and strings hashing to same value and
+ 	 * comparing equal */
+ 	lisp_scope_bind(rt->modules, (lisp_symbol*)m->name, (lisp_value*) m);
+ }
+ 
+ lisp_module *lisp_lookup_module(lisp_runtime *rt, lisp_symbol *name)
+ {
+ 	lisp_module *m = (lisp_module*) lisp_scope_lookup(rt, rt->modules, name);
+ 
+ 	if (!m) /* not found is not necessarily an error */
+ 		lisp_clear_error(rt);
+ 
+ 	return m;
+ }
+ 
+ lisp_module *lisp_new_module(lisp_runtime *rt, lisp_string *name, lisp_string *file)
+ {
+ 	lisp_module *m = (lisp_module *)lisp_new(rt, type_module);
+ 	m->name = name;
+ 	m->file = file;
+ 	m->contents = lisp_new_empty_scope(rt);
+ 	return m;
+ }
+ 
+ lisp_scope *lisp_module_get_scope(lisp_module *module)
+ {
+ 	return module->contents;
+ }
+ 
+ lisp_module *lisp_import_file(lisp_runtime *rt, lisp_string *name, lisp_string *file)
+ {
+ 	FILE *f;
+ 	lisp_scope *builtins = lisp_new_default_scope(rt);
+ 	lisp_scope *modscope = lisp_new_empty_scope(rt);
+ 	lisp_module *module;
+ 	lisp_value *v;
+ 	modscope->up = builtins;
+ 
+ 	f = fopen(file->s, "r");
+ 	if (!f) {
+ 		return (lisp_module*) lisp_error(rt, LE_ERRNO, "error opening file for import");
+ 	}
+ 
+ 	v = lisp_load_file(rt, modscope, f);
+ 	lisp_error_check(v);
+ 
+ 	module = (lisp_module *)lisp_new(rt, type_module);
+ 	module->contents = modscope;
+ 	module->name = name;
+ 	module->file = file;
+ 	lisp_register_module(rt, module);
+ 	return module;
+ }
+ 
+ lisp_module *lisp_do_import(lisp_runtime *rt, lisp_symbol *name)
+ {
+ 	lisp_module *m;
+ 	lisp_string *file, *namestr;
+ 	char *filestr;
+ 	int len;
+ 
+ 	m = lisp_lookup_module(rt, name);
+ 	if (m)
+ 		return m;
+ 
+ 	len = strlen(name->s);
+ 	len += 1 /*nul*/ + 7 /*./ .lisp */;
+ 	filestr = malloc(len);
+ 	sprintf(filestr, "./%s.lisp", name->s);
+ 	file = lisp_string_new(rt, filestr, LS_OWN);
+ 	namestr = lisp_symbol_new(rt, name->s, LS_OWN | LS_CPY);
+ 	return lisp_import_file(rt, namestr, file);
+ }

M src/parse.c => src/parse.c +67 -3
@@ 175,21 175,85 @@ }
  }
  
+ static lisp_value *split_symbol(lisp_runtime *rt, char *string, int dotcount, int remain)
+ {
+ 	char *delim, *tok;
+ 	int i, len;
+ 	lisp_value *prev = NULL;
+ 	lisp_symbol *sym;
+ 	lisp_list *list;
+ 	lisp_symbol *getattr = lisp_symbol_new(rt, "getattr", 0);
+ 
+ 	/* Create the first symbol, which is the left hand side */
+ 	delim = strchr(string, '.');
+ 	len = (int) (delim - string);
+ 	tok = malloc(len + 1);
+ 	strncpy(tok, string, len);
+ 	tok[len] = '\0';
+ 	sym = lisp_symbol_new(rt, tok, LS_OWN);
+ 	prev = (lisp_value*) sym;
+ 	string = delim + 1;
+ 	remain -= len + 1;
+ 
+ 	/* Create a "getattr" for each right hand side remaining */
+ 	for (i = 0; i < dotcount; i++) {
+ 		/* Attribute symbol */
+ 		if (i < dotcount - 1) {
+ 			delim = strchr(string, '.');
+ 			len = (int) (delim - string);
+ 		} else {
+ 			len = remain;
+ 		}
+ 		tok = malloc(len + 1);
+ 		strncpy(tok, string, len);
+ 		tok[len] = '\0';
+ 		sym = lisp_symbol_new(rt, tok, LS_OWN);
+ 
+ 		/* Create (getattr PREV 'tok) */
+ 		list = lisp_list_new(rt,
+ 			(lisp_value *) getattr,
+ 			(lisp_value *) lisp_list_new(rt,
+ 				prev,
+ 				(lisp_value *) lisp_list_new(rt,
+ 					(lisp_value *) lisp_quote_with(rt, (lisp_value *) sym, "quote"),
+ 					lisp_nil_new(rt)
+ 				)
+ 			)
+ 		);
+ 		prev = (lisp_value *) list;
+ 		string = delim + 1;
+ 		remain -= len + 1;
+ 	}
+ 	return prev;
+ }
+ 
  static result lisp_parse_symbol(lisp_runtime *rt, char *input, int index)
  {
  	int n = 0;
+ 	int dotcount = 0;
  	char *copy;
  	lisp_symbol *s;
  
  	while (input[index + n] && !isspace(input[index + n]) &&
- 	       input[index + n] != ')' && input[index + n] != '.' &&
+ 	       input[index + n] != ')' && /* input[index + n] != '.' && */
  	       input[index + n] != '\'' && input[index + n] != COMMENT) {
+ 		if (input[index + n] == '.')
+ 			dotcount++;
  		n++;
  	}
  	if (!input[index]) {
  		rt->error = "unexpected eof while parsing symbol";
  		return_result_err(NULL, index, LE_EOF);
  	}
+ 
+ 	if (dotcount) {
+ 		if (input[index] == '.' || input[index + n - 1] == '.') {
+ 			rt->error = "unexpected '.' at beginning or end of symbol";
+ 			return_result_err(NULL, index, LE_SYNTAX);
+ 		}
+ 		return_result(split_symbol(rt, input + index, dotcount, n), index + n);
+ 	}
+ 
  	copy = malloc(n + 1);
  	strncpy(copy, input + index, n);
  	copy[n] = '\0';


@@ 263,7 327,7 @@ result r = lisp_parse_value_internal(rt, input, index);
  	bytes = r.index - index;
  	if (r.error) {
- 		rt->errno = r.error;
+ 		rt->err_num = r.error;
  		set_error_lineno(rt, input, r.index);
  		bytes = -1;
  	}


@@ 327,7 391,7 @@ input_string = read_file(input);
  	if (!input_string) {
  		rt->error = "error reading from input file";
- 		rt->errno = LE_FERROR;
+ 		rt->err_num = LE_FERROR;
  		return NULL;
  	}
  	result = lisp_parse_progn(rt, input_string);

M src/types.c => src/types.c +58 -1
@@ 179,7 179,7 @@ lisp_scope *scope = (lisp_scope *) v;
  	if (scope->up) {
  		return iterator_concat3(
- 			iterator_single_value(&scope->up),
+ 			iterator_single_value(scope->up),
  			ht_iter_keys_ptr(&scope->scope),
  			ht_iter_values_ptr(&scope->scope)
  		);


@@ 795,3 795,60 @@ {
  	return self->type->compare(self, other);
  }
+ 
+ /*
+  * module
+  */
+ 
+ static void module_print(FILE *f, lisp_value*v);
+ static lisp_value *module_new(lisp_runtime *rt);
+ static struct iterator module_expand(lisp_value *);
+ static int module_compare(lisp_value *self, lisp_value *other);
+ 
+ static lisp_type type_module_obj = {
+ 	TYPE_HEADER,
+ 	/* name */ "module",
+ 	/* print */ module_print,
+ 	/* new */ module_new,
+ 	/* free */ simple_free,
+ 	/* expand */ module_expand,
+ 	/* eval */ eval_error,
+ 	/* call */ call_error,
+ 	/* compare */ module_compare,
+ };
+ lisp_type *type_module = &type_module_obj;
+ 
+ static lisp_value *module_new(lisp_runtime *rt)
+ {
+ 	lisp_module *module;
+ 	(void) rt; /* unused */
+ 
+ 	module = malloc(sizeof(lisp_module));
+ 	module->contents = NULL;
+ 	module->name = NULL;
+ 	module->name = NULL;
+ 	return (lisp_value*)module;
+ }
+ 
+ static void module_print(FILE *f, lisp_value *v)
+ {
+ 	lisp_module *module = (lisp_module*) v;
+ 	fprintf(f, "<module '%s' from '%s' at 0x%p>",
+ 			module->name->s, module->file->s, (void*)module);
+ }
+ 
+ static struct iterator module_expand(lisp_value *v)
+ {
+ 	lisp_module *module = (lisp_module *) v;
+ 	return iterator_from_args(3,
+ 		module->name,
+ 		module->file,
+ 		module->contents
+ 	);
+ }
+ 
+ static int module_compare(lisp_value *self, lisp_value *other)
+ {
+ 	/* Compare by value for simplicity. */
+ 	return self == other;
+ }

M src/util.c => src/util.c +17 -8
@@ 12,6 12,7 @@ #include <string.h>
  #include <stdlib.h>
  #include <stdio.h>
+ #include <errno.h>
  
  #include "funlisp_internal.h"
  #include "hashtable.h"


@@ 33,6 34,7 @@ "LE_EXIT",
  	"LE_ASSERT",
  	"LE_VALUE",
+ 	"LE_ERRNO",
  };
  
  void lisp_scope_bind(lisp_scope *scope, lisp_symbol *symbol, lisp_value *value)


@@ 181,7 183,7 @@ type = lisp_get_type(*format);
  		if (type != NULL && type != list->left->type) {
  			rt->error = "incorrect argument type";
- 			rt->errno = LE_TYPE;
+ 			rt->err_num = LE_TYPE;
  			return 0;
  		}
  		*v = list->left;


@@ 190,11 192,11 @@ }
  	if (*format != '\0') {
  		rt->error = "not enough arguments";
- 		rt->errno = LE_2FEW;
+ 		rt->err_num = LE_2FEW;
  		return 0;
  	} else if(!lisp_nil_p((lisp_value*)list)) {
  		rt->error = "too many arguments";
- 		rt->errno = LE_2MANY;
+ 		rt->err_num = LE_2MANY;
  		return 0;
  	}
  	return 1;


@@ 385,10 387,10 @@ }
  }
  
- lisp_value *lisp_error(lisp_runtime *rt, enum lisp_errno errno, char *message)
+ lisp_value *lisp_error(lisp_runtime *rt, enum lisp_errno err_num, char *message)
  {
  	rt->error = message;
- 	rt->errno = errno;
+ 	rt->err_num = err_num;
  	rt->error_stack = rt->stack;
  	return NULL;
  }


@@ 400,7 402,7 @@   enum lisp_errno lisp_get_errno(lisp_runtime *rt)
  {
- 	return rt->errno;
+ 	return rt->err_num;
  }
  
  void lisp_clear_error(lisp_runtime *rt)


@@ 408,11 410,12 @@ rt->error = NULL;
  	rt->error_stack = NULL;
  	rt->error_line = 0;
- 	rt->errno = 0;
+ 	rt->err_num = 0;
  }
  
  void lisp_print_error(lisp_runtime *rt, FILE *file)
  {
+ 	char *errmsg;
  	if (!rt->error) {
  		fprintf(stderr, "BUG: lisp_print_error() expects error, found none\n");
  		return;


@@ 421,7 424,13 @@ if (rt->error_line)
  		fprintf(file, "at line %d: ", rt->error_line);
  
- 	fprintf(file, "Error %s: %s\n", lisp_error_name[rt->errno], rt->error);
+ 	if (rt->err_num == LE_ERRNO) {
+ 		errmsg = strerror(errno);
+ 		fprintf(file, "Error %s: %s\nSystem error: %s\n",
+ 			lisp_error_name[rt->err_num], rt->error, errmsg);
+ 	} else {
+ 		fprintf(file, "Error %s: %s\n", lisp_error_name[rt->err_num], rt->error);
+ 	}
  
  	if (rt->error_stack)
  		lisp_dump_stack(rt, rt->error_stack, file);