Implement proper exception type hierarchy.

Each built-in exception is now a type, with base type BaseException.
C exceptions are created by passing a pointer to the exception type to
make an instance of.  When raising an exception from the VM, an
instance is created automatically if an exception type is raised (as
opposed to an exception instance).

Exception matching (RT_BINARY_OP_EXCEPTION_MATCH) is now proper.

Handling of parse error changed to match new exceptions.

mp_const_type renamed to mp_type_type for consistency.
diff --git a/py/builtin.c b/py/builtin.c
index 91e54fa..ef9e70c 100644
--- a/py/builtin.c
+++ b/py/builtin.c
@@ -36,7 +36,7 @@
     mp_obj_t meta;
     if (n_args == 2) {
         // no explicit bases, so use 'type'
-        meta = (mp_obj_t)&mp_const_type;
+        meta = (mp_obj_t)&mp_type_type;
     } else {
         // use type of first base object
         meta = mp_obj_get_type(args[2]);
@@ -142,7 +142,7 @@
         byte str[1] = {ord};
         return mp_obj_new_str(str, 1, true);
     } else {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_ValueError, "chr() arg not in range(0x110000)"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "chr() arg not in range(0x110000)"));
     }
 }
 
@@ -187,7 +187,7 @@
         args[1] = MP_OBJ_NEW_SMALL_INT(i1 % i2);
         return rt_build_tuple(2, args);
     } else {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "unsupported operand type(s) for divmod(): '%s' and '%s'", mp_obj_get_type_str(o1_in), mp_obj_get_type_str(o2_in)));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "unsupported operand type(s) for divmod(): '%s' and '%s'", mp_obj_get_type_str(o1_in), mp_obj_get_type_str(o2_in)));
     }
 }
 
@@ -209,7 +209,7 @@
 STATIC mp_obj_t mp_builtin_len(mp_obj_t o_in) {
     mp_obj_t len = mp_obj_len_maybe(o_in);
     if (len == NULL) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "object of type '%s' has no len()", mp_obj_get_type_str(o_in)));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "object of type '%s' has no len()", mp_obj_get_type_str(o_in)));
     } else {
         return len;
     }
@@ -229,7 +229,7 @@
             }
         }
         if (max_obj == NULL) {
-            nlr_jump(mp_obj_new_exception_msg(MP_QSTR_ValueError, "max() arg is an empty sequence"));
+            nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "max() arg is an empty sequence"));
         }
         return max_obj;
     } else {
@@ -258,7 +258,7 @@
             }
         }
         if (min_obj == NULL) {
-            nlr_jump(mp_obj_new_exception_msg(MP_QSTR_ValueError, "min() arg is an empty sequence"));
+            nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "min() arg is an empty sequence"));
         }
         return min_obj;
     } else {
@@ -278,7 +278,7 @@
 STATIC mp_obj_t mp_builtin_next(mp_obj_t o) {
     mp_obj_t ret = rt_iternext(o);
     if (ret == mp_const_stop_iteration) {
-        nlr_jump(mp_obj_new_exception(MP_QSTR_StopIteration));
+        nlr_jump(mp_obj_new_exception(&mp_type_StopIteration));
     } else {
         return ret;
     }
@@ -294,7 +294,7 @@
         // TODO unicode
         return mp_obj_new_int(((const byte*)str)[0]);
     } else {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "ord() expected a character, but string of length %d found", len));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "ord() expected a character, but string of length %d found", len));
     }
 }
 
@@ -364,7 +364,7 @@
 STATIC mp_obj_t mp_builtin_sorted(uint n_args, const mp_obj_t *args, mp_map_t *kwargs) {
     assert(n_args >= 1);
     if (n_args > 1) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError,
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError,
                                           "must use keyword argument for key function"));
     }
     mp_obj_t self = list_type.make_new((mp_obj_t)&list_type, 1, 0, args);
diff --git a/py/builtinevex.c b/py/builtinevex.c
index 3e3c9a6..6e920e8 100644
--- a/py/builtinevex.c
+++ b/py/builtinevex.c
@@ -13,6 +13,7 @@
 #include "lexerunix.h"
 #include "parse.h"
 #include "obj.h"
+#include "parsehelper.h"
 #include "compile.h"
 #include "runtime0.h"
 #include "runtime.h"
@@ -28,14 +29,13 @@
     qstr source_name = mp_lexer_source_name(lex);
 
     // parse the string
-    qstr parse_exc_id;
-    const char *parse_exc_msg;
-    mp_parse_node_t pn = mp_parse(lex, parse_input_kind, &parse_exc_id, &parse_exc_msg);
+    mp_parse_error_kind_t parse_error_kind;
+    mp_parse_node_t pn = mp_parse(lex, parse_input_kind, &parse_error_kind);
     mp_lexer_free(lex);
 
     if (pn == MP_PARSE_NODE_NULL) {
         // parse error; raise exception
-        nlr_jump(mp_obj_new_exception_msg(parse_exc_id, parse_exc_msg));
+        nlr_jump(mp_parse_make_exception(parse_error_kind));
     }
 
     // compile the string
@@ -74,6 +74,7 @@
         rt_locals_set(mp_obj_dict_get_map(locals));
     }
     mp_obj_t res = parse_compile_execute(args[0], MP_PARSE_FILE_INPUT);
+    // TODO if the above call throws an exception, then we never get to reset the globals/locals
     rt_globals_set(old_globals);
     rt_locals_set(old_locals);
     return res;
diff --git a/py/builtinimport.c b/py/builtinimport.c
index 0e44676..c90625e 100644
--- a/py/builtinimport.c
+++ b/py/builtinimport.c
@@ -13,6 +13,7 @@
 #include "lexerunix.h"
 #include "parse.h"
 #include "obj.h"
+#include "parsehelper.h"
 #include "compile.h"
 #include "runtime0.h"
 #include "runtime.h"
@@ -77,7 +78,7 @@
 
     if (lex == NULL) {
         // we verified the file exists using stat, but lexer could still fail
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_ImportError, "ImportError: No module named '%s'", vstr_str(file)));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_ImportError, "ImportError: No module named '%s'", vstr_str(file)));
     }
 
     qstr source_name = mp_lexer_source_name(lex);
@@ -91,16 +92,15 @@
     rt_globals_set(mp_obj_module_get_globals(module_obj));
 
     // parse the imported script
-    qstr parse_exc_id;
-    const char *parse_exc_msg;
-    mp_parse_node_t pn = mp_parse(lex, MP_PARSE_FILE_INPUT, &parse_exc_id, &parse_exc_msg);
+    mp_parse_error_kind_t parse_error_kind;
+    mp_parse_node_t pn = mp_parse(lex, MP_PARSE_FILE_INPUT, &parse_error_kind);
     mp_lexer_free(lex);
 
     if (pn == MP_PARSE_NODE_NULL) {
         // parse error; clean up and raise exception
         rt_locals_set(old_locals);
         rt_globals_set(old_globals);
-        nlr_jump(mp_obj_new_exception_msg(parse_exc_id, parse_exc_msg));
+        nlr_jump(mp_parse_make_exception(parse_error_kind));
     }
 
     // compile the imported script
@@ -172,7 +172,7 @@
 
             // fail if we couldn't find the file
             if (stat == MP_IMPORT_STAT_NO_EXIST) {
-                nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_ImportError, "ImportError: No module named '%s'", qstr_str(mod_name)));
+                nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_ImportError, "ImportError: No module named '%s'", qstr_str(mod_name)));
             }
 
             module_obj = mp_obj_module_get(mod_name);
diff --git a/py/lexer.c b/py/lexer.c
index c3c992a..be0b188 100644
--- a/py/lexer.c
+++ b/py/lexer.c
@@ -51,6 +51,7 @@
     return i == len && *str == 0;
 }
 
+#ifdef MICROPY_DEBUG_PRINTERS
 void mp_token_show(const mp_token_t *tok) {
     printf("(%d:%d) kind:%d str:%p len:%d", tok->src_line, tok->src_column, tok->kind, tok->str, tok->len);
     if (tok->str != NULL && tok->len > 0) {
@@ -69,6 +70,7 @@
     }
     printf("\n");
 }
+#endif
 
 #define CUR_CHAR(lex) ((lex)->chr0)
 
@@ -711,35 +713,3 @@
 bool mp_lexer_is_kind(mp_lexer_t *lex, mp_token_kind_t kind) {
     return lex->tok_cur.kind == kind;
 }
-
-/*
-bool mp_lexer_is_str(mp_lexer_t *lex, const char *str) {
-    return mp_token_is_str(&lex->tok_cur, str);
-}
-
-bool mp_lexer_opt_kind(mp_lexer_t *lex, mp_token_kind_t kind) {
-    if (mp_lexer_is_kind(lex, kind)) {
-        mp_lexer_to_next(lex);
-        return true;
-    }
-    return false;
-}
-
-bool mp_lexer_opt_str(mp_lexer_t *lex, const char *str) {
-    if (mp_lexer_is_str(lex, str)) {
-        mp_lexer_to_next(lex);
-        return true;
-    }
-    return false;
-}
-*/
-
-bool mp_lexer_show_error_pythonic_prefix(mp_lexer_t *lex) {
-    printf("  File \"%s\", line %d column %d\n", qstr_str(lex->source_name), lex->tok_cur.src_line, lex->tok_cur.src_column);
-    return false;
-}
-
-bool mp_lexer_show_error_pythonic(mp_lexer_t *lex, const char *msg) {
-    printf("  File \"%s\", line %d column %d\n%s\n", qstr_str(lex->source_name), lex->tok_cur.src_line, lex->tok_cur.src_column, msg);
-    return false;
-}
diff --git a/py/mpconfig.h b/py/mpconfig.h
index 6ff0692..00e2439 100644
--- a/py/mpconfig.h
+++ b/py/mpconfig.h
@@ -40,6 +40,7 @@
 #endif
 
 // Whether to build functions that print debugging info:
+//   mp_token_show
 //   mp_byte_code_print
 //   mp_parse_node_print
 #ifndef MICROPY_DEBUG_PRINTERS
