Add load(), load_library(), lisp std
diff --git a/.vscode/launch.json b/.vscode/launch.json
index c265161..0597115 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -12,7 +12,12 @@
             "args": ["test-closures.lisp"],
             "stopAtEntry": false,
             "cwd": "${workspaceFolder}/src/lisp",
-            "environment": [],
+            "environment": [
+                {
+                    "name": "LISP_LIBRARY_PATH",
+                    "value": "${workspaceFolder}/lib/lisp"
+                }
+            ],
             "externalConsole": false,
             "MIMode": "gdb",
             "setupCommands": [
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 54186f5..7f1a121 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -5,6 +5,11 @@
         "Jmk.options": "makefile",
         "Jmk": "makefile",
         "*.dasc": "c",
-        "typeinfo": "c"
+        "*.sass": "plaintext",
+        "typeinfo": "c",
+        "atomic": "cpp",
+        "memory_resource": "cpp",
+        "functional": "cpp",
+        "iostream": "cpp"
     }
 }
\ No newline at end of file
diff --git a/README.md b/README.md
index f80c12a..774e14f 100644
--- a/README.md
+++ b/README.md
@@ -43,11 +43,12 @@
   - [ ] Memory management API (`sbrk`, `mmap`, etc)
   - [ ] Process/thread API (`spawn_process`, `spawn_thread`, etc)
 - [ ] Lisp compiler
-  - [ ] JIT compiler using dynasm
+  - [x] JIT compiler using dynasm
     - [x] Basic compilation
     - [x] GC
-    - [ ] Lexical closures
+    - [x] Lexical closures
   - [ ] Standard library (in progress)
+    - [ ] CLOS-style OO library
 - [ ] Lisp integrated into kernel
 - [ ] User-space driver API
   - [ ] Lisp API
diff --git a/lib/lisp/std/std.lisp b/lib/lisp/std/std.lisp
new file mode 100644
index 0000000..096b885
--- /dev/null
+++ b/lib/lisp/std/std.lisp
@@ -0,0 +1,26 @@
+;;;; std.lisp -- Lisp standard library
+
+(defun caar (val)
+  (car (car val)))
+
+(defun cadr (val)
+  (car (cdr val)))
+ 
+(defun caddr (val)
+  (car (cdr (cdr val))))
+
+(defun cadar (val)
+  (car (cdr (car val))))
+
+(defun caddar (val)
+  (car (cdr (cdr (car val)))))
+
+;; Instead of a function this is a macro for a slight performance increase
+(defmacro not (val)
+  (nilp val))
+
+;; TODO: make tail recursive (for this `flet` would be nice)
+(defun length (list)
+  (if (nilp list)
+    0
+    (+ 1 (length (cdr list)))))
diff --git a/src/lisp/Jmk b/src/lisp/Jmk
index 7c1fe8e..628a33d 100644
--- a/src/lisp/Jmk
+++ b/src/lisp/Jmk
@@ -38,14 +38,15 @@
 type(executable)
 
 F ?= test.lisp
+lisp_libpath = $(ROOT)/lib/lisp
 
 run: lisp
 	status_log(LISP, $(F))
-	@./lisp $(F)
+	@LISP_LIBRARY_PATH="$(lisp_libpath)" ./lisp $(F)
 
 leak-check: lisp
 	status_log(VALGRIND, lisp $(F))
-	@valgrind --leak-check=full ./lisp $(F)
+	@LISP_LIBRARY_PATH="$(lisp_libpath)" valgrind --leak-check=full ./lisp $(F)
 
 format:
 	status_log(FORMAT, *)
diff --git a/src/lisp/compiler.dasc b/src/lisp/compiler.dasc
index c2c0639..2807967 100644
--- a/src/lisp/compiler.dasc
+++ b/src/lisp/compiler.dasc
@@ -44,14 +44,10 @@
 
 struct function *find_function(struct environment *env, char *name)
 {
-	struct function *f = env->first;
+	struct function *f;
 
-	while (strcmp(f->name, name) != 0)
+	for (f = env->first; f && strcmp(f->name, name); f = f->prev)
 	{
-		if (f->prev)
-			f = f->prev;
-		else
-			return NULL;
 	}
 
 	return f;
@@ -106,6 +102,26 @@
 		// We're not gonna bother munmap()ing the function
 		free(f);
 	}
