Refactor exception objects to have better impl of Python-side interface.

This implements internal args tuple of arguments, while still keeping
object useful for reporting C-side errors.

Further elaboration is needed.
diff --git a/py/objexcept.c b/py/objexcept.c
index 4708a27..f083e61 100644
--- a/py/objexcept.c
+++ b/py/objexcept.c
@@ -8,105 +8,86 @@
 #include "misc.h"
 #include "mpconfig.h"
 #include "obj.h"
+#include "objtuple.h"
 
+// This is unified class for C-level and Python-level exceptions
+// Python-level exception have empty ->msg and all arguments are in
+// args tuple. C-level excepttion likely have ->msg, and may as well
+// have args tuple (or otherwise have it as NULL).
 typedef struct mp_obj_exception_t {
     mp_obj_base_t base;
     qstr id;
-    int n_args;
-    const void *args[];
+    qstr msg;
+    mp_obj_tuple_t args;
 } mp_obj_exception_t;
 
 void exception_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in) {
     mp_obj_exception_t *o = o_in;
-    switch (o->n_args) {
-        case 0:
-            print(env, "%s", qstr_str(o->id));
-            break;
-        case 1:
-            print(env, "%s: %s", qstr_str(o->id), (const char*)o->args[0]);
-            break;
-        case 2:
-            print(env, "%s: ", qstr_str(o->id));
-            print(env, (const char*)o->args[0], o->args[1]);
-            break;
-        default: // here we just assume at least 3 args, but only use first 3
-            print(env, "%s: ", qstr_str(o->id));
-            print(env, (const char*)o->args[0], o->args[1], o->args[2]);
-            break;
+    if (o->msg != 0) {
+        print(env, "%s: %s", qstr_str(o->id), qstr_str(o->msg));
+    } else {
+        print(env, "%s", qstr_str(o->id));
+        tuple_print(print, env, &o->args);
     }
 }
 
+// args in reversed order
+static mp_obj_t exception_call(mp_obj_t self_in, int n_args, const mp_obj_t *args) {
+    mp_obj_exception_t *base = self_in;
+    mp_obj_exception_t *o = m_new_obj_var(mp_obj_exception_t, mp_obj_t*, n_args);
+    o->base.type = &exception_type;
+    o->id = base->id;
+    o->msg = 0;
+    o->args.len = n_args;
+
+    // TODO: factor out as reusable copy_reversed()
+    int j = 0;
+    for (int i = n_args - 1; i >= 0; i--) {
+        o->args.items[i] = args[j++];
+    }
+    return o;
+}
+
 const mp_obj_type_t exception_type = {
     { &mp_const_type },
     "exception",
     .print = exception_print,
+    .call_n = exception_call,
 };
 
 mp_obj_t mp_obj_new_exception(qstr id) {
-    mp_obj_exception_t *o = m_new_obj(mp_obj_exception_t);
-    o->base.type = &exception_type;
-    o->id = id;
-    o->n_args = 0;
-    return o;
+    return mp_obj_new_exception_msg_varg(id, NULL);
 }
 
 mp_obj_t mp_obj_new_exception_msg(qstr id, const char *msg) {
-    mp_obj_exception_t *o = m_new_obj_var(mp_obj_exception_t, void*, 1);
-    o->base.type = &exception_type;
-    o->id = id;
-    o->n_args = 1;
-    o->args[0] = msg;
-    return o;
+    return mp_obj_new_exception_msg_varg(id, msg);
 }
 
 mp_obj_t mp_obj_new_exception_msg_1_arg(qstr id, const char *fmt, const char *a1) {
-    mp_obj_exception_t *o = m_new_obj_var(mp_obj_exception_t, void*, 2);
-    o->base.type = &exception_type;
-    o->id = id;
-    o->n_args = 2;
-    o->args[0] = fmt;
-    o->args[1] = a1;
-    return o;
+    return mp_obj_new_exception_msg_varg(id, fmt, a1);
 }
 
 mp_obj_t mp_obj_new_exception_msg_2_args(qstr id, const char *fmt, const char *a1, const char *a2) {
-    mp_obj_exception_t *o = m_new_obj_var(mp_obj_exception_t, void*, 3);
-    o->base.type = &exception_type;
-    o->id = id;
-    o->n_args = 3;
-    o->args[0] = fmt;
-    o->args[1] = a1;
-    o->args[2] = a2;
-    return o;
+    return mp_obj_new_exception_msg_varg(id, fmt, a1, a2);
 }
 
 mp_obj_t mp_obj_new_exception_msg_varg(qstr id, const char *fmt, ...) {
-    // count number of arguments by number of % signs, excluding %%
-    int n_args = 1; // count fmt
-    for (const char *s = fmt; *s; s++) {
-        if (*s == '%') {
-            if (s[1] == '%') {
-                s += 1;
-            } else {
-                n_args += 1;
-            }
-        }
-    }
-
     // make exception object
-    mp_obj_exception_t *o = m_new_obj_var(mp_obj_exception_t, void*, n_args);
+    mp_obj_exception_t *o = m_new_obj_var(mp_obj_exception_t, mp_obj_t*, 0);
     o->base.type = &exception_type;
     o->id = id;
-    o->n_args = n_args;
-    o->args[0] = fmt;
-
-    // extract args and store them
-    va_list ap;
-    va_start(ap, fmt);
-    for (int i = 1; i < n_args; i++) {
-        o->args[i] = va_arg(ap, void*);
+    o->args.len = 0;
+    if (fmt == NULL) {
+        o->msg = 0;
+    } else {
+        // render exception message
+        vstr_t *vstr = vstr_new();
+        va_list ap;
+        va_start(ap, fmt);
+        vstr_vprintf(vstr, fmt, ap);
+        va_end(ap);
+        o->msg = qstr_from_str_take(vstr->buf, vstr->alloc);
     }
-    va_end(ap);
 
     return o;
 }
diff --git a/tests/basics/tests/exception1.py b/tests/basics/tests/exception1.py
new file mode 100644
index 0000000..95d933d
--- /dev/null
+++ b/tests/basics/tests/exception1.py
@@ -0,0 +1,3 @@
+# TODO: requires repr()
+#a = IndexError(1, "test", [100, 200])
+#print(repr(a))