diff --git a/py/obj.c b/py/obj.c
index 86c0edc..0068af4 100644
--- a/py/obj.c
+++ b/py/obj.c
@@ -51,7 +51,7 @@
 
 // helper function to print an exception with traceback
 void mp_obj_print_exception(mp_obj_t exc) {
-    if (MP_OBJ_IS_TYPE(exc, &exception_type)) {
+    if (mp_obj_is_exception_instance(exc)) {
         machine_uint_t n, *values;
         mp_obj_exception_get_traceback(exc, &n, &values);
         if (n > 0) {
@@ -133,7 +133,7 @@
             }
         }
 
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_NotImplementedError,
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_NotImplementedError,
             "Equality for '%s' and '%s' types not yet implemented", mp_obj_get_type_str(o1), mp_obj_get_type_str(o2)));
         return false;
     }
@@ -160,7 +160,7 @@
     } else if (MP_OBJ_IS_TYPE(arg, &int_type)) {
         return mp_obj_int_get_checked(arg);
     } else {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "can't convert %s to int", mp_obj_get_type_str(arg)));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "can't convert %s to int", mp_obj_get_type_str(arg)));
     }
 }
 
@@ -175,7 +175,7 @@
     } else if (MP_OBJ_IS_TYPE(arg, &float_type)) {
         return mp_obj_float_get(arg);
     } else {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "can't convert %s to float", mp_obj_get_type_str(arg)));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "can't convert %s to float", mp_obj_get_type_str(arg)));
     }
 }
 
@@ -195,7 +195,7 @@
     } else if (MP_OBJ_IS_TYPE(arg, &complex_type)) {
         mp_obj_complex_get(arg, real, imag);
     } else {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "can't convert %s to complex", mp_obj_get_type_str(arg)));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "can't convert %s to complex", mp_obj_get_type_str(arg)));
     }
 }
 #endif
@@ -210,11 +210,11 @@
             mp_obj_list_get(o_in, &seq_len, &seq_items);
         }
         if (seq_len != n) {
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_IndexError, "requested length %d but object has length %d", n, seq_len));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_IndexError, "requested length %d but object has length %d", n, seq_len));
         }
         return seq_items;
     } else {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "object '%s' is not a tuple or list", mp_obj_get_type_str(o_in)));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "object '%s' is not a tuple or list", mp_obj_get_type_str(o_in)));
     }
 }
 
@@ -226,11 +226,11 @@
             i += len;
         }
         if (i < 0 || i >= len) {
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_IndexError, "%s index out of range", qstr_str(type->name)));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_IndexError, "%s index out of range", qstr_str(type->name)));
         }
         return i;
     } else {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "%s indices must be integers, not %s", qstr_str(type->name), mp_obj_get_type_str(index)));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "%s indices must be integers, not %s", qstr_str(type->name), mp_obj_get_type_str(index)));
     }
 }
 
diff --git a/py/obj.h b/py/obj.h
index bed119c..dd6d217 100644
--- a/py/obj.h
+++ b/py/obj.h
@@ -188,9 +188,27 @@
 
 typedef struct _mp_obj_type_t mp_obj_type_t;
 
+// Constant types, globally accessible
+
+extern const mp_obj_type_t mp_type_type;
+extern const mp_obj_type_t mp_type_BaseException;
+extern const mp_obj_type_t mp_type_AssertionError;
+extern const mp_obj_type_t mp_type_AttributeError;
+extern const mp_obj_type_t mp_type_ImportError;
+extern const mp_obj_type_t mp_type_IndentationError;
+extern const mp_obj_type_t mp_type_IndexError;
+extern const mp_obj_type_t mp_type_KeyError;
+extern const mp_obj_type_t mp_type_NameError;
+extern const mp_obj_type_t mp_type_SyntaxError;
+extern const mp_obj_type_t mp_type_TypeError;
+extern const mp_obj_type_t mp_type_ValueError;
+extern const mp_obj_type_t mp_type_OverflowError;
+extern const mp_obj_type_t mp_type_OSError;
+extern const mp_obj_type_t mp_type_NotImplementedError;
+extern const mp_obj_type_t mp_type_StopIteration;
+
 // Constant objects, globally accessible
 
-extern const mp_obj_type_t mp_const_type;
 extern const mp_obj_t mp_const_none;
 extern const mp_obj_t mp_const_false;
 extern const mp_obj_t mp_const_true;
@@ -213,9 +231,9 @@
 mp_obj_t mp_obj_new_float(mp_float_t val);
 mp_obj_t mp_obj_new_complex(mp_float_t real, mp_float_t imag);
 #endif
-mp_obj_t mp_obj_new_exception(qstr id);
-mp_obj_t mp_obj_new_exception_msg(qstr id, const char *msg);
-mp_obj_t mp_obj_new_exception_msg_varg(qstr id, const char *fmt, ...); // counts args by number of % symbols in fmt, excluding %%; can only handle void* sizes (ie no float/double!)
+mp_obj_t mp_obj_new_exception(const mp_obj_type_t *exc_type);
+mp_obj_t mp_obj_new_exception_msg(const mp_obj_type_t *exc_type, const char *msg);
+mp_obj_t mp_obj_new_exception_msg_varg(const mp_obj_type_t *exc_type, const char *fmt, ...); // counts args by number of % symbols in fmt, excluding %%; can only handle void* sizes (ie no float/double!)
 mp_obj_t mp_obj_new_range(int start, int stop, int step);
 mp_obj_t mp_obj_new_range_iterator(int cur, int stop, int step);
 mp_obj_t mp_obj_new_fun_bc(int n_args, mp_obj_t def_args, uint n_state, const byte *code);
@@ -235,6 +253,7 @@
 
 mp_obj_type_t *mp_obj_get_type(mp_obj_t o_in);
 const char *mp_obj_get_type_str(mp_obj_t o_in);
+bool mp_obj_is_subclass(mp_obj_t object, mp_obj_t classinfo);
 
 void mp_obj_print_helper(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind);
 void mp_obj_print(mp_obj_t o, mp_print_kind_t kind);
@@ -274,8 +293,9 @@
 machine_int_t mp_obj_int_get_checked(mp_obj_t self_in);
 
 // exception
-extern const mp_obj_type_t exception_type;
-qstr mp_obj_exception_get_type(mp_obj_t self_in);
+bool mp_obj_is_exception_type(mp_obj_t self_in);
+bool mp_obj_is_exception_instance(mp_obj_t self_in);
+void mp_obj_exception_clear_traceback(mp_obj_t self_in);
 void mp_obj_exception_add_traceback(mp_obj_t self_in, qstr file, machine_uint_t line, qstr block);
 void mp_obj_exception_get_traceback(mp_obj_t self_in, machine_uint_t *n, machine_uint_t **values);
 
diff --git a/py/objarray.c b/py/objarray.c
index 4a70f9f..9e36196 100644
--- a/py/objarray.c
+++ b/py/objarray.c
@@ -82,7 +82,7 @@
 
 STATIC mp_obj_t array_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) {
     if (n_args < 1 || n_args > 2) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "unexpected # of arguments, %d given", n_args));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "unexpected # of arguments, %d given", n_args));
     }
     // TODO check args
     uint l;
@@ -160,7 +160,7 @@
 };
 
 const mp_obj_type_t array_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_array,
     .print = array_print,
     .make_new = array_make_new,
@@ -222,7 +222,7 @@
 }
 
 STATIC const mp_obj_type_t array_it_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_iterator,
     .iternext = array_it_iternext,
 };
diff --git a/py/objbool.c b/py/objbool.c
index 2dd019b..1dc5e57 100644
--- a/py/objbool.c
+++ b/py/objbool.c
@@ -29,7 +29,7 @@
     switch (n_args) {
         case 0: return mp_const_false;
         case 1: if (rt_is_true(args[0])) { return mp_const_true; } else { return mp_const_false; }
-        default: nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "bool takes at most 1 argument, %d given", n_args));
+        default: nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "bool takes at most 1 argument, %d given", n_args));
     }
 }
 
@@ -46,7 +46,7 @@
 }
 
 const mp_obj_type_t bool_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_bool,
     .print = bool_print,
     .make_new = bool_make_new,
diff --git a/py/objboundmeth.c b/py/objboundmeth.c
index 72fbc23..0b5fc10 100644
--- a/py/objboundmeth.c
+++ b/py/objboundmeth.c
@@ -40,7 +40,7 @@
 }
 
 const mp_obj_type_t bound_meth_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_bound_method,
     .call = bound_meth_call,
 };
diff --git a/py/objcell.c b/py/objcell.c
index ce8f360..3666617 100644
--- a/py/objcell.c
+++ b/py/objcell.c
@@ -25,7 +25,7 @@
 }
 
 const mp_obj_type_t cell_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_, // should never need to print cell type
 };
 
diff --git a/py/objclosure.c b/py/objclosure.c
index 39e38c9..e2de0e0 100644
--- a/py/objclosure.c
+++ b/py/objclosure.c
@@ -41,7 +41,7 @@
 }
 
 const mp_obj_type_t closure_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_closure,
     .call = closure_call,
 };
diff --git a/py/objcomplex.c b/py/objcomplex.c
index 3b5de03..188c334 100644
--- a/py/objcomplex.c
+++ b/py/objcomplex.c
@@ -66,7 +66,7 @@
         }
 
         default:
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "complex takes at most 2 arguments, %d given", n_args));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "complex takes at most 2 arguments, %d given", n_args));
     }
 }
 
@@ -86,7 +86,7 @@
 }
 
 const mp_obj_type_t complex_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_complex,
     .print = complex_print,
     .make_new = complex_make_new,
diff --git a/py/objdict.c b/py/objdict.c
index 15e738d..31a80bd 100644
--- a/py/objdict.c
+++ b/py/objdict.c
@@ -60,7 +60,7 @@
             // dict load
             mp_map_elem_t *elem = mp_map_lookup(&o->map, rhs_in, MP_MAP_LOOKUP);
             if (elem == NULL) {
-                nlr_jump(mp_obj_new_exception_msg(MP_QSTR_KeyError, "<value>"));
+                nlr_jump(mp_obj_new_exception_msg(&mp_type_KeyError, "<value>"));
             } else {
                 return elem->value;
             }
@@ -112,7 +112,7 @@
 }
 
 STATIC const mp_obj_type_t dict_it_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_iterator,
     .iternext = dict_it_iternext,
 };
