diff --git a/Makefile b/Makefile
index c07ad4c..4813818 100644
--- a/Makefile
+++ b/Makefile
@@ -3,9 +3,9 @@
 test: stresstest cleanexec
 
 CFLAGS += -g1 -O0 -Wuninitialized -Wno-unused-command-line-argument -lm -lreadline
-ifeq ($(MODE), cpp)
+ifeq ($(CPP), 1)
 	CC := g++
-	CFLAGS += --std=gnu++2c
+	CFLAGS += --std=gnu++2c -Wdeprecated
 else
 	CFLAGS += --std=gnu2x
 endif
@@ -29,7 +29,11 @@ cleanexec:
 	rm -f cogni *.o
 
 stresstest: cogni
+ifeq ($(PROFILE), 1)
+	python3 run_tests.py --profile
+else
 	python3 run_tests.py
+endif
 
 cleanlogs:
 	rm -f cognac/tests/*.log
diff --git a/cog_regex.c b/cog_regex.c
index 459e6b0..d4f80d2 100644
--- a/cog_regex.c
+++ b/cog_regex.c
@@ -20,7 +20,7 @@
         char* errmsg = (char*)alloca(size + 1); \
 		regerror(status, &reg, errmsg, size); \
         regfree(&reg); \
-        COG_RETURN_ERROR(cog_sprintf("Error compiling regex %O: %s", pat, errmsg)); \
+        COG_RETURN_ERROR(Regex-error, cog_sprintf("Error compiling regex %O: %s", pat, errmsg)); \
     }
 
 #define _REGEX_RUN_ERR \
@@ -29,7 +29,7 @@
         char* errmsg = (char*)alloca(size + 1); \
 		regerror(status, &reg, errmsg, size); \
         regfree(&reg); \
-        COG_RETURN_ERROR(cog_sprintf("Error running regex %O on string %O: %s", pat, str, errmsg)); \
+        COG_RETURN_ERROR(Regex-error, cog_sprintf("Error running regex %O on string %O: %s", pat, str, errmsg)); \
     }
 
 cog_object* fn_regex() {
diff --git a/cogni.c b/cogni.c
index fd67f72..7295929 100644
--- a/cogni.c
+++ b/cogni.c
@@ -67,6 +67,7 @@ static struct {
     cog_object* stack;
     cog_object* command_queue;
     cog_object* scopes;
+    cog_object* context;
 
     cog_object* error_sym;
     cog_object* not_impl_sym;
@@ -114,6 +115,10 @@ void cog_set_stdin(cog_object* stream) {
     COG_GLOBALS.stdin_stream = stream;
 }
 
+cog_object* cog_get_context() {
+    return COG_GLOBALS.context;
+}
+
 // MARK: GC
 
 #define COG_MEM_CHUNK_SIZE 32
@@ -193,6 +198,7 @@ static void gc() {
     cog_walk(COG_GLOBALS.stack, markobject, NULL);
     cog_walk(COG_GLOBALS.command_queue, markobject, NULL);
     cog_walk(COG_GLOBALS.scopes, markobject, NULL);
+    cog_walk(COG_GLOBALS.context, markobject, NULL);
     cog_walk(COG_GLOBALS.error_sym, markobject, NULL);
     cog_walk(COG_GLOBALS.not_impl_sym, markobject, NULL);
     cog_walk(COG_GLOBALS.on_exit_sym, markobject, NULL);
@@ -248,6 +254,7 @@ void cog_quit() {
     COG_GLOBALS.command_queue = NULL;
     COG_GLOBALS.stack = NULL;
     COG_GLOBALS.scopes = NULL;
+    COG_GLOBALS.context = NULL;
     COG_GLOBALS.error_sym = NULL;
     COG_GLOBALS.not_impl_sym = NULL;
     COG_GLOBALS.on_enter_sym = NULL;
@@ -274,7 +281,7 @@ void cog_add_module(cog_module* module) {
 // MARK: UTILITY
 
 cog_object* cog_expect_type_fatal(cog_object* obj, cog_obj_type* t) {
-    assert(obj->type == t);
+    assert(obj && obj->type == t);
     return obj;
 }
 
@@ -311,15 +318,14 @@ cog_object* cog_pop_from(cog_object** stack) {
 cog_object* cog_clone_list_shallow(cog_object* list) {
     if (list == NULL) return NULL;
     cog_obj_type* t = list->type;
-    cog_object* out = cog_make_obj(t);
-    cog_object* tail = out;
+    cog_object* out = NULL;
+    cog_object** tail = &out;
     while (list) {
-        tail->data = list->data;
-        tail->next = cog_make_obj(t);
-        tail = tail->next;
+        *tail = cog_make_obj(t);
+        (*tail)->data = list->data;
+        tail = &((*tail)->next);
         list = list->next;
     }
-    tail->next = NULL;
     return out;
 }
 
@@ -358,16 +364,34 @@ int64_t cog_list_length(cog_object* list) {
     return len;
 }
 
+cog_object* cog_tuple(size_t n, ...) {
+    va_list args;
+    va_start(args, n);
+    cog_object* out = NULL;
+    cog_object** tail = &out;
+    while (n > 0) {
+        cog_object* item = va_arg(args, cog_object*);
+        cog_object* cell = cog_make_obj(&cog_ot_list);
+        cell->data = item;
+        cell->next = NULL;
+        *tail = cell;
+        tail = &(cell->next);
+        n--;
+    }
+    va_end(args);
+    return out;
+}
+
 cog_object* m_list_show_recursive() {
     cog_object* obj = cog_pop();
-    bool readably = cog_expect_type_fatal(cog_pop(), &cog_ot_bool)->as_int;
+    bool repr = cog_expect_type_fatal(cog_pop(), &cog_ot_bool)->as_int;
     cog_object* stream = cog_pop();
     cog_object* alist = cog_pop();
     int64_t* counter = (int64_t*)cog_pop()->as_ptr;
 
     cog_fputchar_imm(stream, '(');
     for (;;) {
-        cog_print_refs_recursive(obj->data, alist, stream, counter, readably);
+        cog_print_refs_recursive(obj->data, alist, stream, counter, repr);
         obj = obj->next;
         int64_t ref = cog_rec_get_refnum(obj, alist, counter);
         if (ref) {
@@ -383,7 +407,7 @@ cog_object* m_list_show_recursive() {
     }
     if (obj) {
         cog_fputs_imm(stream, " . ");
-        cog_print_refs_recursive(obj, alist, stream, counter, readably);
+        cog_print_refs_recursive(obj, alist, stream, counter, repr);
     }
     cog_fputchar_imm(stream, ')');
     return NULL;
@@ -540,25 +564,25 @@ cog_object* cog_table_reduce(cog_object* table, cog_object* (*func)(cog_object*,
     return _reduce_helper(table->next, func, accum);
 }
 
-void _table_show_rec_helper(cog_object* tree, cog_object* alist, cog_object* stream, int64_t* counter, bool readably) {
+void _table_show_rec_helper(cog_object* tree, cog_object* alist, cog_object* stream, int64_t* counter, bool repr) {
     if (!tree) return;
-    _table_show_rec_helper(tree->TLEFT, alist, stream, counter, readably);
+    _table_show_rec_helper(tree->TLEFT, alist, stream, counter, repr);
     if (tree->TLEFT) cog_fputs_imm(stream, ", ");
-    cog_print_refs_recursive(tree->TKEY, alist, stream, counter, readably);
+    cog_print_refs_recursive(tree->TKEY, alist, stream, counter, repr);
     cog_fputs_imm(stream, ": ");
-    cog_print_refs_recursive(tree->TVAL, alist, stream, counter, readably);
+    cog_print_refs_recursive(tree->TVAL, alist, stream, counter, repr);
     if (tree->TRIGHT) cog_fputs_imm(stream, ", ");
-    _table_show_rec_helper(tree->TRIGHT, alist, stream, counter, readably);
+    _table_show_rec_helper(tree->TRIGHT, alist, stream, counter, repr);
 }
 
 cog_object* m_table_show_recursive() {
     cog_object* table = cog_pop();
-    bool readably = cog_expect_type_fatal(cog_pop(), &cog_ot_bool)->as_int;
+    bool repr = cog_expect_type_fatal(cog_pop(), &cog_ot_bool)->as_int;
     cog_object* stream = cog_pop();
     cog_object* alist = cog_pop();
     int64_t* counter = (int64_t*)cog_pop()->as_ptr;
     cog_fputs_imm(stream, "{ ");
-    _table_show_rec_helper(table->next, alist, stream, counter, readably);
+    _table_show_rec_helper(table->next, alist, stream, counter, repr);
     cog_fputs_imm(stream, " }");
     return NULL;
 }
@@ -727,7 +751,7 @@ cog_object* cog_mainloop(cog_object* status) {
             cog_object* new_status = cog_run_well_known(which, "Exec");
             if (cog_same_identifiers(new_status, cog_not_implemented())) {
                 cog_pop();
-                cog_push(cog_sprintf("Can't run %O", which));
+                cog_push(cog_make_error(cog_make_identifier_c("Runtime-error"), cog_sprintf("Can't run %O", which), cog_get_context()));
                 new_status = cog_error();
             }
             if (is_normal_exec) status = new_status;
@@ -865,12 +889,16 @@ cog_object_method ome_float_equal_other_type = {&cog_ot_float, "Equal_OtherType"
 
 // MARK: IDENTIFIERS
 
-static cog_object* walk_identifier(cog_object* i, cog_walk_fun f, cog_object* arg) {
-    (void)arg;
-    if (i->as_int) return NULL;
-    return i->next;
-}
-cog_obj_type cog_ot_identifier = {"Identifier", walk_identifier};
+// Identifier layout is as follows:
+// If it is a packed or builtin identifier:
+//  - the data pointer (or as_int field) contains the data
+//  - the next pointer is a cons (NULL WHERE)
+// If it is a long symbol:
+//  - the data pointer is NULL
+//  - the next pointer is a cons (STRING WHERE)
+// And the WHERE pointer is NULL if it is synthetic, otherwise it is a list (FILENAME LINE COLUMN)
+
+cog_obj_type cog_ot_identifier = {"Identifier", cog_walk_only_next};
 
 // TODO: parameterize / calculate this constant at compile time
 #define MAXPACKED 11
@@ -927,18 +955,17 @@ cog_object* cog_explode_identifier(cog_object* i, bool cap_first) {
         }
     } else if (i->as_fun != NULL) {
         // builtin identifier
-        for (const char* s = i->as_fun->name; *s; s++)
-            cog_string_append_byte(&tail, *s);
+        buffer = cog_string(i->as_fun->name);
     } else {
         // long identifier
-        buffer = i->next;
+        buffer = i->next->data;
     }
     return buffer;
 }
 
 cog_object* cog_make_identifier_c(const char* const name) {
-    // TODO: intern?
     cog_object* out = cog_make_obj(&cog_ot_identifier);
+    out->next = cog_make_obj(&cog_ot_list);
     // first try the builtin function names
     COG_ITER_LIST(COG_GLOBALS.modules, modobj) {
         cog_module* mod = (cog_module*)modobj->as_ptr;
@@ -960,13 +987,14 @@ cog_object* cog_make_identifier_c(const char* const name) {
         goto done;
     }
     // then default to long identifier
-    out->next = cog_string(name);
+    out->next->data = cog_string(name);
     done:
     return out;
 }
 
 cog_object* cog_make_identifier(cog_object* string) {
     cog_object* out = cog_make_obj(&cog_ot_identifier);
+    out->next = cog_make_obj(&cog_ot_list);
     // first try the builtin function names
     COG_ITER_LIST(COG_GLOBALS.modules, modobj) {
         cog_module* mod = (cog_module*)modobj->as_ptr;
@@ -988,14 +1016,25 @@ cog_object* cog_make_identifier(cog_object* string) {
         goto done;
     }
     // then default to long identifier
-    out->next = string;
+    out->next->data = string;
     done:
     return out;
 }
 
+void cog_identifier_set_location(cog_object* ident, cog_object* filename, cog_object* line, cog_object* col) {
+    ident->next->next = cog_tuple(3, filename, line, col);
+}
+
+cog_object* cog_identifier_get_location(cog_object* identifier) {
+    if (identifier->type != &cog_ot_identifier) return NULL;
+    cog_object* loc =  identifier->next->next;
+    if (loc) return loc;
+    return cog_tuple(3, cog_string("native code"), cog_box_int(0), cog_box_int(0));
+}
+
 cog_object* m_show_identifier() {
     cog_object* i = cog_pop();
-    cog_pop(); // ignore readably
+    cog_pop(); // ignore repr
     cog_push(cog_explode_identifier(i, true));
     return NULL;
 }
@@ -1008,6 +1047,7 @@ cog_object_method ome_identifier_show = {
 cog_object* m_run_identifier() {
     cog_object* self = cog_pop();
     cog_object* cookie = cog_pop();
+    COG_GLOBALS.context = self;
     // first look up definition
     bool found = false;
     cog_object* def = cog_get_fun(self, &found);
@@ -1017,12 +1057,11 @@ cog_object* m_run_identifier() {
         return NULL;
     } else {
         // use builtin identifier if available or throw undefined
-        if (!self->next && (self->as_packed_sym & 1) == 0 && self->as_packed_sym != 0) {
+        if ((self->as_packed_sym & 1) == 0 && self->as_packed_sym != 0) {
             cog_run_next(cog_make_bfunction(self->as_fun), NULL, cookie);
             return NULL;
         } else {
-            cog_push(cog_sprintf("undefined: %O", self));
-            return cog_error();
+            COG_RETURN_ERROR(Name-error, cog_sprintf("undefined: %O", self));
         }
     }
 }
@@ -1058,9 +1097,9 @@ cog_object* cog_sym(cog_object* i) {
 
 cog_object* m_symbol_show() {
     cog_object* sym = cog_pop();
-    bool readably = cog_expect_type_fatal(cog_pop(), &cog_ot_bool)->as_int;
+    bool repr = cog_expect_type_fatal(cog_pop(), &cog_ot_bool)->as_int;
     cog_object* chars = cog_explode_identifier(sym->next, false);
-    if (!readably) {
+    if (!repr) {
         cog_push(chars);
     } else {
         cog_push(cog_sprintf("\\%#O", chars));
@@ -1178,15 +1217,15 @@ void cog_string_delete_char(cog_object** str, size_t index) {
         current = &(*current)->next;
     }
 
-    if (!*current) {
-        // If index is out of bounds, do nothing
+    if (!*current || (*current)->stored_chars == 0) {
+        // If index is out of bounds or there are no characters to move, do nothing
         return;
     }
 
     memmove((*current)->as_chars + index, (*current)->as_chars + index + 1, (*current)->stored_chars - index - 1);
     (*current)->stored_chars--;
 
-    if ((*current)->stored_chars == 0) {
+    if ((*current)->stored_chars == 0 && (*current)->next) {
         *current = (*current)->next;
     }
 }
@@ -1242,8 +1281,8 @@ size_t cog_string_to_cstring(cog_object* str, char* const cstr, size_t len) {
 
 cog_object* m_string_show() {
     cog_object* buffer = cog_pop();
-    bool readably = cog_expect_type_fatal(cog_pop(), &cog_ot_bool)->as_int;
-    if (!readably) {
+    bool repr = cog_expect_type_fatal(cog_pop(), &cog_ot_bool)->as_int;
+    if (!repr) {
         cog_push(buffer);
     }
     else {
@@ -1423,21 +1462,26 @@ cog_obj_type ot_iostring = {"IOString", cog_walk_both, NULL};
 cog_object* cog_empty_io_string() {
     cog_object* stream = cog_make_obj(&ot_iostring);
     stream->data = cog_box_int(0); // the cursor position
-    stream->next = cog_make_obj(&cog_ot_list);
-    stream->next->data = cog_emptystring(); // the ungetc stack
-    stream->next->next = cog_emptystring(); // the actual contents
+    stream->next = cog_tuple(4,
+        cog_emptystring(), // the ungetc stack
+        cog_emptystring(), // the actual contents
+        cog_string("<string>"), // the name of the string stream
+        cog_tuple(2, cog_box_int(1), cog_box_int(1))); // line and column
     return stream;
 }
 
 cog_object* cog_iostring_wrap(cog_object* string) {
     cog_object* stream = cog_empty_io_string();
-    stream->next->next = string;
+    stream->next->next->data = cog_expect_type_fatal(string, &cog_ot_string);
     return stream;
 }
 
 cog_object* cog_iostring_get_contents(cog_object* stream) {
-    assert(stream && stream->type == &ot_iostring);
-    return stream->next->next;
+    return cog_expect_type_fatal(stream, &ot_iostring)->next->next->data;
+}
+
+void cog_iostring_set_name(cog_object* stream, cog_object* name) {
+    cog_expect_type_fatal(stream, &ot_iostring)->next->next->next->data = cog_expect_type_fatal(name, &cog_ot_string);
 }
 
 static cog_object* m_iostring_write() {
@@ -1476,8 +1520,21 @@ cog_object* m_iostring_getch() {
     cog_object* data = cog_iostring_get_contents(stream);
     size_t len = cog_strlen(data);
     if (pos < len) {
-        cog_push(cog_make_character(cog_nthchar(data, pos)));
+        char c = cog_nthchar(data, pos);
+        cog_push(cog_make_character(c));
         stream->data = cog_box_int(pos + 1);
+
+        // Update line and column
+        cog_object* line_col = stream->next->next->next->next->data;
+        int64_t line = line_col->data->as_int;
+        int64_t col = line_col->next->data->as_int;
+        if (c == '\n') {
+            line++;
+            col = 1;
+        } else {
+            col++;
+        }
+        stream->next->next->next->next->data = cog_tuple(2, cog_box_int(line), cog_box_int(col));
     } else {
         cog_push(cog_eof());
     }
@@ -1498,12 +1555,27 @@ cog_object_method ome_iostring_ungets = {&ot_iostring, "Stream::UngetString", m_
 
 cog_object* m_iostring_show() {
     cog_object* stream = cog_pop();
-    cog_pop(); // ignore readably
+    cog_pop(); // ignore repr
     cog_push(cog_sprintf("<IOstring at pos %O of %O>", stream->data, cog_iostring_get_contents(stream)));
     return NULL;
 }
 cog_object_method ome_iostring_show = {&ot_iostring, "Show", m_iostring_show};
 
+cog_object* m_iostring_get_name() {
+    cog_object* stream = cog_pop();
+    cog_push(stream->next->next->next->data); // return the name of the string stream
+    return NULL;
+}
+cog_object_method ome_iostring_get_name = {&ot_iostring, "Stream::Get_Name", m_iostring_get_name};
+
+cog_object* m_iostring_tell_linecol() {
+    cog_object* stream = cog_pop();
+    cog_object* line_col = stream->next->next->next->next->data;
+    cog_push(cog_clone_list_shallow(line_col));
+    return NULL;
+}
+cog_object_method ome_iostring_tell_linecol = {&ot_iostring, "Stream::Tell_LineCol", m_iostring_tell_linecol};
+
 // MARK: BUILTIN FUNCTION OBJECTS
 
 cog_obj_type ot_bfunction = {"BuiltinFunction", NULL};
@@ -1602,12 +1674,38 @@ cog_object_method ome_block_exec = {&ot_block, "Exec", m_block_exec};
 
 cog_object* m_block_show() {
     cog_object* block = cog_pop();
-    cog_pop(); // ignore readably
+    cog_pop(); // ignore repr
     cog_push(cog_sprintf("<Block %O>", block->next));
     return NULL;
 }
 cog_object_method ome_block_show = {&ot_block, "Show", m_block_show};
 
+// MARK: ERROR OBJECTS
+
+cog_obj_type cog_ot_error = {"Error", cog_walk_both, NULL};
+
+cog_object* cog_make_error(cog_object* type, cog_object* message, cog_object* location) {
+    cog_object* error = cog_make_obj(&cog_ot_error);
+    error->data = type;
+    error->next = cog_make_obj(&cog_ot_list);
+    error->next->data = message;
+    error->next->next = location;
+    return error;
+}
+
+cog_object* m_error_show() {
+    cog_object* error = cog_pop();
+    bool repr = cog_expect_type_fatal(cog_pop(), &cog_ot_bool)->as_int;
+
+    cog_object* ctx = error->next->next;
+    cog_object* where = cog_identifier_get_location(ctx);
+    cog_push(cog_sprintf(repr ? "<Error %O %O at %O (%#O:%O:%O)>" : "ERROR(%O): %#O\n  at %O (%#O:%O:%O)",
+        error->data, error->next->data, ctx, where->data, where->next->data, where->next->next->data));
+
+    return NULL;
+}
+cog_object_method ome_error_show = {&cog_ot_error, "Show", m_error_show};
+
 // MARK: DUMPER
 
 static bool make_refs_list(cog_object* obj, cog_object* alist_header) {
@@ -1639,7 +1737,7 @@ int64_t cog_rec_get_refnum(cog_object* obj, cog_object* alist, int64_t* counter)
     return 0;
 }
 
-void cog_print_refs_recursive(cog_object* obj, cog_object* alist, cog_object* stream, int64_t* counter, bool readably) {
+void cog_print_refs_recursive(cog_object* obj, cog_object* alist, cog_object* stream, int64_t* counter, bool repr) {
     char buffer[256];
     if (obj == NULL) {
         cog_fputs_imm(stream, "nil");
@@ -1662,14 +1760,14 @@ void cog_print_refs_recursive(cog_object* obj, cog_object* alist, cog_object* st
     cog_push(counter_ptr);
     cog_push(alist);
     cog_push(stream);
-    cog_push(cog_box_bool(readably));
+    cog_push(cog_box_bool(repr));
     if (cog_same_identifiers(cog_run_well_known(obj, "Show_Recursive"), cog_not_implemented())) {
         cog_pop();
         cog_pop();
         cog_pop();
         cog_pop();
         // defer to Show if that didn't work
-        cog_push(cog_box_bool(readably));
+        cog_push(cog_box_bool(repr));
         if (cog_same_identifiers(cog_run_well_known(obj, "Show"), cog_not_implemented())) {
             cog_pop();
             snprintf(buffer, sizeof(buffer), "#<%s: %p %p>", obj->type ? obj->type->name : "NULL", obj->data, obj->next);
@@ -1680,11 +1778,11 @@ void cog_print_refs_recursive(cog_object* obj, cog_object* alist, cog_object* st
     }
 }
 
-void cog_dump(cog_object* obj, cog_object* stream, bool readably) {
+void cog_dump(cog_object* obj, cog_object* stream, bool repr) {
     cog_object* alist_header = cog_make_obj(&cog_ot_list);
     int64_t counter = 1;
     cog_walk(obj, make_refs_list, alist_header);
-    cog_print_refs_recursive(obj, alist_header->data, stream, &counter, readably);
+    cog_print_refs_recursive(obj, alist_header->data, stream, &counter, repr);
 }
 
 // MARK: PARSER
@@ -1715,6 +1813,8 @@ char cog_unescape_char(char c) {
 const char doc_parser_internals[] = "Parser Internal -- you should never see this.";
 
 static void run_nextitem_next(cog_object* stream, cog_object* ibuf) {
+    // cog_object* cookie = cog_tuple(4, stream, COG_GLOBALS.modules, ibuf, cog_box_int(0));
+    // cog_list_splice(&cookie, cog_emptystring());
     cog_object* cookie = cog_emptystring();
     cog_push_to(&cookie, cog_box_int(0));
     cog_push_to(&cookie, ibuf);
@@ -1773,7 +1873,7 @@ cog_object* fn_parser_handle_token() {
     error:
     cog_push(cog_box_bool(true));
     cog_run_well_known_strict(buffer, "Show");
-    COG_RETURN_ERROR(cog_sprintf("PARSE ERROR: could not handle token %O", cog_pop()));
+    COG_RETURN_ERROR(Syntax-error, cog_sprintf("could not handle token %O", cog_pop()));
 
     retry:
     cog_run_next(cog_make_identifier_c("[[Parser::HandleToken]]"), NULL, cookie);
@@ -2061,9 +2161,15 @@ cog_object* fn_parser_handle_identifiers() {
     cog_object* s = cookie->data;
     if (s->type == &cog_ot_string) {
         char first = cog_nthchar(s, 0);
-        if (!isalpha(first) || (toupper(first) == first && all_valid_for_ident(s))) {
+        if ((!isalpha(first) || toupper(first) == first) && all_valid_for_ident(s)) {
             // defined identifier
-            cog_push(cog_make_identifier(s));
+            cog_object* the_identifier = cog_make_identifier(s);
+            cog_run_well_known_strict(cookie->next, "Stream::Get_Name");
+            cog_object* filename = cog_pop();
+            cog_run_well_known_strict(cookie->next, "Stream::Tell_LineCol");
+            cog_object* where = cog_pop();
+            cog_identifier_set_location(the_identifier, filename, where->data, where->next->data);
+            cog_push(the_identifier);
             return NULL;
         }
     }
@@ -2129,6 +2235,7 @@ cog_object* fn_parser_handle_string() {
     cog_object* buffer = cog_emptystring();
     cog_object* tail = buffer;
     char ch;
+    char hex[3];
     for (;;) {
         ch = cog_getch(stream);
         if (ch == '"') break;
@@ -2139,7 +2246,6 @@ cog_object* fn_parser_handle_string() {
             if (ch == 'x') {
                 // \x escapes are not in cognac but github copilot
                 // gave this to me for free and it works so why not
-                char hex[3];
                 hex[0] = cog_getch(stream);
                 hex[1] = cog_getch(stream);
                 hex[2] = 0;
@@ -2165,7 +2271,7 @@ cog_object* fn_parser_handle_string() {
     return cog_error();
 
     badescape:
-    cog_push(cog_string("bad hex escape sequence in string"));
+    cog_push(cog_sprintf("bad hex escape sequence in string '\\x%c%c'", hex[0], hex[1]));
     return cog_error();
 }
 cog_modfunc fne_parser_handle_string = {
@@ -2257,7 +2363,7 @@ cog_object_method ome_def_or_let_exec = {&ot_def_or_let_special, "Exec", m_def_o
 
 cog_object* m_def_or_let_show() {
     cog_object* self = cog_pop();
-    cog_pop(); // ignore readably
+    cog_pop(); // ignore repr
     bool is_def = self->as_int;
     cog_object* symbol = self->next;
     cog_push(cog_sprintf("<%s %O>", is_def ? "Def" : "Let", symbol));
@@ -2675,12 +2781,12 @@ cog_object* fn_first() {
     COG_ENSURE_N_ITEMS(1);
     cog_object* a = cog_pop();
     if (a && a->type == &cog_ot_string) {
-        if (cog_strlen(a) == 0) COG_RETURN_ERROR(cog_string("tried to get First of an empty string"));
+        if (cog_strlen(a) == 0) COG_RETURN_ERROR(Value-error, cog_string("tried to get First of an empty string"));
         cog_push(cog_make_character(cog_nthchar(a, 0)));
         return NULL;
     }
     COG_ENSURE_LIST(a);
-    if (!a) COG_RETURN_ERROR(cog_string("tried to get First of an empty list"));
+    if (!a) COG_RETURN_ERROR(Value-error, cog_string("tried to get First of an empty list"));
     cog_push(a->data);
     return NULL;
 }
@@ -2690,14 +2796,14 @@ cog_object* fn_rest() {
     COG_ENSURE_N_ITEMS(1);
     cog_object* a = cog_pop();
     if (a && a->type == &cog_ot_string) {
-        if (cog_strlen(a) == 0) COG_RETURN_ERROR(cog_string("tried to get Rest of an empty string"));
+        if (cog_strlen(a) == 0) COG_RETURN_ERROR(Value-error, cog_string("tried to get Rest of an empty string"));
         cog_object* dup = cog_strdup(a);
         cog_string_delete_char(&dup, 0);
         cog_push(dup);
         return NULL;
     }
     COG_ENSURE_LIST(a);
-    if (!a) COG_RETURN_ERROR(cog_string("tried to get Rest of an empty list"));
+    if (!a) COG_RETURN_ERROR(Value-error, cog_string("tried to get Rest of an empty list"));
     cog_push(a->next);
     return NULL;
 }
@@ -2768,7 +2874,7 @@ cog_object* fn_ordinal() {
     char b[MB_CUR_MAX + 1];
     cog_string_to_cstring(a, b, MB_CUR_MAX);
     if (!b[0])
-		COG_RETURN_ERROR(cog_string("Gave empty string to Ordinal"));
+		COG_RETURN_ERROR(Value-error, cog_string("Gave empty string to Ordinal"));
 	wchar_t chr = 0;
     mbtowc(NULL, NULL, 0); // reset the conversion state
 	mbtowc(&chr, b, MB_CUR_MAX);
@@ -2790,7 +2896,7 @@ cog_object* fn_character() {
     memset(b, 0, MB_CUR_MAX + 1);
     size_t len = wctomb(b, ord);
     if (len == (size_t)-1)
-        COG_RETURN_ERROR(cog_sprintf("Invalid ordinal %O", a));
+        COG_RETURN_ERROR(Value-error, cog_sprintf("Invalid ordinal %O", a));
     cog_push(cog_string(b));
     return NULL;
 }
@@ -2925,7 +3031,7 @@ cog_object* fn_number() {
     } else if (sscanf(b, "%lf", &f) == 1) {
         cog_push(cog_box_float(f));
     } else {
-        COG_RETURN_ERROR(cog_sprintf("Can't convert %O to a number", a));
+        COG_RETURN_ERROR(Value-error, cog_sprintf("Can't convert %O to a number", a));
     }
     return NULL;
 }
@@ -2942,7 +3048,8 @@ cog_object* fn_wait() {
 cog_modfunc fne_wait = {"Wait", COG_FUNC, fn_wait, "Sleep for a number of seconds."};
 
 cog_object* fn_stop() {
-    COG_RETURN_ERROR(NULL);
+    cog_push(NULL);
+    return cog_error();
 }
 cog_modfunc fne_stop = {"Stop", COG_FUNC, fn_stop, "Stop the program."};
 
@@ -2976,13 +3083,13 @@ static cog_object* _box(cog_object* what) {
 
 cog_object* m_box_show_recursive() {
     cog_object* box = cog_pop();
-    bool readably = cog_expect_type_fatal(cog_pop(), &cog_ot_bool)->as_int;
+    bool repr = cog_expect_type_fatal(cog_pop(), &cog_ot_bool)->as_int;
     cog_object* stream = cog_pop();
     cog_object* alist = cog_pop();
     int64_t* counter = (int64_t*)cog_pop()->as_ptr;
 
     cog_fputchar_imm(stream, '[');
-    cog_print_refs_recursive(box->next, alist, stream, counter, readably);
+    cog_print_refs_recursive(box->next, alist, stream, counter, repr);
     cog_fputchar_imm(stream, ']');
 
     return NULL;
@@ -3076,7 +3183,7 @@ cog_modfunc fne_table = {"Table", COG_FUNC, fn_table, "Makes a table from the ke
 
 #define ENSURE_HASHABLE(obj) \
     do { \
-        if (!cog_hash(obj)) COG_RETURN_ERROR(cog_sprintf("Can't hash key %O", (obj))); \
+        if (!cog_hash(obj)) COG_RETURN_ERROR(Type-error, cog_sprintf("Can't hash key %O", (obj))); \
     } while (0)
 
 cog_object* fn_list_to_tab() {
@@ -3087,7 +3194,7 @@ cog_object* fn_list_to_tab() {
     while (list) {
         cog_object* key = list->data;
         ENSURE_HASHABLE(key);
-        if (!list->next) COG_RETURN_ERROR(cog_string("Odd-length list in Table initializer"));
+        if (!list->next) COG_RETURN_ERROR(Stack-error, cog_string("Odd-length list in Table initializer"));
         cog_object* val = list->next->data;
         list = list->next->next;
         tab = cog_table_insert_or_update(tab, key, val);
@@ -3117,7 +3224,7 @@ cog_object* fn_remove() {
     ENSURE_HASHABLE(key);
     bool found;
     cog_table_get(table, key, &found);
-    if (!found) COG_RETURN_ERROR(cog_sprintf("Can't remove key %O that is not in the table", key));
+    if (!found) COG_RETURN_ERROR(Key-error, cog_sprintf("Can't remove key %O that is not in the table", key));
     cog_push(cog_table_remove(table, key));
     return NULL;
 }
@@ -3131,7 +3238,7 @@ cog_object* fn_dot() {
     ENSURE_HASHABLE(key);
     bool found;
     cog_object* value = cog_table_get(table, key, &found);
-    if (!found) COG_RETURN_ERROR(cog_sprintf("Can't get key %O from table", key));
+    if (!found) COG_RETURN_ERROR(Key-error, cog_sprintf("Can't get key %O from table", key));
     cog_push(value);
     return NULL;
 }
@@ -3189,7 +3296,7 @@ cog_object* fn_length() {
     else if (x->type == &cog_ot_list) cog_push(cog_box_int(cog_list_length(x)));
     else if (x->type == &cog_ot_string) cog_push(cog_box_int(cog_strlen(x)));
     else if (x->type == &cog_ot_table) cog_push(cog_table_reduce(x, _table_len, cog_box_int(0)));
-    else COG_RETURN_ERROR(cog_sprintf("%s object has no length: %O", GET_TYPENAME_STRING(x), x));
+    else COG_RETURN_ERROR(Value-error, cog_sprintf("%s object has no length: %O", GET_TYPENAME_STRING(x), x));
     return NULL;
 }
 cog_modfunc fne_length = {"Length", COG_FUNC, fn_length, "Return the length of a list, string, or table."};
@@ -3373,7 +3480,6 @@ static cog_modfunc* builtin_modfunc_table[] = {
 };
 
 static cog_object_method* builtin_objfunc_table[] = {
-    // ...existing code...
     &ome_int_show,
     &ome_int_exec,
     &ome_bool_show,
@@ -3390,6 +3496,7 @@ static cog_object_method* builtin_objfunc_table[] = {
     &ome_iostring_getch,
     &ome_iostring_ungets,
     &ome_iostring_show,
+    &ome_iostring_get_name,
     &ome_bfunction_exec,
     &ome_closure_exec,
     &ome_block_exec,
@@ -3411,6 +3518,8 @@ static cog_object_method* builtin_objfunc_table[] = {
     &ome_table_hash,
     &ome_int_equal_other_type,
     &ome_float_equal_other_type,
+    &ome_iostring_tell_linecol,
+    &ome_error_show,
     NULL
 };
 
@@ -3435,6 +3544,7 @@ static cog_obj_type* builtin_types[] = {
     &ot_var,
     &ot_box,
     &cog_ot_continuation,
+    &cog_ot_error,
     NULL
 };
 
@@ -3523,32 +3633,31 @@ void vprint_inner(cog_object* stream, const char* fmt, va_list args) {
                 case 'd': case 'i': case 'u': case 'o': case 'x': case 'X': case 'c': {
                     int val = va_arg(args, int);
                     asprintf(&buffer, format, val);
-                    goto done;
+                    break;
                 }
                 case 'f': case 'F': case 'e': case 'E': case 'g': case 'G': case 'a': case 'A': {
                     double val = va_arg(args, double);
                     asprintf(&buffer, format, val);
-                    goto done;
+                    break;
                 }
                 case 's': {
                     char* val = va_arg(args, char*);
                     asprintf(&buffer, format, val);
-                    goto done;
+                    break;
                 }
                 case 'p': {
                     void* val = va_arg(args, void*);
                     asprintf(&buffer, format, val);
-                    goto done;
+                    break;
                 }
                 case '%': {
                     asprintf(&buffer, "%%");
-                    goto done;
+                    break;
                 }
                 default:
                     fprintf(stderr, "bad format %s\n", format);
                     abort();
             }
-            done:
             cog_fputs_imm(stream, buffer);
             free(buffer);
             afterprint:
diff --git a/cogni.h b/cogni.h
index 07f6a69..eb91701 100644
--- a/cogni.h
+++ b/cogni.h
@@ -215,6 +215,10 @@ cog_object* cog_explode_identifier(cog_object*, bool);
  */
 bool cog_same_identifiers(cog_object*, cog_object*);
 