+
+	for (struct loaded_file *next, *l = env->first_loaded; l; l = next)
+	{
+		next = l->previous;
+		free(l->resolved_path);
+		free(l);
+	}
+}
+
+void add_load(struct environment *env, char *path)
+{
+	static char buffer[512];
+	long size = readlink(path, buffer, 512);
+	buffer[size] = '\0';
+	char *resolved = strdup(buffer);
+
+	struct loaded_file *f = malloc(sizeof(struct loaded_file));
+	f->resolved_path = resolved;
+	f->previous = env->first_loaded;
+	env->first_loaded = f;
 }
 
 struct dasm_State *compile_function(value_t args, enum namespace namespace,
@@ -201,14 +217,22 @@
 		struct local local;
 		int nargs;
 		char *name = (char *)(car(args) ^ SYMBOL_TAG);
+
 		dasm_State *d = compile_function(cdr(args), namespace, env, &local, NULL, &nargs, name);
 
-		add_function(env, name, link(&d),
+		add_function(env, name, link_program(&d),
 		             nargs, namespace);
 
 		dasm_free(&d);
 		del_local(&local);
 	}
+	else if (symstreq(form, "progn"))
+	{
+		for (value_t val = args; !nilp(val); val = cdr(val))
+		{
+			compile_tl(car(val), env);
+		}
+	}
 }
 
 void walk_and_alloc(struct local *local, value_t body)
@@ -245,23 +269,48 @@
 	}
 }
 
-struct environment compile_all(struct istream *is)
+bool load(struct environment *env, char *path)
 {
+	if (!file_exists(path))
+		return false;
+
+	add_load(env, path);
+
 	unsigned char pool = make_pool();
 	unsigned char pop = push_pool(pool);
 
+	struct istream *is = new_fistream(path, false);
+	if (!is)
+		return false;
+
 	value_t val;
-	struct environment env;
-	env.first = NULL;
-	load_std(&env);
 
 	while (read1(is, &val))
 	{
-		compile_tl(val, &env);
+		compile_tl(val, env);
 	}
 
+	del_fistream(is);
 	pop_pool(pop);
 
+	return true;
+}
+
+struct environment compile_file(char *filename, bool *ok)
+{
+	value_t val;
+	struct environment env;
+	env.first = NULL;
+	env.first_loaded = NULL;
+
+	add_load(&env, filename);
+	load_std(&env);
+
+	bool ok_ = load(&env, filename);
+
+	if (ok)
+		*ok = ok_;
+
 	return env;
 }
 
@@ -358,6 +407,13 @@
 			    compile_expression(env, local, elt(args, 2), Dst);
 			|=>after_label:
 		}
+		else if (symstreq(fsym, "progn"))
+		{
+			for (value_t val = args; !nilp(val); val = cdr(val))
+			{
+				compile_expression(env, local, car(val), Dst);
+			}
+		}
 		else if (symstreq(fsym, "let1"))
 		{
 			if (nargs < 2)
@@ -460,7 +516,7 @@
 			dasm_State *d = compile_function(args, NS_ANONYMOUS, env, &new_local, local, &nargs_out, "recurse");
 
 			// Link the function
-			void *func_ptr = link(&d);
+			void *func_ptr = link_program(&d);
 
 			// Create a closure object with the correct number of captures at
 			// runtime
@@ -554,8 +610,13 @@
 			}
 			else if (func->namespace == NS_MACRO)
 			{
+				// Make sure that the stuff allocated by the macro isn't in a pool
+				unsigned char pool = push_pool(0);
+
 				value_t expanded_to = call_list(func, args);
 
+				pop_pool(pool);
+
 				compile_expression(env, local, expanded_to, Dst);
 			}
 		}
@@ -584,7 +645,7 @@
 
 	| cleanup;
 
-	add_function(env, name, link(Dst), 0, NS_FUNCTION);
+	add_function(env, name, link_program(Dst), 0, NS_FUNCTION);
 }
 
 struct variable *add_variable(struct local *local, enum var_type type,
diff --git a/src/lisp/compiler.h b/src/lisp/compiler.h
index d70cac6..62b43c0 100644
--- a/src/lisp/compiler.h
+++ b/src/lisp/compiler.h
@@ -34,9 +34,16 @@
 	struct function *prev;
 };
 
+struct loaded_file
+{
+	char *resolved_path;
+	struct loaded_file *previous;
+};
+
 struct environment
 {
 	struct function *first;
+	struct loaded_file *first_loaded;
 };
 
 enum var_type
@@ -136,7 +143,17 @@
 void walk_and_alloc(struct local *local, value_t body);
 // Compile top-level declaration
 void compile_tl(value_t val, struct environment *env);
-struct environment compile_all(struct istream *is);
+
+/**
+ * Compile a file in a new environment.
+ * @param filename The path to the file.
+ * @param ok Set to `true` if loading succeeds, `false` otherwise. If `ok` is
+ * NULL it is ignored.
+ * @returns The environment for the compiled file, or an empty environment if
+ * `ok` was set to `false` (i.e. the file could not be compiled).
+ */
+struct environment compile_file(char *filename, bool *ok);
+
 struct function *find_function(struct environment *env, char *name);
 struct variable *add_variable(struct local *local, enum var_type type,
                               char *name, int number);
@@ -154,3 +171,16 @@
  */
 value_t call_list(struct function *func, value_t list);
 value_t call_list_closure(struct closure *c, value_t list);