@@ -187,7 +187,7 @@
     if (elem == NULL || elem->value == NULL) {
         if (deflt == NULL) {
             if (lookup_kind == MP_MAP_LOOKUP_REMOVE_IF_FOUND) {
-                nlr_jump(mp_obj_new_exception_msg(MP_QSTR_KeyError, "<value>"));
+                nlr_jump(mp_obj_new_exception_msg(&mp_type_KeyError, "<value>"));
             } else {
                 value = mp_const_none;
             }
@@ -246,7 +246,7 @@
     assert(MP_OBJ_IS_TYPE(self_in, &dict_type));
     mp_obj_dict_t *self = self_in;
     if (self->map.used == 0) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_KeyError, "popitem(): dictionary is empty"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_KeyError, "popitem(): dictionary is empty"));
     }
     mp_obj_dict_it_t *iter = mp_obj_new_dict_iterator(self, 0);
 
@@ -276,7 +276,7 @@
             || value == mp_const_stop_iteration
             || stop != mp_const_stop_iteration) {
             nlr_jump(mp_obj_new_exception_msg(
-                         MP_QSTR_ValueError,
+                         &mp_type_ValueError,
                          "dictionary update sequence has the wrong length"));
         } else {
             mp_map_lookup(&self->map, key, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value;
@@ -341,7 +341,7 @@
 }
 
 STATIC const mp_obj_type_t dict_view_it_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_iterator,
     .iternext = dict_view_it_iternext,
     .methods = NULL,            /* set operations still to come */
@@ -385,7 +385,7 @@
 
 
 STATIC const mp_obj_type_t dict_view_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_dict_view,
     .print = dict_view_print,
     .binary_op = dict_view_binary_op,
@@ -440,7 +440,7 @@
 };
 
 const mp_obj_type_t dict_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_dict,
     .print = dict_print,
     .make_new = dict_make_new,
diff --git a/py/objenumerate.c b/py/objenumerate.c
index 2ca4dcd..1c858ff 100644
--- a/py/objenumerate.c
+++ b/py/objenumerate.c
@@ -27,7 +27,7 @@
 }
 
 const mp_obj_type_t enumerate_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_enumerate,
     .make_new = enumerate_make_new,
     .iternext = enumerate_iternext,
diff --git a/py/objexcept.c b/py/objexcept.c
index e2c154d..d5c056a 100644
--- a/py/objexcept.c
+++ b/py/objexcept.c
@@ -17,19 +17,18 @@
 typedef struct mp_obj_exception_t {
     mp_obj_base_t base;
     mp_obj_t traceback; // a list object, holding (file,line,block) as numbers (not Python objects); a hack for now
-    qstr id;
     vstr_t *msg;
     mp_obj_tuple_t args;
 } mp_obj_exception_t;
 
-STATIC void exception_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) {
+STATIC void mp_obj_exception_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) {
     mp_obj_exception_t *o = o_in;
     if (o->msg != NULL) {
-        print(env, "%s: %s", qstr_str(o->id), vstr_str(o->msg));
+        print(env, "%s: %s", qstr_str(o->base.type->name), vstr_str(o->msg));
     } else {
         // Yes, that's how CPython has it
         if (kind == PRINT_REPR) {
-            print(env, "%s", qstr_str(o->id));
+            print(env, "%s", qstr_str(o->base.type->name));
         }
         if (kind == PRINT_STR) {
             if (o->args.len == 0) {
@@ -44,46 +43,74 @@
     }
 }
 
-STATIC mp_obj_t exception_call(mp_obj_t self_in, uint n_args, uint n_kw, const mp_obj_t *args) {
-    mp_obj_exception_t *base = self_in;
+STATIC mp_obj_t mp_obj_exception_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) {
+    mp_obj_type_t *type = type_in;
 
     if (n_kw != 0) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "%s does not take keyword arguments", qstr_str(base->id)));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "%s does not take keyword arguments", type->name));
     }
 
     mp_obj_exception_t *o = m_new_obj_var(mp_obj_exception_t, mp_obj_t, n_args);
-    o->base.type = &exception_type;
+    o->base.type = type;
     o->traceback = MP_OBJ_NULL;
-    o->id = base->id;
     o->msg = NULL;
     o->args.len = n_args;
     memcpy(o->args.items, args, n_args * sizeof(mp_obj_t));
     return o;
 }
 
-const mp_obj_type_t exception_type = {
-    { &mp_const_type },
-    .name = MP_QSTR_, // TODO proper exception names
-    .print = exception_print,
-    .call = exception_call,
+const mp_obj_type_t mp_type_BaseException = {
+    { &mp_type_type },
+    .name = MP_QSTR_BaseException,
+    .print = mp_obj_exception_print,
+    .make_new = mp_obj_exception_make_new,
 };
 
-mp_obj_t mp_obj_new_exception(qstr id) {
-    return mp_obj_new_exception_msg_varg(id, NULL);
+#define MP_DEFINE_EXCEPTION(exc_name) \
+STATIC const mp_obj_tuple_t mp_type_ ## exc_name ## _bases_tuple = {{&tuple_type}, 1, {(mp_obj_t)&mp_type_BaseException}};\
+const mp_obj_type_t mp_type_ ## exc_name = { \
+    { &mp_type_type }, \
+    .name = MP_QSTR_ ## exc_name, \
+    .print = mp_obj_exception_print, \
+    .make_new = mp_obj_exception_make_new, \
+    .bases_tuple = (mp_obj_t)&mp_type_ ## exc_name ## _bases_tuple, \
+};
+
+MP_DEFINE_EXCEPTION(AssertionError)
+MP_DEFINE_EXCEPTION(AttributeError)
+MP_DEFINE_EXCEPTION(ImportError)
+MP_DEFINE_EXCEPTION(IndentationError)
+MP_DEFINE_EXCEPTION(IndexError)
+MP_DEFINE_EXCEPTION(KeyError)
+MP_DEFINE_EXCEPTION(NameError)
+MP_DEFINE_EXCEPTION(SyntaxError)
+MP_DEFINE_EXCEPTION(TypeError)
+MP_DEFINE_EXCEPTION(ValueError)
+MP_DEFINE_EXCEPTION(OverflowError)
+MP_DEFINE_EXCEPTION(OSError)
+MP_DEFINE_EXCEPTION(NotImplementedError)
+MP_DEFINE_EXCEPTION(StopIteration)
+
+mp_obj_t mp_obj_new_exception(const mp_obj_type_t *exc_type) {
+    return mp_obj_new_exception_msg_varg(exc_type, NULL);
 }
 
-mp_obj_t mp_obj_new_exception_msg(qstr id, const char *msg) {
-    return mp_obj_new_exception_msg_varg(id, msg);
+mp_obj_t mp_obj_new_exception_msg(const mp_obj_type_t *exc_type, const char *msg) {
+    return mp_obj_new_exception_msg_varg(exc_type, msg);
 }
 
-mp_obj_t mp_obj_new_exception_msg_varg(qstr id, const char *fmt, ...) {
+mp_obj_t mp_obj_new_exception_msg_varg(const mp_obj_type_t *exc_type, const char *fmt, ...) {
+    // check that the given type is an exception type
+    assert(exc_type->make_new == mp_obj_exception_make_new);
+
     // make exception object
-    mp_obj_exception_t *o = m_new_obj_var(mp_obj_exception_t, mp_obj_t*, 0);
-    o->base.type = &exception_type;
+    mp_obj_exception_t *o = m_new_obj_var(mp_obj_exception_t, mp_obj_t, 0);
+    o->base.type = exc_type;
     o->traceback = MP_OBJ_NULL;
-    o->id = id;
     o->args.len = 0;
+
     if (fmt == NULL) {
+        // no message
         o->msg = NULL;
     } else {
         // render exception message
@@ -97,18 +124,41 @@
     return o;
 }
 
-qstr mp_obj_exception_get_type(mp_obj_t self_in) {
-    assert(MP_OBJ_IS_TYPE(self_in, &exception_type));
+// return true if the given object is an exception type
+// TODO make this work for user defined exceptions
+bool mp_obj_is_exception_type(mp_obj_t self_in) {
+    if (MP_OBJ_IS_TYPE(self_in, &mp_type_type)) {
+        mp_obj_type_t *self = self_in;
+        return self->make_new == mp_obj_exception_make_new;
+    } else {
+        return false;
+    }
+}
+
+// return true if the given object is an instance of an exception type
+// TODO make this work for user defined exceptions
+bool mp_obj_is_exception_instance(mp_obj_t self_in) {
+    return mp_obj_get_type(self_in)->make_new == mp_obj_exception_make_new;
+}
+
+void mp_obj_exception_clear_traceback(mp_obj_t self_in) {
+    // make sure self_in is an exception instance
+    assert(mp_obj_get_type(self_in)->make_new == mp_obj_exception_make_new);
     mp_obj_exception_t *self = self_in;
-    return self->id;
+
+    // just set the traceback to the null object
+    // we don't want to call any memory management functions here
+    self->traceback = MP_OBJ_NULL;
 }
 
 void mp_obj_exception_add_traceback(mp_obj_t self_in, qstr file, machine_uint_t line, qstr block) {
-    assert(MP_OBJ_IS_TYPE(self_in, &exception_type));
+    // make sure self_in is an exception instance
+    assert(mp_obj_get_type(self_in)->make_new == mp_obj_exception_make_new);
     mp_obj_exception_t *self = self_in;
+
     // for traceback, we are just using the list object for convenience, it's not really a list of Python objects
     if (self->traceback == MP_OBJ_NULL) {
-        self->traceback = mp_obj_new_list(0, NULL);
+        self->traceback = mp_obj_new_list(3, NULL);
     }
     mp_obj_list_append(self->traceback, (mp_obj_t)(machine_uint_t)file);
     mp_obj_list_append(self->traceback, (mp_obj_t)(machine_uint_t)line);
@@ -116,8 +166,10 @@
 }
 
 void mp_obj_exception_get_traceback(mp_obj_t self_in, machine_uint_t *n, machine_uint_t **values) {
-    assert(MP_OBJ_IS_TYPE(self_in, &exception_type));
+    // make sure self_in is an exception instance
+    assert(mp_obj_get_type(self_in)->make_new == mp_obj_exception_make_new);
     mp_obj_exception_t *self = self_in;
+
     if (self->traceback == MP_OBJ_NULL) {
         *n = 0;
         *values = NULL;
diff --git a/py/objfilter.c b/py/objfilter.c
index 4dde7fa..dc400f1 100644
--- a/py/objfilter.c
+++ b/py/objfilter.c
@@ -16,7 +16,7 @@
 
 STATIC mp_obj_t filter_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) {
     if (n_args != 2 || n_kw != 0) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "filter expected 2 arguments"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "filter expected 2 arguments"));
     }
     assert(n_args == 2);
     mp_obj_filter_t *o = m_new_obj(mp_obj_filter_t);
@@ -45,7 +45,7 @@
 }
 
 const mp_obj_type_t filter_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_filter,
     .make_new = filter_make_new,
     .getiter = mp_identity,
diff --git a/py/objfloat.c b/py/objfloat.c
index 83b9826..9d7b796 100644
--- a/py/objfloat.c
+++ b/py/objfloat.c
@@ -40,7 +40,7 @@
             }
 
         default:
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "float takes at most 1 argument, %d given", n_args));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "float takes at most 1 argument, %d given", n_args));
     }
 }
 