+void cog_identifier_set_location(cog_object*, cog_object*, cog_object*, cog_object*);
+
+cog_object* cog_identifier_get_location(cog_object*);
+
 cog_object* cog_sym(cog_object*);
 
 /**
@@ -327,6 +331,8 @@ cog_object* cog_iostring_wrap(cog_object*);
 
 cog_object* cog_iostring_get_contents(cog_object*);
 
+void cog_iostring_set_name(cog_object*, cog_object*);
+
 /**
  * Writes a literal character to a stream.
  */
@@ -388,7 +394,7 @@ char cog_maybe_escape_char(char, bool*);
 
 /**
  * Destructively splices two lists or strings together.
- * @param l1 Pointer to the first list or string, which will be modified to point to the second list or string.
+ * @param l1 Pointer to the first list or string, the last cell of which will be modified to point to the second list or string.
  * @return The spliced list or string. Usually the same as `*l1` but not always.
  */
 cog_object* cog_list_splice(cog_object**, cog_object*);
@@ -405,6 +411,8 @@ cog_object* cog_clone_list_shallow(cog_object*);
 
 void cog_reverse_list_inplace(cog_object**);
 
+cog_object* cog_tuple(size_t n, ...);
+
 /**
  * Pushes an object onto a stack.
  * @param stack A pointer to the stack to push the object onto,
@@ -535,6 +543,10 @@ cog_object* cog_mainloop(cog_object*);
  */
 cog_object* cog_not_implemented();
 