+
+/**
+ * Load a lisp file into the current environment.
+ * @returns `true` if succesful, `false` otherwise.
+ */
+bool load(struct environment *env, char *path);
+
+/**
+ * Mark a file `path` as loaded in the environment. `path` will be expanded with
+ * `readlink`. You can pass a temporary string here, memory will be allocated by
+ * this function as needed.
+ */
+void add_load(struct environment *env, char *path);
diff --git a/src/lisp/lib/std.c b/src/lisp/lib/std.c
index 028dd3d..c7efca0 100644
--- a/src/lisp/lib/std.c
+++ b/src/lisp/lib/std.c
@@ -1,5 +1,7 @@
 #include "std.h"
+#include "../plat/plat.h"
 #include <stdlib.h>
+#include <string.h>
 
 value_t l_plus(value_t a, value_t b)
 {
@@ -78,4 +80,37 @@
 	add_function(env, "print", l_printval, 1, NS_FUNCTION);
 
 	add_function(env, "apply", l_apply, 2, NS_FUNCTION);
+
+	if (!load_library(env, "std"))
+	{
+		err("Could not load library `std`, make sure your $LISP_LIBRARY_PATH is correct.");
+	}
+}
+
+bool load_library(struct environment *env, char *name)
+{
+	char *lib_paths = getenv("LISP_LIBRARY_PATH");
+
+	if (!lib_paths)
+		lib_paths = "/lib/lisp";
+
+	for (char *p = strtok(lib_paths, ":"); p; p = strtok(NULL, ":"))
+	{
+		static char path[512];
+		snprintf(path, 512, "%s/%s.lisp", p, name);
+
+		if (file_exists(path))
+		{
+			return load(env, path);
+		}
+
+		snprintf(path, 512, "%s/%s/%s.lisp", p, name, name);
+
+		if (file_exists(path))
+		{
+			return load(env, path);
+		}
+	}
+
+	return false;
 }
diff --git a/src/lisp/lib/std.h b/src/lisp/lib/std.h
index 9070de8..a117b5d 100644
--- a/src/lisp/lib/std.h
+++ b/src/lisp/lib/std.h
@@ -8,3 +8,4 @@
 
 void add_function(struct environment *env, char *name, void *func, int nargs, enum namespace ns);
 void load_std(struct environment *env);
+bool load_library(struct environment *env, char *name);
diff --git a/src/lisp/main.c b/src/lisp/main.c
index 94bdeaf..1ece9ae 100644
--- a/src/lisp/main.c
+++ b/src/lisp/main.c
@@ -1,6 +1,7 @@
 #include "compiler.h"
 #include "lisp.h"
 #include "gc.h"
+#include "plat/plat.h"
 
 int main(int argc, char **argv)
 {
@@ -10,15 +11,15 @@
 		return 1;
 	}
 
-	struct istream *is = new_fistream(argv[1], false);
+	bool ok;
+	struct environment env = compile_file(argv[1], &ok);
 
-	if (is == NULL)
+	if (!ok)
 	{
 		fprintf(stderr, "Could not open %s\n", argv[1]);
 		return 1;
 	}
 
-	struct environment env = compile_all(is);
 	value_t (*lisp_main)() = find_function(&env, "main")->def0;
 
 	gc_set_base_here();
@@ -26,5 +27,4 @@
 
 	free_all();
 	del_env(&env);
-	del_fistream(is);
 }
diff --git a/src/lisp/plat/linux.c b/src/lisp/plat/linux.c
index 4a223a6..b71f342 100644
--- a/src/lisp/plat/linux.c
+++ b/src/lisp/plat/linux.c
@@ -2,6 +2,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/mman.h>
+#include <unistd.h>
 
 void *malloc_aligned(size_t size)
 {
@@ -20,7 +21,7 @@
 	free(addr);
 }
 
-void *link(dasm_State **Dst)
+void *link_program(dasm_State **Dst)
 {
 	size_t size;
 	void *buf;
@@ -33,3 +34,8 @@
 	mprotect(buf, size, PROT_READ | PROT_EXEC);
 	return buf;
 }
+
+bool file_exists(const char *path)
+{
+	return access(path, F_OK) == 0;
+}
diff --git a/src/lisp/plat/plat.h b/src/lisp/plat/plat.h
index 3640005..1c4216a 100644
--- a/src/lisp/plat/plat.h
+++ b/src/lisp/plat/plat.h
@@ -3,6 +3,8 @@
 #include <dasm_proto.h>
 #include <stddef.h>
 #include <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
 
 /* Platform specific definitions */
 
@@ -11,6 +13,9 @@
 void *realloc_aligned(void *addr, size_t size);
 void free_aligned(void *addr);
 
-void *link(dasm_State **Dst);
+void *link_program(dasm_State **Dst);
+
+extern ssize_t readlink(const char *pathname, char *buf, size_t buf_size);
+bool file_exists(const char *path);
 
 #define THREAD_LOCAL