@@ -64,7 +64,7 @@
 }
 
 const mp_obj_type_t float_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_float,
     .print = float_print,
     .make_new = float_make_new,
diff --git a/py/objfun.c b/py/objfun.c
index 56ea692..433da03 100644
--- a/py/objfun.c
+++ b/py/objfun.c
@@ -20,23 +20,23 @@
 
 STATIC void check_nargs(mp_obj_fun_native_t *self, int n_args, int n_kw) {
     if (n_kw && !self->is_kw) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError,
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError,
                                           "function does not take keyword arguments"));
     }
 
     if (self->n_args_min == self->n_args_max) {
         if (n_args != self->n_args_min) {
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError,
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError,
                                                      "function takes %d positional arguments but %d were given",
                                                      self->n_args_min, n_args));
         }
     } else {
         if (n_args < self->n_args_min) {
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError,
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError,
                                                     "<fun name>() missing %d required positional arguments: <list of names of params>",
                                                     self->n_args_min - n_args));
         } else if (n_args > self->n_args_max) {
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError,
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError,
                                                      "<fun name> expected at most %d arguments, got %d",
                                                      self->n_args_max, n_args));
         }
@@ -89,7 +89,7 @@
 }
 
 const mp_obj_type_t fun_native_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_function,
     .call = fun_native_call,
 };
@@ -143,10 +143,10 @@
     mp_obj_fun_bc_t *self = self_in;
 
     if (n_args < self->n_args - self->n_def_args || n_args > self->n_args) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "function takes %d positional arguments but %d were given", self->n_args, n_args));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "function takes %d positional arguments but %d were given", self->n_args, n_args));
     }
     if (n_kw != 0) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "function does not take keyword arguments"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "function does not take keyword arguments"));
     }
 
     uint use_def_args = self->n_args - n_args;
@@ -159,7 +159,7 @@
 }
 
 const mp_obj_type_t fun_bc_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_function,
     .call = fun_bc_call,
 };
@@ -252,10 +252,10 @@
     mp_obj_fun_asm_t *self = self_in;
 
     if (n_args != self->n_args) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "function takes %d positional arguments but %d were given", self->n_args, n_args));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "function takes %d positional arguments but %d were given", self->n_args, n_args));
     }
     if (n_kw != 0) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "function does not take keyword arguments"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "function does not take keyword arguments"));
     }
 
     machine_uint_t ret;
@@ -276,7 +276,7 @@
 }
 
 STATIC const mp_obj_type_t fun_asm_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_function,
     .call = fun_asm_call,
 };
diff --git a/py/objgenerator.c b/py/objgenerator.c
index 4e7d3a1..226b902 100644
--- a/py/objgenerator.c
+++ b/py/objgenerator.c
@@ -28,17 +28,17 @@
     const byte *bc_code;
     mp_obj_fun_bc_get(self_fun, &bc_n_args, &bc_n_state, &bc_code);
     if (n_args != bc_n_args) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "function takes %d positional arguments but %d were given", bc_n_args, n_args));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "function takes %d positional arguments but %d were given", bc_n_args, n_args));
     }
     if (n_kw != 0) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "function does not take keyword arguments"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "function does not take keyword arguments"));
     }
 
     return mp_obj_new_gen_instance(bc_code, bc_n_state, n_args, args);
 }
 
 const mp_obj_type_t gen_wrap_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_generator,
     .call = gen_wrap_call,
 };
@@ -77,7 +77,7 @@
     }
     if (self->sp == self->state - 1) {
         if (send_value != mp_const_none) {
-            nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "can't send non-None value to a just-started generator"));
+            nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "can't send non-None value to a just-started generator"));
         }
     } else {
         *self->sp = send_value;
@@ -108,10 +108,12 @@
 STATIC mp_obj_t gen_instance_send(mp_obj_t self_in, mp_obj_t send_value) {
     mp_obj_t ret = gen_next_send(self_in, send_value);
     if (ret == mp_const_stop_iteration) {
-        nlr_jump(mp_obj_new_exception(MP_QSTR_StopIteration));
+        nlr_jump(mp_obj_new_exception(&mp_type_StopIteration));
+    } else {
+        return ret;
     }
-    return ret;
 }
+
 STATIC MP_DEFINE_CONST_FUN_OBJ_2(gen_instance_send_obj, gen_instance_send);
 
 STATIC const mp_method_t gen_type_methods[] = {
@@ -120,7 +122,7 @@
 };
 
 const mp_obj_type_t gen_instance_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_generator,
     .print = gen_instance_print,
     .getiter = gen_instance_getiter,
diff --git a/py/objgetitemiter.c b/py/objgetitemiter.c
index 28b118a..bf466c0 100644
--- a/py/objgetitemiter.c
+++ b/py/objgetitemiter.c
@@ -26,7 +26,7 @@
         return value;
     } else {
         // an exception was raised
-        if (MP_OBJ_IS_TYPE(nlr.ret_val, &exception_type) && mp_obj_exception_get_type(nlr.ret_val) == MP_QSTR_StopIteration) {
+        if (mp_obj_get_type(nlr.ret_val) == &mp_type_StopIteration) {
             // return mp_const_stop_iteration instead of raising StopIteration
             return mp_const_stop_iteration;
         } else {
@@ -37,7 +37,7 @@
 }
 
 STATIC const mp_obj_type_t it_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_iterator,
     .iternext = it_iternext
 };
diff --git a/py/objint.c b/py/objint.c
index 1ae3ceb..b33557b 100644
--- a/py/objint.c
+++ b/py/objint.c
@@ -39,7 +39,7 @@
         }
 
         default:
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "int takes at most 2 arguments, %d given", n_args));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "int takes at most 2 arguments, %d given", n_args));
     }
 }
 
@@ -65,7 +65,7 @@
 
 // This is called only with strings whose value doesn't fit in SMALL_INT
 mp_obj_t mp_obj_new_int_from_long_str(const char *s) {
-    nlr_jump(mp_obj_new_exception_msg(MP_QSTR_OverflowError, "long int not supported in this build"));
+    nlr_jump(mp_obj_new_exception_msg(&mp_type_OverflowError, "long int not supported in this build"));
     return mp_const_none;
 }
 
@@ -75,7 +75,7 @@
     if ((value & (WORD_MSBIT_HIGH | (WORD_MSBIT_HIGH >> 1))) == 0) {
         return MP_OBJ_NEW_SMALL_INT(value);
     }
-    nlr_jump(mp_obj_new_exception_msg(MP_QSTR_OverflowError, "small int overflow"));
+    nlr_jump(mp_obj_new_exception_msg(&mp_type_OverflowError, "small int overflow"));
     return mp_const_none;
 }
 
@@ -83,7 +83,7 @@
     if (MP_OBJ_FITS_SMALL_INT(value)) {
         return MP_OBJ_NEW_SMALL_INT(value);
     }
-    nlr_jump(mp_obj_new_exception_msg(MP_QSTR_OverflowError, "small int overflow"));
+    nlr_jump(mp_obj_new_exception_msg(&mp_type_OverflowError, "small int overflow"));
     return mp_const_none;
 }
 
@@ -98,7 +98,7 @@
 #endif // MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_NONE
 
 const mp_obj_type_t int_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_int,
     .print = int_print,
     .make_new = int_make_new,
diff --git a/py/objlist.c b/py/objlist.c
index 844f9cc..a6fbe4e 100644
--- a/py/objlist.c
+++ b/py/objlist.c
@@ -62,7 +62,7 @@
         }
 
         default:
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "list takes at most 1 argument, %d given", n_args));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "list takes at most 1 argument, %d given", n_args));
     }
     return NULL;
 }
@@ -188,7 +188,7 @@
     assert(MP_OBJ_IS_TYPE(args[0], &list_type));
     mp_obj_list_t *self = args[0];
     if (self->len == 0) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_IndexError, "pop from empty list"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_IndexError, "pop from empty list"));
     }
     uint index = mp_get_index(self->base.type, self->len, n_args == 1 ? mp_obj_new_int(-1) : args[1]);
     mp_obj_t ret = self->items[index];
@@ -228,7 +228,7 @@
     assert(n_args >= 1);
     assert(MP_OBJ_IS_TYPE(args[0], &list_type));
     if (n_args > 1) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError,
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError,
                                           "list.sort takes no positional arguments"));
     }
     mp_obj_list_t *self = args[0];
@@ -346,7 +346,7 @@
 };
 
 const mp_obj_type_t list_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_list,
     .print = list_print,
     .make_new = list_make_new,
@@ -408,7 +408,7 @@
 }
 
 STATIC const mp_obj_type_t list_it_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_iterator,
     .iternext = list_it_iternext,
 };
diff --git a/py/objmap.c b/py/objmap.c
index 012f0aa..cbaef6f 100644
--- a/py/objmap.c
+++ b/py/objmap.c
@@ -17,7 +17,7 @@
 
 STATIC mp_obj_t map_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) {
     if (n_args < 2 || n_kw != 0) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "map must have at least 2 arguments and no keyword arguments"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "map must have at least 2 arguments and no keyword arguments"));
     }
     assert(n_args >= 2);
     mp_obj_map_t *o = m_new_obj_var(mp_obj_map_t, mp_obj_t, n_args - 1);
@@ -51,7 +51,7 @@
 }
 
 const mp_obj_type_t map_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_map,
     .make_new = map_make_new,
     .getiter = map_getiter,