+cog_object* cog_make_error(cog_object*, cog_object*, cog_object*);
+
+cog_object* cog_get_context();
+
 /**
  * Returns the error status identifier.
  */
@@ -615,9 +627,9 @@ extern cog_obj_type cog_ot_continuation;
 /**
  * Returns early with an error status and the specified message.
  */
-#define COG_RETURN_ERROR(msg) \
+#define COG_RETURN_ERROR(type, msg) \
     do { \
-        cog_push(msg); \
+        cog_push(cog_make_error(cog_make_identifier_c(#type), msg, cog_get_context())); \
         return cog_error(); \
     } while (0)
 
@@ -644,7 +656,7 @@ extern cog_obj_type cog_ot_continuation;
 #define COG_ENSURE_N_ITEMS(n) \
     do { \
         if (!cog_stack_has_at_least(n)) { \
-            COG_RETURN_ERROR(cog_sprintf("%s: Expected %d items on the stack, but there were only %d", __func__, n, cog_stack_length())); \
+            COG_RETURN_ERROR(Stack-error, cog_sprintf("Expected %d items on the stack, but there were only %d", n, cog_stack_length())); \
         } \
     } while (0)
 
@@ -654,7 +666,7 @@ extern cog_obj_type cog_ot_continuation;
 #define COG_ENSURE_TYPE(obj, typeobj) \
     do { \
         if ((obj) == NULL || (obj)->type != typeobj) { \
-            COG_RETURN_ERROR(cog_sprintf("Expected %s, but got %s: %O", \
+            COG_RETURN_ERROR(Type-error, cog_sprintf("Expected %s, but got %s: %O", \
                 (typeobj) ? (typeobj)->name : "empty List", (obj) && (obj)->type ? (obj)->type->name : "empty List", (obj))); \
         } \
     } while (0)
@@ -674,7 +686,7 @@ extern cog_obj_type cog_ot_continuation;
 #define COG_GET_NUMBER(obj, var) \
     do { \
         if ((obj) == NULL || ((obj)->type != &cog_ot_int && (obj)->type != &cog_ot_float)) { \
-            COG_RETURN_ERROR(cog_sprintf("Expected a number, but got %s: %O", (obj) ? (obj)->type->name : "empty List", (obj))); \
+            COG_RETURN_ERROR(Type-error, cog_sprintf("Expected a number, but got %s: %O", (obj) ? (obj)->type->name : "empty List", (obj))); \
         } \
         else var = (obj)->type == &cog_ot_float ? (obj)->as_float : (obj)->as_int; \
     } while (0)
diff --git a/files.c b/files.c
index f6b78ad..f267e0a 100644
--- a/files.c
+++ b/files.c
@@ -17,7 +17,7 @@ cog_obj_type ot_file = {"File", cog_walk_only_next, closefile};
 cog_object* cog_make_filestream(FILE* f, cog_object* filename) {
     cog_object* fo = cog_make_obj(&ot_file);
     fo->as_ptr = (void*)f;
-    fo->next = filename;
+    fo->next = cog_tuple(3, filename, cog_box_int(1), cog_box_int(1));
     return fo;
 }
 
@@ -40,9 +40,25 @@ cog_object_method ome_file_write = {&ot_file, "Stream::PutString", m_file_write}
 
 static cog_object* m_file_getch() {
     cog_object* file = cog_pop();
-    FILE* f = (FILE*)file->as_ptr;
-    if (feof(f)) cog_push(cog_eof());
-    else cog_push(cog_make_character(fgetc(f)));
+    FILE* fp = (FILE*)file->as_ptr;
+    int c = fgetc(fp);
+    if (c != EOF) {
+        cog_push(cog_make_character(c));
+
+        // Update line and column
+        cog_object* line_col = file->next->next;
+        int64_t line = line_col->data->as_int;
+        int64_t col = line_col->next->data->as_int;
+        if (c == '\n') {
+            line++;
+            col = 1;
+        } else {
+            col++;
+        }
+        file->next->next = cog_tuple(2, cog_box_int(line), cog_box_int(col));
+    } else {
+        cog_push(cog_eof());
+    }
     return NULL;
 }
 static cog_object_method ome_file_getch = {&ot_file, "Stream::GetChar", m_file_getch};
@@ -51,11 +67,19 @@ static cog_object* m_file_ungets() {
     cog_object* file = cog_pop();
     cog_object* buf = cog_pop();
     FILE* f = (FILE*)file->as_ptr;
+    // Track line and column
+    cog_object* line_col = file->next->next;
+    int64_t line = line_col->data->as_int;
+    int64_t col = line_col->next->data->as_int;
     while (buf) {
-        for (int i = 0; i < buf->stored_chars; i++)
-            ungetc(buf->as_chars[i], f);
+        for (int i = 0; i < buf->stored_chars; i++) {
+            char c = buf->as_chars[i];
+            ungetc(c, f);
+            col--; // tracking if it goes below 0 ie to the previous line is not important here
+        }
         buf = buf->next;
     }
+    file->next->next = cog_tuple(2, cog_box_int(line), cog_box_int(col));
     return NULL;
 }
 static cog_object_method ome_file_ungets = {&ot_file, "Stream::UngetString", m_file_ungets};
@@ -99,6 +123,22 @@ static cog_object* m_file_stringify() {
 }
 cog_object_method ome_file_stringify = {&ot_file, "Show", m_file_stringify};
 
+static cog_object* m_file_get_name() {
+    cog_object* file = cog_pop();
+    cog_push(file->next);
+    return NULL;
+}
+cog_object_method ome_file_get_name = {&ot_file, "Stream::Get_Name", m_file_get_name};
+
+cog_object* m_file_tell_linecol() {
+    cog_object* file = cog_pop();
+    cog_object* line_col = file->next->next;
+    cog_push(cog_clone_list_shallow(line_col));
+    return NULL;
+}
+
+cog_object_method ome_file_tell_linecol = {&ot_file, "Stream::Tell_LineCol", m_file_tell_linecol};
+
 cog_object_method ome_file_hash = {&ot_file, "Hash", cog_not_implemented};
 
 cog_object_method* m_file_table[] = {
@@ -106,6 +146,8 @@ cog_object_method* m_file_table[] = {
     &ome_file_getch,
     &ome_file_ungets,
     &ome_file_stringify,
+    &ome_file_get_name,
+    &ome_file_tell_linecol,
     &ome_file_hash,
     NULL
 };
@@ -129,7 +171,7 @@ cog_object* fn_open() {
     else if (cog_same_identifiers(mode->next, cog_make_identifier_c("read-append"))) mod = "a+";
     else if (cog_same_identifiers(mode->next, cog_make_identifier_c("read-write"))) mod = "w+";
     else if (cog_same_identifiers(mode->next, cog_make_identifier_c("read-write-existing"))) mod = "r+";
-    else COG_RETURN_ERROR(cog_sprintf("Expected one of \\read, \\write, \\append, \\read-write, \\read-append, \\read-write-existing but got %O", mode));
+    else COG_RETURN_ERROR(Value-error, cog_sprintf("Expected one of \\read, \\write, \\append, \\read-write, \\read-append, \\read-write-existing but got %O", mode));
     // get the filename
     size_t len = cog_strlen(filename);
     char* buf = (char*)alloca(len + 1);
@@ -137,8 +179,8 @@ cog_object* fn_open() {
     cog_string_to_cstring(filename, buf, len);
     errno = 0;
     FILE* f = fopen(buf, mod);
-    if (errno) COG_RETURN_ERROR(cog_sprintf("While opening %O: [Errno %i] %s", filename, errno, strerror(errno)));
-    else if (!f) COG_RETURN_ERROR(cog_sprintf("Unknown error while opening %O", filename));
+    if (errno) COG_RETURN_ERROR(System-error, cog_sprintf("While opening %O: [Errno %i] %s", filename, errno, strerror(errno)));
+    else if (!f) COG_RETURN_ERROR(System-error, cog_sprintf("Unknown error while opening %O", filename));
     cog_push(cog_make_filestream(f, filename));
     return NULL;
 }
@@ -149,7 +191,7 @@ cog_object* fn_readfile() {
     cog_object* file = cog_pop();
     COG_ENSURE_TYPE(file, &ot_file);
     FILE* f = (FILE*)file->as_ptr;
-    if (!f) COG_RETURN_ERROR(cog_string("Tried to read a closed file"));
+    if (!f) COG_RETURN_ERROR(Resource-error, cog_string("Tried to read a closed file"));
     cog_object* str = cog_emptystring();
     cog_object* tail = str;
     for (int ch = fgetc(f); ch != EOF; ch = fgetc(f))
@@ -170,25 +212,25 @@ cog_object* fn_seek() {
     COG_ENSURE_TYPE(what, &ot_file);
     int w = SEEK_SET;
     FILE* f = (FILE*)what->as_ptr;
-    if (!f) COG_RETURN_ERROR(cog_string("Tried to seek a closed file"));
+    if (!f) COG_RETURN_ERROR(Resource-error, cog_string("Tried to seek a closed file"));
     if (cog_same_identifiers(how->next, cog_make_identifier_c("start"))) w = SEEK_SET;
     else if (cog_same_identifiers(how->next, cog_make_identifier_c("end"))) w = SEEK_END;
     else if (cog_same_identifiers(how->next, cog_make_identifier_c("current"))) w = SEEK_CUR;
-    else COG_RETURN_ERROR(cog_sprintf("Expected one of \\start, \\end, \\current but got %O", how));
+    else COG_RETURN_ERROR(Value-error, cog_sprintf("Expected one of \\start, \\end, \\current but got %O", how));
     long nf = n;
-    if (nf != n) COG_RETURN_ERROR(cog_sprintf("can't seek to a non-integer offset: %O", where));
+    if (nf != n) COG_RETURN_ERROR(Value-error, cog_sprintf("can't seek to a non-integer offset: %O", where));
     int err = fseek(f, n, w);
-    if (err) COG_RETURN_ERROR(cog_sprintf("failed to seek to %O for %O", where, how));
+    if (err) COG_RETURN_ERROR(System-error, cog_sprintf("failed to seek to %O for %O", where, how));
     return NULL;
 }
-cog_modfunc fne_seek = {"Seek", COG_FUNC, fn_seek, "Seeks a file to a particular offset,"};
+cog_modfunc fne_seek = {"Seek", COG_FUNC, fn_seek, "Seeks a file to a particular offset."};
 
 cog_object* fn_readline() {
     COG_ENSURE_N_ITEMS(1);
     cog_object* file = cog_pop();
     COG_ENSURE_TYPE(file, &ot_file);
     FILE* f = (FILE*)file->as_ptr;
-    if (!f) COG_RETURN_ERROR(cog_string("Tried to read a closed file"));
+    if (!f) COG_RETURN_ERROR(Resource-error, cog_string("Tried to read a closed file"));
     cog_object* str = cog_emptystring();
     cog_object* tail = str;
     int ch;
@@ -206,8 +248,10 @@ cog_object* fn_close() {
     cog_object* file = cog_pop();
     COG_ENSURE_TYPE(file, &ot_file);
     FILE* f = (FILE*)file->as_ptr;
-    if (!f) COG_RETURN_ERROR(cog_string("File is already closed"));
-    fclose(f);
+    if (!f) COG_RETURN_ERROR(Resource-error, cog_string("File is already closed"));
+    if (f != stdin && f != stdout && f != stderr) {
+        fclose(f);
+    }
     file->as_ptr = NULL;
     return NULL;
 }
diff --git a/main.c b/main.c
index fa8a7cc..5b4701f 100644
--- a/main.c
+++ b/main.c
@@ -21,15 +21,21 @@ bool do_top(cog_object* cookie) {
 
     if (cog_same_identifiers(end_status, cog_error())) {
         cog_object* msg = cog_pop();
-        if (msg) cog_printf("ERROR: %#O\n", msg);
-        if (isatty(fileno(stdout))) putchar('\a');
-        return false;
+        if (msg) {
+            cog_printf("%#O\n", msg);
+            if (isatty(fileno(stdout))) putchar('\a');
+            return false;
+        }
     }
     return true;
 }
 
-bool run(cog_object* obj) {
+bool run(cog_object* obj, const char* what) {
     // parse it
+    if (obj->type == &cog_ot_string) {
+        obj = cog_iostring_wrap(obj);
+        cog_iostring_set_name(obj, cog_string(what));
+    }
     cog_push(obj);
     cog_push(cog_make_identifier_c("Parse"));
     if (!do_top(NULL)) return false;
@@ -74,7 +80,7 @@ void repl() {
             free(line_input);
             if (!len) {
                 is_end = true;
-                run(the_string);
+                run(the_string, "<input>");
             }
         } else {
             cog_push(cog_string("Interrupted!"));
@@ -94,9 +100,12 @@ void repl() {
 void usage(const char* argv0) {
     printf("Usage: %s [filename]\n", argv0);
     printf("   or: %s -c \"commands\"\n", argv0);
+    cog_quit();
     exit(EXIT_FAILURE);
 }
 
+#define EXIT_INTERNAL_ERROR 2
+
 int main(int argc, char* argv[]) {
     cog_init();
     cog_add_module(&m_file);
@@ -113,11 +122,18 @@ int main(int argc, char* argv[]) {
     }
     cog_defun(cog_make_identifier_c("Parameters"), cog_make_var(params));
 
+    int status = EXIT_SUCCESS;
     cog_object* prelude = cog_string_from_bytes((char*)cognac_src_prelude_cog, cognac_src_prelude_cog_len);
     cog_object* userscript = NULL;
-    if (!run(prelude)) goto end;
+    if (!run(prelude, "<prelude>")) {
+        status = EXIT_INTERNAL_ERROR;
+        goto end;
+    }
     prelude = cog_string_from_bytes((char*)prelude2_cog, prelude2_cog_len);
-    if (!run(prelude)) goto end;
+    if (!run(prelude, "<prelude2>")) {
+        status = EXIT_INTERNAL_ERROR;
+        goto end;
+    }
 
     // Run user script
     if (argc == 1) {
@@ -129,14 +145,15 @@ int main(int argc, char* argv[]) {
         userscript = cog_open_file(filename, "r");
         if (errno) {
             fprintf(stderr, "%s: %s: %s\n", argv[0], filename, strerror(errno));
+            status = EXIT_FAILURE;
             goto end;
         }
     } else if (argc == 3 && !strcmp(argv[1], "-c")) {
         userscript = cog_string(argv[2]);
     } else usage(argv[0]);
 
-    run(userscript);
+    status = run(userscript, "<string>") ? EXIT_SUCCESS : EXIT_FAILURE;
     end:
     cog_quit();
-    return 0;
+    return status;
 }
diff --git a/misc_io.c b/misc_io.c
index 3b90749..76c7aa4 100644
--- a/misc_io.c
+++ b/misc_io.c
@@ -1,12 +1,13 @@
 #define _FORTIFY_SOURCE 3
-#include "files.h"
+
+#include "misc_io.h"
 #include <stdio.h>
 #include <unistd.h>
 
 cog_object* fn_path() {
     char buf[FILENAME_MAX];
     errno = 0;
-	if (!getcwd(buf, FILENAME_MAX)) COG_RETURN_ERROR(cog_sprintf("Couldn't get current path: [Errno %i] %s", errno, strerror(errno)));
+	if (!getcwd(buf, FILENAME_MAX)) COG_RETURN_ERROR(System-error, cog_sprintf("Couldn't get current path: [Errno %i] %s", errno, strerror(errno)));
     cog_push(cog_string(buf));
     return NULL;
 }
diff --git a/run_tests.py b/run_tests.py
index 31291f5..b2f68fe 100644
--- a/run_tests.py
+++ b/run_tests.py
@@ -1,8 +1,9 @@
 import glob
 import os
 import re
-import signal
 import subprocess
+import sys
+import time
 
 os.chdir("cognac/")
 
@@ -12,48 +13,72 @@
 crashes = 0
 
 
-def test(file: str, process: subprocess.Popen, pad_length: int):
+def test(file: str, process: subprocess.Popen,
+         pad_length: int, is_kill: bool, start: int):
     global successes
     global failures
     global errors
     global crashes
+    if process.poll() is None:
+        return False
     print("Testing", file.ljust(pad_length), end=" :: ")
-    try:
-        exit_code = process.wait()
-    except KeyboardInterrupt:
-        exit_code = signal.SIGINT
+    exit_code = process.returncode
     out: bytes = process.stdout.read() + process.stderr.read() + \
         f"\nProgram exited with code {exit_code}\n".encode()
-    if exit_code == signal.SIGINT:
-        print("\x1b[35mINTERRUPT\x1b[0m", end=" ")
+    if is_kill:
+        print("\x1b[35mSIGINT\x1b[0m", end=" ")
     elif exit_code != 0:
         crashes += 1
-        print("\x1b[31mCRASH\x1b[0m", end=" ")
+        print("\x1b[31mCRASH \x1b[0m", end=" ")
     elif b"ERROR" in out:
         errors += 1
-        print("\x1b[31mERROR\x1b[0m", end=" ")
+        print("\x1b[31mERROR \x1b[0m", end=" ")
     elif b"FAIL" in out:
         failures += 1
-        print("\x1b[31mFAIL\x1b[0m", end=" ")
+        print("\x1b[31mFAIL  \x1b[0m", end=" ")
     elif b"PASS" in out:
         successes += 1
-        print("\x1b[32mPASS\x1b[0m", end=" ")
+        print("\x1b[32mPASS  \x1b[0m", end=" ")
     else:
         print("NO STATUS", end=" ")
     with open(file.replace(".cog", ".log"), "wb") as f:
         f.write(out)
+    print(f" (time: {(time.perf_counter_ns() - start)/1e9:.3g}s)", end="")
     if (m := re.search(r"undefined: (.+?)\n", out.decode(), re.I)):
         print("requires", m.group(1), end="")
     print()
+    return True
 
 
+is_profiling = len(sys.argv) > 1
 test_files = sorted(glob.glob("tests/*.cog"))
-test_commands = [["../cogni", file] for file in test_files]
+if is_profiling:
+    raise NotImplementedError
+    test_commands = [["xctrace", "record",
+                      "--output", "traces/",
+                      "--template", "Time Profiler",
+                      "--time-limit", "10s",
+                      "--launch", "--", "../cogni", file]
+                     for file in test_files]
+
+else:
+    test_commands = [["../cogni", file] for file in test_files]
+start = time.perf_counter_ns()
 test_processes = [subprocess.Popen(
     command, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
     for command in test_commands]
-for f, p in zip(test_files, test_processes):
-    test(f, p, max(map(len, test_files)))
+file2proc = dict(zip(test_files, [[p, False] for p in test_processes]))
+pl = max(map(len, test_files))
+try:
+    while any(not q for p, q in file2proc.values()):
+        for f, (p, q) in file2proc.items():
+            if not q:
+                file2proc[f][1] = test(f, p, pl, False, start)
+except KeyboardInterrupt:
+    for f, (p, q) in file2proc.items():
+        if not q:
+            p.kill()
+            test(f, p, pl, True, start)
 
 print("Successes:", successes)
 print(" Failures:", failures)