diff --git a/py/objmodule.c b/py/objmodule.c
index 14a2491..ab460fb 100644
--- a/py/objmodule.c
+++ b/py/objmodule.c
@@ -38,7 +38,7 @@
 }
 
 const mp_obj_type_t module_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_module,
     .print = module_print,
     .load_attr = module_load_attr,
diff --git a/py/objnone.c b/py/objnone.c
index 73f2601..489d34d 100644
--- a/py/objnone.c
+++ b/py/objnone.c
@@ -24,7 +24,7 @@
 }
 
 const mp_obj_type_t none_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_NoneType,
     .print = none_print,
     .unary_op = none_unary_op,
diff --git a/py/objrange.c b/py/objrange.c
index a526ebc..80c5928 100644
--- a/py/objrange.c
+++ b/py/objrange.c
@@ -24,7 +24,7 @@
 }
 
 STATIC const mp_obj_type_t range_type = {
-    { &mp_const_type} ,
+    { &mp_type_type} ,
     .name = MP_QSTR_range,
     .getiter = range_getiter,
 };
@@ -62,7 +62,7 @@
 }
 
 STATIC const mp_obj_type_t range_it_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_iterator,
     .iternext = range_it_iternext,
 };
diff --git a/py/objset.c b/py/objset.c
index 580b9de..aea107f 100644
--- a/py/objset.c
+++ b/py/objset.c
@@ -67,12 +67,12 @@
         }
 
         default:
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "set takes at most 1 argument, %d given", n_args));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "set takes at most 1 argument, %d given", n_args));
     }
 }
 
 const mp_obj_type_t set_it_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_iterator,
     .iternext = set_it_iternext,
 };
@@ -310,7 +310,7 @@
     mp_obj_set_t *self = self_in;
 
     if (self->set.used == 0) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_KeyError, "pop from an empty set"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_KeyError, "pop from an empty set"));
     }
     mp_obj_t obj = mp_set_lookup(&self->set, NULL,
                          MP_MAP_LOOKUP_REMOVE_IF_FOUND | MP_MAP_LOOKUP_FIRST);
@@ -322,7 +322,7 @@
     assert(MP_OBJ_IS_TYPE(self_in, &set_type));
     mp_obj_set_t *self = self_in;
     if (mp_set_lookup(&self->set, item, MP_MAP_LOOKUP_REMOVE_IF_FOUND) == MP_OBJ_NULL) {
-        nlr_jump(mp_obj_new_exception(MP_QSTR_KeyError));
+        nlr_jump(mp_obj_new_exception(&mp_type_KeyError));
     }
     return mp_const_none;
 }
@@ -446,7 +446,7 @@
 };
 
 const mp_obj_type_t set_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_set,
     .print = set_print,
     .make_new = set_make_new,
diff --git a/py/objslice.c b/py/objslice.c
index 66a3c7a..10df671 100644
--- a/py/objslice.c
+++ b/py/objslice.c
@@ -22,7 +22,7 @@
 }
 
 const mp_obj_type_t ellipsis_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_Ellipsis,
     .print = ellipsis_print,
 };
@@ -49,7 +49,7 @@
 }
 
 const mp_obj_type_t slice_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_slice,
     .print = slice_print,
 };
diff --git a/py/objstr.c b/py/objstr.c
index 6ccd239..a1291a2 100644
--- a/py/objstr.c
+++ b/py/objstr.c
@@ -124,7 +124,7 @@
             } else {
                 // Message doesn't match CPython, but we don't have so much bytes as they
                 // to spend them on verbose wording
-                nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "index must be int"));
+                nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "index must be int"));
             }
 
         case RT_BINARY_OP_ADD:
@@ -235,7 +235,7 @@
     return mp_obj_str_builder_end(joined_str);
 
 bad_arg:
-    nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "?str.join expecting a list of str's"));
+    nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "?str.join expecting a list of str's"));
 }
 
 #define is_ws(c) ((c) == ' ' || (c) == '\t')
@@ -387,7 +387,7 @@
             } else {
                 while (str < top && *str != '}') str++;
                 if (arg_i >= n_args) {
-                    nlr_jump(mp_obj_new_exception_msg(MP_QSTR_IndexError, "tuple index out of range"));
+                    nlr_jump(mp_obj_new_exception_msg(&mp_type_IndexError, "tuple index out of range"));
                 }
                 // TODO: may be PRINT_REPR depending on formatting code
                 mp_obj_print_helper((void (*)(void*, const char*, ...))vstr_printf, vstr, args[arg_i], PRINT_STR);
@@ -507,7 +507,7 @@
 };
 
 const mp_obj_type_t str_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_str,
     .print = str_print,
     .binary_op = str_binary_op,
@@ -517,7 +517,7 @@
 
 // Reuses most of methods from str
 const mp_obj_type_t bytes_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_bytes,
     .print = str_print,
     .binary_op = str_binary_op,
@@ -589,7 +589,7 @@
 
 void bad_implicit_conversion(mp_obj_t self_in) __attribute__((noreturn));
 void bad_implicit_conversion(mp_obj_t self_in) {
-    nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "Can't convert '%s' object to str implicitly", mp_obj_get_type_str(self_in)));
+    nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "Can't convert '%s' object to str implicitly", mp_obj_get_type_str(self_in)));
 }
 
 uint mp_obj_str_get_hash(mp_obj_t self_in) {
@@ -667,7 +667,7 @@
 }
 
 STATIC const mp_obj_type_t str_it_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_iterator,
     .iternext = str_it_iternext,
 };
@@ -685,7 +685,7 @@
 }
 
 STATIC const mp_obj_type_t bytes_it_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_iterator,
     .iternext = bytes_it_iternext,
 };
diff --git a/py/objtuple.c b/py/objtuple.c
index de49ce7..41ad8a5 100644
--- a/py/objtuple.c
+++ b/py/objtuple.c
@@ -70,7 +70,7 @@
         }
 
         default:
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "tuple takes at most 1 argument, %d given", n_args));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "tuple takes at most 1 argument, %d given", n_args));
     }
 }
 
@@ -174,7 +174,7 @@
 };
 
 const mp_obj_type_t tuple_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_tuple,
     .print = tuple_print,
     .make_new = tuple_make_new,
@@ -241,7 +241,7 @@
 }
 
 STATIC const mp_obj_type_t tuple_it_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_iterator,
     .iternext = tuple_it_iternext,
 };
diff --git a/py/objtype.c b/py/objtype.c
index a159214..46b96c7 100644
--- a/py/objtype.c
+++ b/py/objtype.c
@@ -64,7 +64,7 @@
             return NULL;
         }
         for (uint i = 0; i < len - 1; i++) {
-            assert(MP_OBJ_IS_TYPE(items[i], &mp_const_type));
+            assert(MP_OBJ_IS_TYPE(items[i], &mp_type_type));
             mp_obj_t obj = mp_obj_class_lookup((mp_obj_type_t*)items[i], attr);
             if (obj != MP_OBJ_NULL) {
                 return obj;
@@ -72,7 +72,7 @@
         }
 
         // search last base (simple tail recursion elimination)
-        assert(MP_OBJ_IS_TYPE(items[len - 1], &mp_const_type));
+        assert(MP_OBJ_IS_TYPE(items[len - 1], &mp_type_type));
         type = (mp_obj_type_t*)items[len - 1];
     }
 }
@@ -82,7 +82,7 @@
 }
 
 STATIC mp_obj_t class_make_new(mp_obj_t self_in, uint n_args, uint n_kw, const mp_obj_t *args) {
-    assert(MP_OBJ_IS_TYPE(self_in, &mp_const_type));
+    assert(MP_OBJ_IS_TYPE(self_in, &mp_type_type));
     mp_obj_type_t *self = self_in;
 
     mp_obj_t o = mp_obj_new_class(self_in);
@@ -103,13 +103,13 @@
             m_del(mp_obj_t, args2, 1 + n_args + 2 * n_kw);
         }
         if (init_ret != mp_const_none) {
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "__init__() should return None, not '%s'", mp_obj_get_type_str(init_ret)));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "__init__() should return None, not '%s'", mp_obj_get_type_str(init_ret)));
         }
 
     } else {
         // TODO
         if (n_args != 0) {
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "function takes 0 positional arguments but %d were given", n_args));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "function takes 0 positional arguments but %d were given", n_args));
         }
     }
 
@@ -252,7 +252,7 @@
 /******************************************************************************/
 // type object
 //  - the struct is mp_obj_type_t and is defined in obj.h so const types can be made
-//  - there is a constant mp_obj_type_t (called mp_const_type) for the 'type' object
+//  - there is a constant mp_obj_type_t (called mp_type_type) for the 'type' object
 //  - creating a new class (a new type) creates a new mp_obj_type_t
 
 STATIC void type_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
@@ -274,7 +274,7 @@
             return mp_obj_new_type(mp_obj_str_get_qstr(args[0]), args[1], args[2]);
 
         default:
-            nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "type takes 1 or 3 arguments"));
+            nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "type takes 1 or 3 arguments"));
     }
 }
 
@@ -284,7 +284,7 @@
     mp_obj_type_t *self = self_in;
 
     if (self->make_new == NULL) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "cannot create '%s' instances", qstr_str(self->name)));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "cannot create '%s' instances", qstr_str(self->name)));
     }
 
     // make new instance
@@ -296,7 +296,7 @@
 
 // for fail, do nothing; for attr, dest[0] = value; for method, dest[0] = method, dest[1] = self
 STATIC void type_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
-    assert(MP_OBJ_IS_TYPE(self_in, &mp_const_type));
+    assert(MP_OBJ_IS_TYPE(self_in, &mp_type_type));
     mp_obj_type_t *self = self_in;
     mp_obj_t member = mp_obj_class_lookup(self, attr);
     if (member != MP_OBJ_NULL) {
@@ -318,7 +318,7 @@
 }
 
 STATIC bool type_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) {
-    assert(MP_OBJ_IS_TYPE(self_in, &mp_const_type));
+    assert(MP_OBJ_IS_TYPE(self_in, &mp_type_type));
     mp_obj_type_t *self = self_in;
 
     // TODO CPython allows STORE_ATTR to a class, but is this the correct implementation?
@@ -333,8 +333,8 @@
     }
 }
 
-const mp_obj_type_t mp_const_type = {
-    { &mp_const_type },
+const mp_obj_type_t mp_type_type = {
+    { &mp_type_type },
     .name = MP_QSTR_type,
     .print = type_print,
     .make_new = type_make_new,
@@ -347,7 +347,7 @@
     assert(MP_OBJ_IS_TYPE(bases_tuple, &tuple_type)); // Micro Python restriction, for now
     assert(MP_OBJ_IS_TYPE(locals_dict, &dict_type)); // Micro Python restriction, for now
     mp_obj_type_t *o = m_new0(mp_obj_type_t, 1);
-    o->base.type = &mp_const_type;
+    o->base.type = &mp_type_type;
     o->name = name;
     o->print = class_print;
     o->make_new = class_make_new;
@@ -383,7 +383,7 @@
     if (n_args != 2 || n_kw != 0) {
         // 0 arguments are turned into 2 in the compiler
         // 1 argument is not yet implemented
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "super() requires 2 arguments"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "super() requires 2 arguments"));
     }
     return mp_obj_new_super(args[0], args[1]);
 }
@@ -393,7 +393,7 @@
     assert(MP_OBJ_IS_TYPE(self_in, &super_type));
     mp_obj_super_t *self = self_in;
 
-    assert(MP_OBJ_IS_TYPE(self->type, &mp_const_type));
+    assert(MP_OBJ_IS_TYPE(self->type, &mp_type_type));
 
     mp_obj_type_t *type = self->type;
 
@@ -406,7 +406,7 @@
     mp_obj_t *items;
     mp_obj_tuple_get(type->bases_tuple, &len, &items);
     for (uint i = 0; i < len; i++) {
-        assert(MP_OBJ_IS_TYPE(items[i], &mp_const_type));
+        assert(MP_OBJ_IS_TYPE(items[i], &mp_type_type));
         mp_obj_t member = mp_obj_class_lookup((mp_obj_type_t*)items[i], attr);
         if (member != MP_OBJ_NULL) {
             // XXX this and the code in class_load_attr need to be factored out
@@ -438,7 +438,7 @@
 }
 
 const mp_obj_type_t super_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_super,
     .print = super_print,
     .make_new = super_make_new,
@@ -452,42 +452,42 @@
 }
 
 /******************************************************************************/
-// built-ins specific to types
+// subclassing and built-ins specific to types
 
-STATIC mp_obj_t mp_builtin_issubclass(mp_obj_t object, mp_obj_t classinfo) {
-    if (!MP_OBJ_IS_TYPE(object, &mp_const_type)) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "issubclass() arg 1 must be a class"));
+bool mp_obj_is_subclass(mp_obj_t object, mp_obj_t classinfo) {
+    if (!MP_OBJ_IS_TYPE(object, &mp_type_type)) {
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "issubclass() arg 1 must be a class"));
     }
 
     // TODO support a tuple of classes for second argument
-    if (!MP_OBJ_IS_TYPE(classinfo, &mp_const_type)) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "issubclass() arg 2 must be a class"));
+    if (!MP_OBJ_IS_TYPE(classinfo, &mp_type_type)) {
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "issubclass() arg 2 must be a class"));
     }
 
     for (;;) {
         if (object == classinfo) {
-            return mp_const_true;
+            return true;
         }
 
         // not equivalent classes, keep searching base classes
 
-        assert(MP_OBJ_IS_TYPE(object, &mp_const_type));
+        assert(MP_OBJ_IS_TYPE(object, &mp_type_type));
         mp_obj_type_t *self = object;
 
         // for a const struct, this entry might be NULL
         if (self->bases_tuple == MP_OBJ_NULL) {
-            return mp_const_false;
+            return false;
         }
 
         uint len;
         mp_obj_t *items;
         mp_obj_tuple_get(self->bases_tuple, &len, &items);
         if (len == 0) {
-            return mp_const_false;
+            return false;
         }
         for (uint i = 0; i < len - 1; i++) {
-            if (mp_builtin_issubclass(items[i], classinfo) == mp_const_true) {
-                return mp_const_true;
+            if (mp_obj_is_subclass(items[i], classinfo)) {
+                return true;
             }
         }
 
@@ -496,10 +496,14 @@
     }
 }
 
+STATIC mp_obj_t mp_builtin_issubclass(mp_obj_t object, mp_obj_t classinfo) {
+    return MP_BOOL(mp_obj_is_subclass(object, classinfo));
+}
+
 MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_issubclass_obj, mp_builtin_issubclass);
 
 STATIC mp_obj_t mp_builtin_isinstance(mp_obj_t object, mp_obj_t classinfo) {
-    return mp_builtin_issubclass(mp_obj_get_type(object), classinfo);
+    return MP_BOOL(mp_obj_is_subclass(mp_obj_get_type(object), classinfo));
 }
 
 MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_isinstance_obj, mp_builtin_isinstance);
@@ -511,7 +515,7 @@
     assert(self_in == &mp_type_staticmethod || self_in == &mp_type_classmethod);
 
     if (n_args != 1 || n_kw != 0) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "function takes 1 positional argument but %d were given", n_args));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "function takes 1 positional argument but %d were given", n_args));
     }
 
     mp_obj_static_class_method_t *o = m_new_obj(mp_obj_static_class_method_t);
@@ -520,13 +524,13 @@
 }
 
 const mp_obj_type_t mp_type_staticmethod = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_staticmethod,
     .make_new = static_class_method_make_new
 };
 
 const mp_obj_type_t mp_type_classmethod = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_classmethod,
     .make_new = static_class_method_make_new
 };
diff --git a/py/objzip.c b/py/objzip.c
index 8f1bfe1..b939ff6 100644
--- a/py/objzip.c
+++ b/py/objzip.c
@@ -51,7 +51,7 @@
 }
 
 const mp_obj_type_t zip_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_zip,
     .make_new = zip_make_new,
     .getiter = zip_getiter,
diff --git a/py/parse.c b/py/parse.c
index d2e892b..bbab19d 100644
--- a/py/parse.c
+++ b/py/parse.c
@@ -302,7 +302,7 @@
     push_result_node(parser, (mp_parse_node_t)pn);
 }
 
-mp_parse_node_t mp_parse(mp_lexer_t *lex, mp_parse_input_kind_t input_kind, qstr *exc_id_out, const char **exc_msg_out) {
+mp_parse_node_t mp_parse(mp_lexer_t *lex, mp_parse_input_kind_t input_kind, mp_parse_error_kind_t *parse_error_kind_out) {
 
     // allocate memory for the parser and its stacks
 
@@ -634,19 +634,18 @@
 
 syntax_error:
     if (mp_lexer_is_kind(lex, MP_TOKEN_INDENT)) {
-        *exc_id_out = MP_QSTR_IndentationError;
-        *exc_msg_out = "unexpected indent";
+        *parse_error_kind_out = MP_PARSE_ERROR_UNEXPECTED_INDENT;
     } else if (mp_lexer_is_kind(lex, MP_TOKEN_DEDENT_MISMATCH)) {
-        *exc_id_out = MP_QSTR_IndentationError;
-        *exc_msg_out = "unindent does not match any outer indentation level";
+        *parse_error_kind_out = MP_PARSE_ERROR_UNMATCHED_UNINDENT;
     } else {
-        *exc_id_out = MP_QSTR_SyntaxError;
-        *exc_msg_out = "invalid syntax";
+        *parse_error_kind_out = MP_PARSE_ERROR_INVALID_SYNTAX;
 #ifdef USE_RULE_NAME
         // debugging: print the rule name that failed and the token
-        mp_lexer_show_error_pythonic(lex, rule->rule_name);
+        printf("rule: %s\n", rule->rule_name);
+#if MICROPY_DEBUG_PRINTERS
         mp_token_show(mp_lexer_cur(lex));
 #endif
+#endif
     }
     result = MP_PARSE_NODE_NULL;
     goto finished;
diff --git a/py/parse.h b/py/parse.h
index 9797873..66efd8a 100644
--- a/py/parse.h
+++ b/py/parse.h
@@ -63,5 +63,11 @@
     MP_PARSE_EVAL_INPUT,
 } mp_parse_input_kind_t;
 
-// returns MP_PARSE_NODE_NULL on error, and then exc_id_out and exc_msg_out are valid
-mp_parse_node_t mp_parse(struct _mp_lexer_t *lex, mp_parse_input_kind_t input_kind, qstr *exc_id_out, const char **exc_msg_out);
+typedef enum {
+    MP_PARSE_ERROR_UNEXPECTED_INDENT,
+    MP_PARSE_ERROR_UNMATCHED_UNINDENT,
+    MP_PARSE_ERROR_INVALID_SYNTAX,
+} mp_parse_error_kind_t;
+
+// returns MP_PARSE_NODE_NULL on error, and then parse_error_kind_out is valid
+mp_parse_node_t mp_parse(struct _mp_lexer_t *lex, mp_parse_input_kind_t input_kind, mp_parse_error_kind_t *parse_error_kind_out);
diff --git a/py/parsehelper.c b/py/parsehelper.c
new file mode 100644
index 0000000..3177e9a
--- /dev/null
+++ b/py/parsehelper.c
@@ -0,0 +1,49 @@
+// these functions are separate from parse.c to keep parser independent of mp_obj_t
+
+#include <stdint.h>
+#include <stdio.h>
+
+#include "misc.h"
+#include "mpconfig.h"
+#include "qstr.h"
+#include "lexer.h"
+#include "parse.h"
+#include "obj.h"
+#include "parsehelper.h"
+
+#define STR_UNEXPECTED_INDENT "unexpected indent"
+#define STR_UNMATCHED_UNINDENT "unindent does not match any outer indentation level"
+#define STR_INVALID_SYNTAX "invalid syntax"
+
+void mp_parse_show_exception(mp_lexer_t *lex, mp_parse_error_kind_t parse_error_kind) {
+    printf("  File \"%s\", line %d, column %d\n", qstr_str(mp_lexer_source_name(lex)), mp_lexer_cur(lex)->src_line, mp_lexer_cur(lex)->src_column);
+    switch (parse_error_kind) {
+        case MP_PARSE_ERROR_UNEXPECTED_INDENT:
+            printf("IndentationError: %s\n", STR_UNEXPECTED_INDENT);
+            break;
+
+        case MP_PARSE_ERROR_UNMATCHED_UNINDENT:
+            printf("IndentationError: %s\n", STR_UNMATCHED_UNINDENT);
+            break;
+
+        case MP_PARSE_ERROR_INVALID_SYNTAX:
+        default:
+            printf("SyntaxError: %s\n", STR_INVALID_SYNTAX);
+            break;
+    }
+}
+
+mp_obj_t mp_parse_make_exception(mp_parse_error_kind_t parse_error_kind) {
+    // TODO add source file and line number to exception?
+    switch (parse_error_kind) {
+        case MP_PARSE_ERROR_UNEXPECTED_INDENT:
+            return mp_obj_new_exception_msg(&mp_type_IndentationError, STR_UNEXPECTED_INDENT);
+
+        case MP_PARSE_ERROR_UNMATCHED_UNINDENT:
+            return mp_obj_new_exception_msg(&mp_type_IndentationError, STR_UNMATCHED_UNINDENT);
+
+        case MP_PARSE_ERROR_INVALID_SYNTAX:
+        default:
+            return mp_obj_new_exception_msg(&mp_type_SyntaxError, STR_INVALID_SYNTAX);
+    }
+}
diff --git a/py/parsehelper.h b/py/parsehelper.h
new file mode 100644
index 0000000..1de70d1
--- /dev/null
+++ b/py/parsehelper.h
@@ -0,0 +1,2 @@
+void mp_parse_show_exception(mp_lexer_t *lex, mp_parse_error_kind_t parse_error_kind);
+mp_obj_t mp_parse_make_exception(mp_parse_error_kind_t parse_error_kind);
diff --git a/py/py.mk b/py/py.mk
index 1d9eb53..cbb63b4 100644
--- a/py/py.mk
+++ b/py/py.mk
@@ -19,6 +19,7 @@
 	lexerstr.o \
 	lexerunix.o \
 	parse.o \
+	parsehelper.o \
 	scope.o \
 	compile.o \
 	emitcommon.o \
diff --git a/py/qstrdefs.h b/py/qstrdefs.h
index ac106f9..9f00277 100644
--- a/py/qstrdefs.h
+++ b/py/qstrdefs.h
@@ -30,6 +30,7 @@
 Q(Ellipsis)
 Q(StopIteration)
 
+Q(BaseException)
 Q(AssertionError)
 Q(AttributeError)
 Q(ImportError)
diff --git a/py/runtime.c b/py/runtime.c
index b473a95..68b8fe0 100644
--- a/py/runtime.c
+++ b/py/runtime.c
@@ -108,7 +108,7 @@
     { MP_QSTR_set, (mp_obj_t)&set_type },
     { MP_QSTR_super, (mp_obj_t)&super_type },
     { MP_QSTR_tuple, (mp_obj_t)&tuple_type },
-    { MP_QSTR_type, (mp_obj_t)&mp_const_type },
+    { MP_QSTR_type, (mp_obj_t)&mp_type_type },
     { MP_QSTR_zip, (mp_obj_t)&zip_type },
 
     { MP_QSTR_classmethod, (mp_obj_t)&mp_type_classmethod },
@@ -144,6 +144,25 @@
     { MP_QSTR_str, (mp_obj_t)&mp_builtin_str_obj },
     { MP_QSTR_bytearray, (mp_obj_t)&mp_builtin_bytearray_obj },
 
+    // built-in exceptions
+    { MP_QSTR_BaseException, (mp_obj_t)&mp_type_BaseException },
+    { MP_QSTR_AssertionError, (mp_obj_t)&mp_type_AssertionError },
+    { MP_QSTR_AttributeError, (mp_obj_t)&mp_type_AttributeError },
+    { MP_QSTR_ImportError, (mp_obj_t)&mp_type_ImportError },
+    { MP_QSTR_IndentationError, (mp_obj_t)&mp_type_IndentationError },
+    { MP_QSTR_IndexError, (mp_obj_t)&mp_type_IndexError },
+    { MP_QSTR_KeyError, (mp_obj_t)&mp_type_KeyError },
+    { MP_QSTR_NameError, (mp_obj_t)&mp_type_NameError },
+    { MP_QSTR_SyntaxError, (mp_obj_t)&mp_type_SyntaxError },
+    { MP_QSTR_TypeError, (mp_obj_t)&mp_type_TypeError },
+    { MP_QSTR_ValueError, (mp_obj_t)&mp_type_ValueError },
+    // Somehow CPython managed to have OverflowError not inherit from ValueError ;-/
+    // TODO: For MICROPY_CPYTHON_COMPAT==0 use ValueError to avoid exc proliferation
+    { MP_QSTR_OverflowError, (mp_obj_t)&mp_type_OverflowError },
+    { MP_QSTR_OSError, (mp_obj_t)&mp_type_OSError },
+    { MP_QSTR_NotImplementedError, (mp_obj_t)&mp_type_NotImplementedError },
+    { MP_QSTR_StopIteration, (mp_obj_t)&mp_type_StopIteration },
+
     // Extra builtins as defined by a port
     MICROPY_EXTRA_BUILTINS
 
@@ -166,23 +185,6 @@
     // init loaded modules table
     mp_map_init(&map_loaded_modules, 3);
 
-    // built-in exceptions (TODO, make these proper classes, and const if possible)
-    mp_map_add_qstr(&map_builtins, MP_QSTR_AttributeError, mp_obj_new_exception(MP_QSTR_AttributeError));
-    mp_map_add_qstr(&map_builtins, MP_QSTR_ImportError, mp_obj_new_exception(MP_QSTR_ImportError));
-    mp_map_add_qstr(&map_builtins, MP_QSTR_IndexError, mp_obj_new_exception(MP_QSTR_IndexError));
-    mp_map_add_qstr(&map_builtins, MP_QSTR_KeyError, mp_obj_new_exception(MP_QSTR_KeyError));
-    mp_map_add_qstr(&map_builtins, MP_QSTR_NameError, mp_obj_new_exception(MP_QSTR_NameError));
-    mp_map_add_qstr(&map_builtins, MP_QSTR_TypeError, mp_obj_new_exception(MP_QSTR_TypeError));
-    mp_map_add_qstr(&map_builtins, MP_QSTR_SyntaxError, mp_obj_new_exception(MP_QSTR_SyntaxError));
-    mp_map_add_qstr(&map_builtins, MP_QSTR_ValueError, mp_obj_new_exception(MP_QSTR_ValueError));
-    // Somehow CPython managed to have OverflowError not inherit from ValueError ;-/
-    // TODO: For MICROPY_CPYTHON_COMPAT==0 use ValueError to avoid exc proliferation
-    mp_map_add_qstr(&map_builtins, MP_QSTR_OverflowError, mp_obj_new_exception(MP_QSTR_OverflowError));
-    mp_map_add_qstr(&map_builtins, MP_QSTR_OSError, mp_obj_new_exception(MP_QSTR_OSError));
-    mp_map_add_qstr(&map_builtins, MP_QSTR_AssertionError, mp_obj_new_exception(MP_QSTR_AssertionError));
-    mp_map_add_qstr(&map_builtins, MP_QSTR_NotImplementedError, mp_obj_new_exception(MP_QSTR_NotImplementedError));
-    mp_map_add_qstr(&map_builtins, MP_QSTR_StopIteration, mp_obj_new_exception(MP_QSTR_StopIteration));
-
     // built-in objects
     mp_map_add_qstr(&map_builtins, MP_QSTR_Ellipsis, mp_const_ellipsis);
 
@@ -413,7 +415,7 @@
         }
     }
     if (*s != 0) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_SyntaxError, "invalid syntax for number"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_SyntaxError, "invalid syntax for number"));
     }
     if (exp_neg) {
         exp_val = -exp_val;
@@ -431,7 +433,7 @@
         return mp_obj_new_float(dec_val);
     }
 #else
-    nlr_jump(mp_obj_new_exception_msg(MP_QSTR_SyntaxError, "decimal numbers not supported"));
+    nlr_jump(mp_obj_new_exception_msg(&mp_type_SyntaxError, "decimal numbers not supported"));
 #endif
 }
 
@@ -470,7 +472,7 @@
                     return e->fun;
                 }
             }
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_NameError, "name '%s' is not defined", qstr_str(qstr)));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_NameError, "name '%s' is not defined", qstr_str(qstr)));
         }
     }
     return elem->value;
@@ -529,7 +531,7 @@
             }
         }
         // TODO specify in error message what the operator is
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "bad operand type for unary operator: '%s'", type->name));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "bad operand type for unary operator: '%s'", type->name));
     }
 }
 
@@ -569,9 +571,13 @@
 
     // deal with exception_match for all types
     if (op == RT_BINARY_OP_EXCEPTION_MATCH) {
-        // TODO properly! at the moment it just compares the exception identifier for equality
-        if (MP_OBJ_IS_TYPE(lhs, &exception_type) && MP_OBJ_IS_TYPE(rhs, &exception_type)) {
-            if (mp_obj_exception_get_type(lhs) == mp_obj_exception_get_type(rhs)) {
+        // rhs must be issubclass(rhs, BaseException)
+        if (mp_obj_is_exception_type(rhs)) {
+            // if lhs is an instance of an exception, then extract and use its type
+            if (mp_obj_is_exception_instance(lhs)) {
+                lhs = mp_obj_get_type(lhs);
+            }
+            if (mp_obj_is_subclass(lhs, rhs)) {
                 return mp_const_true;
             } else {
                 return mp_const_false;
@@ -673,7 +679,7 @@
         }
 
         nlr_jump(mp_obj_new_exception_msg_varg(
-                     MP_QSTR_TypeError, "'%s' object is not iterable",
+                     &mp_type_TypeError, "'%s' object is not iterable",
                      mp_obj_get_type_str(rhs)));
         return mp_const_none;
     }
@@ -690,7 +696,7 @@
     // TODO implement dispatch for reverse binary ops
 
     // TODO specify in error message what the operator is
-    nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError,
+    nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError,
         "unsupported operand types for binary operator: '%s', '%s'",
         mp_obj_get_type_str(lhs), mp_obj_get_type_str(rhs)));
     return mp_const_none;
@@ -772,7 +778,7 @@
     if (type->call != NULL) {
         return type->call(fun_in, n_args, n_kw, args);
     } else {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "'%s' object is not callable", type->name));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "'%s' object is not callable", type->name));
     }
 }
 
@@ -836,9 +842,9 @@
     return;
 
 too_short:
-    nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_ValueError, "need more than %d values to unpack", seq_len));
+    nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "need more than %d values to unpack", seq_len));
 too_long:
-    nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_ValueError, "too many values to unpack (expected %d)", num));
+    nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "too many values to unpack (expected %d)", num));
 }
 
 mp_obj_t rt_build_map(int n_args) {
@@ -925,10 +931,10 @@
     if (dest[0] == MP_OBJ_NULL) {
         // no attribute/method called attr
         // following CPython, we give a more detailed error message for type objects
-        if (MP_OBJ_IS_TYPE(base, &mp_const_type)) {
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_AttributeError, "type object '%s' has no attribute '%s'", ((mp_obj_type_t*)base)->name, qstr_str(attr)));
+        if (MP_OBJ_IS_TYPE(base, &mp_type_type)) {
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_AttributeError, "type object '%s' has no attribute '%s'", ((mp_obj_type_t*)base)->name, qstr_str(attr)));
         } else {
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_AttributeError, "'%s' object has no attribute '%s'", mp_obj_get_type_str(base), qstr_str(attr)));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_AttributeError, "'%s' object has no attribute '%s'", mp_obj_get_type_str(base), qstr_str(attr)));
         }
     }
 }
@@ -941,7 +947,7 @@
             return;
         }
     }
-    nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_AttributeError, "'%s' object has no attribute '%s'", mp_obj_get_type_str(base), qstr_str(attr)));
+    nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_AttributeError, "'%s' object has no attribute '%s'", mp_obj_get_type_str(base), qstr_str(attr)));
 }
 
 void rt_store_subscr(mp_obj_t base, mp_obj_t index, mp_obj_t value) {
@@ -961,7 +967,7 @@
             }
             // TODO: call base classes here?
         }
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "'%s' object does not support item assignment", mp_obj_get_type_str(base)));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "'%s' object does not support item assignment", mp_obj_get_type_str(base)));
     }
 }
 
@@ -978,7 +984,7 @@
             return mp_obj_new_getitem_iter(dest);
         } else {
             // object not iterable
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "'%s' object is not iterable", type->name));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "'%s' object is not iterable", type->name));
         }
     }
 }
@@ -988,7 +994,22 @@
     if (type->iternext != NULL) {
         return type->iternext(o_in);
     } else {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "'%s' object is not an iterator", type->name));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "'%s' object is not an iterator", type->name));
+    }
+}
+
+mp_obj_t rt_make_raise_obj(mp_obj_t o) {
+    DEBUG_printf("raise %p\n", o);
+    if (mp_obj_is_exception_type(o)) {
+        // o is an exception type (it is derived from BaseException (or is BaseException))
+        // create and return a new exception instance by calling o
+        return rt_call_function_n_kw(o, 0, 0, NULL);
+    } else if (mp_obj_is_exception_instance(o)) {
+        // o is an instance of an exception, so use it as the exception
+        return o;
+    } else {
+        // o cannot be used as an exception, so return a type error (which will be raised by the caller)
+        return mp_obj_new_exception_msg(&mp_type_TypeError, "exceptions must derive from BaseException");
     }
 }
 
diff --git a/py/runtime.h b/py/runtime.h
index f5a9f2a..1eef99d 100644
--- a/py/runtime.h
+++ b/py/runtime.h
@@ -37,6 +37,7 @@
 void rt_store_subscr(mp_obj_t base, mp_obj_t index, mp_obj_t val);
 mp_obj_t rt_getiter(mp_obj_t o);
 mp_obj_t rt_iternext(mp_obj_t o);
+mp_obj_t rt_make_raise_obj(mp_obj_t o);
 mp_obj_t rt_import_name(qstr name, mp_obj_t fromlist, mp_obj_t level);
 mp_obj_t rt_import_from(mp_obj_t module, qstr name);
 void rt_import_all(mp_obj_t module);
diff --git a/py/sequence.c b/py/sequence.c
index f531073..d3c3e32 100644
--- a/py/sequence.c
+++ b/py/sequence.c
@@ -162,7 +162,7 @@
         }
     }
 
-    nlr_jump(mp_obj_new_exception_msg(MP_QSTR_ValueError, "object not in sequence"));
+    nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "object not in sequence"));
 }
 
 mp_obj_t mp_seq_count_obj(const mp_obj_t *items, uint len, mp_obj_t value) {
diff --git a/py/stream.c b/py/stream.c
index f3487cc..59877d7 100644
--- a/py/stream.c
+++ b/py/stream.c
@@ -16,7 +16,7 @@
     struct _mp_obj_base_t *o = (struct _mp_obj_base_t *)args[0];
     if (o->type->stream_p.read == NULL) {
         // CPython: io.UnsupportedOperation, OSError subclass
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_OSError, "Operation not supported"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_OSError, "Operation not supported"));
     }
 
     machine_int_t sz;
@@ -27,7 +27,7 @@
     int error;
     machine_int_t out_sz = o->type->stream_p.read(o, buf, sz, &error);
     if (out_sz == -1) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "[Errno %d]", error));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_OSError, "[Errno %d]", error));
     } else {
         mp_obj_t s = mp_obj_new_str(buf, out_sz, false); // will reallocate to use exact size
         m_free(buf, sz);
@@ -39,7 +39,7 @@
     struct _mp_obj_base_t *o = (struct _mp_obj_base_t *)self_in;
     if (o->type->stream_p.write == NULL) {
         // CPython: io.UnsupportedOperation, OSError subclass
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_OSError, "Operation not supported"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_OSError, "Operation not supported"));
     }
 
     uint sz;
@@ -47,7 +47,7 @@
     int error;
     machine_int_t out_sz = o->type->stream_p.write(self_in, buf, sz, &error);
     if (out_sz == -1) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "[Errno %d]", error));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_OSError, "[Errno %d]", error));
     } else {
         // http://docs.python.org/3/library/io.html#io.RawIOBase.write
         // "None is returned if the raw stream is set not to block and no single byte could be readily written to it."
@@ -62,7 +62,7 @@
     struct _mp_obj_base_t *o = (struct _mp_obj_base_t *)self_in;
     if (o->type->stream_p.read == NULL) {
         // CPython: io.UnsupportedOperation, OSError subclass
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_OSError, "Operation not supported"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_OSError, "Operation not supported"));
     }
 
     int total_size = 0;
@@ -74,7 +74,7 @@
     while (true) {
         machine_int_t out_sz = o->type->stream_p.read(self_in, p, current_read, &error);
         if (out_sz == -1) {
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "[Errno %d]", error));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_OSError, "[Errno %d]", error));
         }
         if (out_sz == 0) {
             break;
@@ -88,7 +88,7 @@
             p = vstr_extend(vstr, current_read);
             if (p == NULL) {
                 // TODO
-                nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError/*MP_QSTR_RuntimeError*/, "Out of memory"));
+                nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_OSError/*&mp_type_RuntimeError*/, "Out of memory"));
             }
         }
     }
@@ -103,7 +103,7 @@
     struct _mp_obj_base_t *o = (struct _mp_obj_base_t *)args[0];
     if (o->type->stream_p.read == NULL) {
         // CPython: io.UnsupportedOperation, OSError subclass
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_OSError, "Operation not supported"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_OSError, "Operation not supported"));
     }
 
     machine_int_t max_size = -1;
@@ -123,12 +123,12 @@
         char *p = vstr_add_len(vstr, 1);
         if (p == NULL) {
             // TODO
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError/*MP_QSTR_RuntimeError*/, "Out of memory"));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_OSError/*&mp_type_RuntimeError*/, "Out of memory"));
         }
 
         machine_int_t out_sz = o->type->stream_p.read(o, p, 1, &error);
         if (out_sz == -1) {
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "[Errno %d]", error));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_OSError, "[Errno %d]", error));
         }
         if (out_sz == 0) {
             // Back out previously added byte
diff --git a/py/strtonum.c b/py/strtonum.c
index 74fa3d6..e83b975 100644
--- a/py/strtonum.c
+++ b/py/strtonum.c
@@ -18,7 +18,7 @@
 
     // check radix base
     if ((base != 0 && base < 2) || base > 36) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_ValueError, "ValueError: int() arg 2 must be >=2 and <= 36"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "ValueError: int() arg 2 must be >=2 and <= 36"));
     }
     // skip surrounded whitespace
     while (isspace((c = *(p++))));
@@ -84,7 +84,7 @@
     return (found ^ neg) - neg;
 
 value_error:
-    nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_ValueError, "invalid literal for int() with base %d: '%s'", base, s));
+    nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "invalid literal for int() with base %d: '%s'", base, s));
 }
 
 #else /* defined(UNIX) */
diff --git a/py/vm.c b/py/vm.c
index 1fc5b4a..461fecb 100644
--- a/py/vm.c
+++ b/py/vm.c
@@ -371,7 +371,7 @@
                         // if TOS is None, just pops it and continues
                         // if TOS is an integer, does something else
                         // else error
-                        if (MP_OBJ_IS_TYPE(TOP(), &exception_type)) {
+                        if (mp_obj_is_exception_instance(TOP())) {
                             nlr_jump(TOP());
                         }
                         if (TOP() == mp_const_none) {
@@ -575,7 +575,7 @@
                         unum = *ip++;
                         assert(unum == 1);
                         obj1 = POP();
-                        nlr_jump(obj1);
+                        nlr_jump(rt_make_raise_obj(obj1));
 
                     case MP_BC_YIELD_VALUE:
                         nlr_pop();
@@ -613,7 +613,7 @@
             // set file and line number that the exception occurred at
             // TODO: don't set traceback for exceptions re-raised by END_FINALLY.
             // But consider how to handle nested exceptions.
-            if (MP_OBJ_IS_TYPE(nlr.ret_val, &exception_type)) {
+            if (mp_obj_is_exception_instance(nlr.ret_val)) {
                 machine_uint_t code_info_size = code_info[0] | (code_info[1] << 8) | (code_info[2] << 16) | (code_info[3] << 24);
                 qstr source_file = code_info[4] | (code_info[5] << 8) | (code_info[6] << 16) | (code_info[7] << 24);
                 qstr block_name = code_info[8] | (code_info[9] << 8) | (code_info[10] << 16) | (code_info[11] << 24);