blueloveTH před 2 roky
rodič
revize
e4e43826fe
10 změnil soubory, kde provedl 152 přidání a 139 odebrání
  1. 0 2
      src/cffi.h
  2. 3 2
      src/common.h
  3. 36 36
      src/compiler.h
  4. 21 14
      src/expr.h
  5. 6 6
      src/frame.h
  6. 3 3
      src/iter.h
  7. 0 1
      src/memory.h
  8. 6 2
      src/obj.h
  9. 1 1
      src/pocketpy.h
  10. 76 72
      src/vm.h

+ 0 - 2
src/cffi.h

@@ -2,8 +2,6 @@
 
 #include "common.h"
 #include "vm.h"
-#include <type_traits>
-#include <vector>
 
 namespace pkpy {
 

+ 3 - 2
src/common.h

@@ -26,12 +26,13 @@
 #include <random>
 #include <initializer_list>
 #include <variant>
+#include <type_traits>
 
 #define PK_VERSION				"0.9.6"
 
 // debug macros
-#define DEBUG_NO_BUILTIN_MODULES	0
-#define DEBUG_EXTRA_CHECK			1
+#define DEBUG_NO_BUILTIN_MODULES	1
+#define DEBUG_MODE					1
 
 #if (defined(__ANDROID__) && __ANDROID_API__ <= 22) || defined(__EMSCRIPTEN__)
 #define PK_ENABLE_FILEIO 		0

+ 36 - 36
src/compiler.h

@@ -26,7 +26,7 @@ class Compiler {
     const Token& prev() { return tokens.at(i-1); }
     const Token& curr() { return tokens.at(i); }
     const Token& next() { return tokens.at(i+1); }
-    void advance() { i++; }
+    void advance(int delta=1) { i += delta; }
 
     CodeEmitContext* ctx() { return &contexts.top(); }
     CompileMode mode() const{ return lexer->src->mode; }
@@ -42,7 +42,7 @@ class Compiler {
     void pop_context(){
         if(!ctx()->s_expr.empty()) UNREACHABLE();
         // if the last op does not return, add a default return None
-        if(ctx()->co->codes.back().op != OP_RETURN_VALUE){
+        if(ctx()->co->codes.empty() || ctx()->co->codes.back().op != OP_RETURN_VALUE){
             ctx()->emit(OP_LOAD_NONE, BC_NOARG, BC_KEEPLINE);
             ctx()->emit(OP_RETURN_VALUE, BC_NOARG, BC_KEEPLINE);
         }
@@ -185,12 +185,14 @@ class Compiler {
 
     // PASS
     void exprTuple(){
-        auto e = make_expr<TupleExpr>();
+        std::vector<Expr_> items;
         do {
             EXPR();         // NOTE: "1," will fail, "1,2" will be ok
-            e->items.push_back(ctx()->s_expr.popx());
+            items.push_back(ctx()->s_expr.popx());
         } while(match(TK(",")));
-        ctx()->s_expr.push(std::move(e));
+        ctx()->s_expr.push(make_expr<TupleExpr>(
+            std::move(items)
+        ));
     }
 
     // PASS
@@ -577,7 +579,7 @@ class Compiler {
         EXPR(false);
         // TODO: support multiple decorator
         // use a while loop to consume '@'
-        if(!match_newlines(mode()==REPL_MODE)) SyntaxError();
+        if(!match_newlines_repl()) SyntaxError();
         ctx()->emit(OP_SETUP_DECORATOR, BC_NOARG, prev().line);
         consume(TK("def"));
         compile_function();
@@ -585,11 +587,9 @@ class Compiler {
 
     bool try_compile_assignment(){
         Expr* lhs_p = ctx()->s_expr.top().get();
-        bool inplace;
         switch (curr().type) {
             case TK("+="): case TK("-="): case TK("*="): case TK("/="): case TK("//="): case TK("%="):
             case TK("<<="): case TK(">>="): case TK("&="): case TK("|="): case TK("^="): {
-                inplace = true;
                 advance();
                 auto e = make_expr<BinaryExpr>();
                 e->op = prev().type - 1; // -1 to remove =
@@ -599,7 +599,6 @@ class Compiler {
                 ctx()->s_expr.push(std::move(e));
             } break;
             case TK("="):
-                inplace = false;
                 advance();
                 EXPR_TUPLE();
                 break;
@@ -625,7 +624,7 @@ class Compiler {
                 ctx()->emit(OP_LOOP_CONTINUE, BC_NOARG, kw_line);
                 consume_end_stmt();
                 break;
-            case TK("yield"):
+            case TK("yield"): 
                 if (contexts.size() <= 1) SyntaxError("'yield' outside function");
                 EXPR_TUPLE(true);
                 // if yield present, mark the function as generator
@@ -634,7 +633,7 @@ class Compiler {
                 consume_end_stmt();
                 break;
             case TK("return"):
-                if (contexts.size() <= 1) SyntaxError("'ret                                              urn' outside function");
+                if (contexts.size() <= 1) SyntaxError("'return' outside function");
                 if(match_end_stmt()){
                     ctx()->emit(OP_LOAD_NONE, BC_NOARG, kw_line);
                 }else{
@@ -717,6 +716,7 @@ class Compiler {
             /*************************************************/
             // handle dangling expression or assignment
             default: {
+                advance(-1);    // do revert since we have pre-called advance() at the beginning
                 EXPR_TUPLE();
                 if(!try_compile_assignment()){
                     ctx()->emit_expr();
@@ -791,7 +791,6 @@ class Compiler {
 
     void compile_function(){
         // TODO: bug, if there are multiple decorators, will cause error
-        bool has_decorator = !co()->codes.empty() && co()->codes.back().op == OP_SETUP_DECORATOR;
         Function func;
         StrName obj_name;
         consume(TK("@id"));
@@ -812,38 +811,38 @@ class Compiler {
         func.code = push_context(lexer->src, func.name.str());
         compile_block_body();
         pop_context();
-        emit(OP_LOAD_FUNCTION, co()->add_const(VAR(func)));
-        if(name_scope() == NAME_LOCAL) emit(OP_SETUP_CLOSURE);
+        ctx()->emit(OP_LOAD_FUNCTION, ctx()->add_const(VAR(func)), prev().line);
+        if(name_scope() == NAME_LOCAL) ctx()->emit(OP_SETUP_CLOSURE, BC_NOARG, prev().line);
         if(!ctx()->is_compiling_class){
             if(obj_name.empty()){
-                if(has_decorator) emit(OP_CALL, 1);
-                emit(OP_STORE_NAME, co()->add_name(func.name, name_scope()));
+                auto e = make_expr<NameExpr>(func.name, name_scope());
+                e->emit_store(ctx());
             } else {
-                if(has_decorator) SyntaxError("decorator is not supported here");
-                emit(OP_LOAD_NAME, co()->add_name(obj_name, name_scope()));
-                int index = co()->add_name(func.name, NAME_ATTR);
-                emit(OP_BUILD_ATTR_REF, index);
-                emit(OP_ROT_TWO);
-                emit(OP_STORE_REF);
+                ctx()->emit(OP_LOAD_NAME, ctx()->add_name(obj_name), prev().line);
+                int index = ctx()->add_name(func.name);
+                ctx()->emit(OP_STORE_ATTR, index, prev().line);
             }
         }else{
-            if(has_decorator) emit(OP_CALL, 1);
-            emit(OP_STORE_CLASS_ATTR, co()->add_name(func.name, name_scope()));
+            ctx()->emit(OP_STORE_CLASS_ATTR, ctx()->add_name(func.name), BC_KEEPLINE);
         }
     }
 
     PyObject* read_literal(){
-        if(match(TK("-"))){
-            consume(TK("@num"));
-            PyObject* val = get_value(prev());
-            return vm->num_negated(val);
+        advance();
+        switch(prev().type){
+            case TK("-"): {
+                consume(TK("@num"));
+                PyObject* val = LiteralExpr(prev().value).to_object(ctx());
+                return vm->num_negated(val);
+            }
+            case TK("@num"): return LiteralExpr(prev().value).to_object(ctx());
+            case TK("@str"): return LiteralExpr(prev().value).to_object(ctx());
+            case TK("True"): return VAR(true);
+            case TK("False"): return VAR(false);
+            case TK("None"): return vm->None;
+            case TK("..."): return vm->Ellipsis;
+            default: break;
         }
-        if(match(TK("@num"))) return get_value(prev());
-        if(match(TK("@str"))) return get_value(prev());
-        if(match(TK("True"))) return VAR(true);
-        if(match(TK("False"))) return VAR(false);
-        if(match(TK("None"))) return vm->None;
-        if(match(TK("..."))) return vm->Ellipsis;
         return nullptr;
     }
 
@@ -858,7 +857,8 @@ public:
         this->lexer = std::make_unique<Lexer>(
             make_sp<SourceData>(source, filename, mode)
         );
-        if(rules.empty()) init_pratt_rules();
+        // TODO: check if already initialized
+        init_pratt_rules();
     }
 
     CodeObject_ compile(){
@@ -883,7 +883,7 @@ public:
             return code;
         }else if(mode()==JSON_MODE){
             PyObject* value = read_literal();
-            if(value != nullptr) emit(OP_LOAD_CONST, code->add_const(value));
+            if(value != nullptr) ctx()->emit(OP_LOAD_CONST, ctx()->add_const(value), prev().line);
             else if(match(TK("{"))) exprMap();
             else if(match(TK("["))) exprList();
             else SyntaxError("expect a JSON object or array");

+ 21 - 14
src/expr.h

@@ -31,12 +31,7 @@ struct CodeEmitContext{
     CodeObject_ co;
     VM* vm;
     stack<Expr_> s_expr;
-
     CodeEmitContext(VM* vm, CodeObject_ co): co(co) {}
-    CodeEmitContext(const CodeEmitContext&) = delete;
-    CodeEmitContext& operator=(const CodeEmitContext&) = delete;
-    CodeEmitContext(CodeEmitContext&&) = delete;
-    CodeEmitContext& operator=(CodeEmitContext&&) = delete;
 
     int curr_block_i = 0;
     bool is_compiling_class = false;
@@ -101,12 +96,11 @@ struct CodeEmitContext{
 
 // PASS
 struct NameExpr: Expr{
-    Str name;
+    StrName name;
     NameScope scope;
-    NameExpr(const Str& name, NameScope scope): name(name), scope(scope) {}
-    NameExpr(Str&& name, NameScope scope): name(std::move(name)), scope(scope) {}
+    NameExpr(StrName name, NameScope scope): name(name), scope(scope) {}
 
-    Str str() const override { return "$" + name; }
+    Str str() const override { return "$" + name.str(); }
 
     void emit(CodeEmitContext* ctx) override {
         int index = ctx->add_name(name);
@@ -246,7 +240,7 @@ struct LiteralExpr: Expr{
         UNREACHABLE();
     }
 
-    void emit(CodeEmitContext* ctx) override {
+    PyObject* to_object(CodeEmitContext* ctx){
         VM* vm = ctx->vm;
         PyObject* obj = nullptr;
         if(std::holds_alternative<i64>(value)){
@@ -258,6 +252,11 @@ struct LiteralExpr: Expr{
         if(std::holds_alternative<Str>(value)){
             obj = VAR(std::get<Str>(value));
         }
+        return obj;
+    }
+
+    void emit(CodeEmitContext* ctx) override {
+        PyObject* obj = to_object(ctx);
         if(obj == nullptr) UNREACHABLE();
         int index = ctx->add_const(obj);
         ctx->emit(OP_LOAD_CONST, index, line);
@@ -287,7 +286,7 @@ struct NegatedExpr: Expr{
                 obj = VAR(std::get<f64>(lit->value));
             }
             if(obj != nullptr){
-                ctx->emit(OP_LOAD_CONST, ctx()->add_const(obj), line);
+                ctx->emit(OP_LOAD_CONST, ctx->add_const(obj), line);
                 return;
             }
         }
@@ -362,21 +361,25 @@ struct SequenceExpr: Expr{
 };
 
 struct ListExpr: SequenceExpr{
+    using SequenceExpr::SequenceExpr;
     Str str() const override { return "list()"; }
     Opcode opcode() const override { return OP_BUILD_LIST; }
 };
 
 struct DictExpr: SequenceExpr{
+    using SequenceExpr::SequenceExpr;
     Str str() const override { return "dict()"; }
-    Opcode opcode() const override { return OP_BUILD_MAP; }
+    Opcode opcode() const override { return OP_BUILD_DICT; }
 };
 
 struct SetExpr: SequenceExpr{
+    using SequenceExpr::SequenceExpr;
     Str str() const override { return "set()"; }
     Opcode opcode() const override { return OP_BUILD_SET; }
 };
 
 struct TupleExpr: SequenceExpr{
+    using SequenceExpr::SequenceExpr;
     Str str() const override { return "tuple()"; }
     Opcode opcode() const override { return OP_BUILD_TUPLE; }
 
@@ -432,7 +435,8 @@ struct CompExpr: Expr{
         ctx->enter_block(FOR_LOOP);
         ctx->emit(OP_FOR_ITER, BC_NOARG, BC_KEEPLINE);
         bool ok = vars->emit_store(ctx);
-        if(!ok) SyntaxError();  // this error occurs in `vars` instead of this line, but...nevermind
+        // this error occurs in `vars` instead of this line, but...nevermind
+        if(!ok) UNREACHABLE();  // TODO: raise a SyntaxError instead
         if(cond){
             cond->emit(ctx);
             int patch = ctx->emit(OP_POP_JUMP_IF_FALSE, BC_NOARG, BC_KEEPLINE);
@@ -449,16 +453,19 @@ struct CompExpr: Expr{
 struct ListCompExpr: CompExpr{
     Opcode op0() override { return OP_BUILD_LIST; }
     Opcode op1() override { return OP_LIST_APPEND; }
+    Str str() const override { return "listcomp()"; }
 };
 
 struct DictCompExpr: CompExpr{
-    Opcode op0() override { return OP_BUILD_DI                                                         CT; }
+    Opcode op0() override { return OP_BUILD_DICT; }
     Opcode op1() override { return OP_DICT_ADD; }
+    Str str() const override { return "dictcomp()"; }
 };
 
 struct SetCompExpr: CompExpr{
     Opcode op0() override { return OP_BUILD_SET; }
     Opcode op1() override { return OP_SET_ADD; }
+    Str str() const override { return "setcomp()"; }
 };
 
 struct LambdaExpr: Expr{

+ 6 - 6
src/frame.h

@@ -53,14 +53,14 @@ struct Frame {
     // }
 
     void pop(){
-#if DEBUG_EXTRA_CHECK
+#if DEBUG_MODE
         if(_data.empty()) throw std::runtime_error("_data.empty() is true");
 #endif
         _data.pop_back();
     }
 
     PyObject* popx(){
-#if DEBUG_EXTRA_CHECK
+#if DEBUG_MODE
         if(_data.empty()) throw std::runtime_error("_data.empty() is true");
 #endif
         PyObject* ret = _data.back();
@@ -69,21 +69,21 @@ struct Frame {
     }
 
     PyObject*& top(){
-#if DEBUG_EXTRA_CHECK
+#if DEBUG_MODE
         if(_data.empty()) throw std::runtime_error("_data.empty() is true");
 #endif
         return _data.back();
     }
 
     PyObject*& top_1(){
-#if DEBUG_EXTRA_CHECK
+#if DEBUG_MODE
         if(_data.size() < 2) throw std::runtime_error("_data.size() < 2");
 #endif
         return _data[_data.size()-2];
     }
 
     PyObject*& top_2(){
-#if DEBUG_EXTRA_CHECK
+#if DEBUG_MODE
         if(_data.size() < 3) throw std::runtime_error("_data.size() < 3");
 #endif
         return _data[_data.size()-3];
@@ -115,7 +115,7 @@ struct Frame {
     }
 
     int _exit_block(int i){
-        if(co->blocks[i].type == FOR_LOOP) _pop();
+        if(co->blocks[i].type == FOR_LOOP) pop();
         else if(co->blocks[i].type == TRY_EXCEPT) on_try_block_exit();
         return co->blocks[i].parent;
     }

+ 3 - 3
src/iter.h

@@ -26,8 +26,8 @@ public:
 
 template <typename T>
 class ArrayIter : public BaseIter {
-    int index;
     PyObject* ref;
+    int index;
 public:
     ArrayIter(VM* vm, PyObject* ref) : BaseIter(vm), ref(ref), index(0) {}
 
@@ -43,10 +43,10 @@ public:
 };
 
 class StringIter : public BaseIter {
-    int index = 0;
     PyObject* ref;
+    int index;
 public:
-    StringIter(VM* vm, PyObject* ref) : BaseIter(vm), ref(ref) {}
+    StringIter(VM* vm, PyObject* ref) : BaseIter(vm), ref(ref), index(0) {}
 
     PyObject* next() override{
         Str* str = &OBJ_GET(Str, ref);

+ 0 - 1
src/memory.h

@@ -1,7 +1,6 @@
 #pragma once
 
 #include "common.h"
-#include <type_traits>
 
 namespace pkpy{
 

+ 6 - 2
src/obj.h

@@ -3,7 +3,6 @@
 #include "common.h"
 #include "namedict.h"
 #include "tuplelist.h"
-#include <type_traits>
 
 namespace pkpy {
     
@@ -142,9 +141,14 @@ struct Py_ : PyObject {
 };
 
 #define OBJ_GET(T, obj) (((Py_<T>*)(obj))->_value)
-#define OBJ_NAME(obj) OBJ_GET(Str, vm->getattr(obj, __name__))
 #define OBJ_MARK(obj) if(!is_tagged(obj)) obj->_mark()
 
+#if DEBUG_NO_BUILTIN_MODULES
+#define OBJ_NAME(obj) Str("<?>")
+#else
+#define OBJ_NAME(obj) OBJ_GET(Str, vm->getattr(obj, __name__))
+#endif
+
 const int kTpIntIndex = 2;
 const int kTpFloatIndex = 3;
 

+ 1 - 1
src/pocketpy.h

@@ -542,7 +542,7 @@ inline void init_builtins(VM* _vm) {
     });
 
     /************ PyBool ************/
-    _vm->bind_static_method<1>("bool", "__new__", CPP_LAMBDA(vm->asBool(args[0])));
+    _vm->bind_static_method<1>("bool", "__new__", CPP_LAMBDA(VAR(vm->asBool(args[0]))));
 
     _vm->bind_method<0>("bool", "__repr__", [](VM* vm, Args& args) {
         bool val = CAST(bool, args[0]);

+ 76 - 72
src/vm.h

@@ -93,7 +93,7 @@ public:
     }
 
     Frame* top_frame() const {
-#if DEBUG_EXTRA_CHECK
+#if DEBUG_MODE
         if(callstack.empty()) UNREACHABLE();
 #endif
         return callstack.top().get();
@@ -166,14 +166,18 @@ public:
         if(_module == nullptr) _module = _main;
         try {
             CodeObject_ code = compile(source, filename, mode);
-            // if(_module == _main) std::cout << disassemble(code) << '\n';
+            if(_module == _main) std::cout << disassemble(code) << '\n';
             return _exec(code, _module);
         }catch (const Exception& e){
             *_stderr << e.summary() << '\n';
-        }catch (const std::exception& e) {
+
+        }
+#if !DEBUG_MODE
+        catch (const std::exception& e) {
             *_stderr << "An std::exception occurred! It could be a bug.\n";
             *_stderr << e.what() << '\n';
         }
+#endif
         callstack = {};
         return nullptr;
     }
@@ -289,6 +293,7 @@ public:
     void NameError(StrName name){ _error("NameError", "name " + name.str().escape(true) + " is not defined"); }
 
     void AttributeError(PyObject* obj, StrName name){
+        // OBJ_NAME calls getattr, which may lead to a infinite recursion
         _error("AttributeError", "type " +  OBJ_NAME(_t(obj)).escape(true) + " has no attribute " + name.str().escape(true));
     }
 
@@ -551,74 +556,73 @@ inline PyObject* VM::new_module(StrName name) {
 }
 
 inline Str VM::disassemble(CodeObject_ co){
-    return "";
-    // auto pad = [](const Str& s, const int n){
-    //     if(s.size() >= n) return s.substr(0, n);
-    //     return s + std::string(n - s.size(), ' ');
-    // };
-
-    // std::vector<int> jumpTargets;
-    // for(auto byte : co->codes){
-    //     if(byte.op == OP_JUMP_ABSOLUTE || byte.op == OP_POP_JUMP_IF_FALSE){
-    //         jumpTargets.push_back(byte.arg);
-    //     }
-    // }
-    // StrStream ss;
-    // ss << std::string(54, '-') << '\n';
-    // ss << co->name << ":\n";
-    // int prev_line = -1;
-    // for(int i=0; i<co->codes.size(); i++){
-    //     const Bytecode& byte = co->codes[i];
-    //     if(byte.op == OP_NO_OP) continue;
-    //     Str line = std::to_string(byte.line);
-    //     if(byte.line == prev_line) line = "";
-    //     else{
-    //         if(prev_line != -1) ss << "\n";
-    //         prev_line = byte.line;
-    //     }
-
-    //     std::string pointer;
-    //     if(std::find(jumpTargets.begin(), jumpTargets.end(), i) != jumpTargets.end()){
-    //         pointer = "-> ";
-    //     }else{
-    //         pointer = "   ";
-    //     }
-    //     ss << pad(line, 8) << pointer << pad(std::to_string(i), 3);
-    //     ss << " " << pad(OP_NAMES[byte.op], 20) << " ";
-    //     // ss << pad(byte.arg == -1 ? "" : std::to_string(byte.arg), 5);
-    //     std::string argStr = byte.arg == -1 ? "" : std::to_string(byte.arg);
-    //     if(byte.op == OP_LOAD_CONST){
-    //         argStr += " (" + CAST(Str, asRepr(co->consts[byte.arg])) + ")";
-    //     }
-    //     if(byte.op == OP_LOAD_NAME_REF || byte.op == OP_LOAD_NAME || byte.op == OP_RAISE || byte.op == OP_STORE_NAME){
-    //         argStr += " (" + co->names[byte.arg].first.str().escape(true) + ")";
-    //     }
-    //     ss << argStr;
-    //     // ss << pad(argStr, 20);      // may overflow
-    //     // ss << co->blocks[byte.block].to_string();
-    //     if(i != co->codes.size() - 1) ss << '\n';
-    // }
-    // StrStream consts;
-    // consts << "co_consts: ";
-    // consts << CAST(Str, asRepr(VAR(co->consts)));
-
-    // StrStream names;
-    // names << "co_names: ";
-    // List list;
-    // for(int i=0; i<co->names.size(); i++){
-    //     list.push_back(VAR(co->names[i].first.str()));
-    // }
-    // names << CAST(Str, asRepr(VAR(list)));
-    // ss << '\n' << consts.str() << '\n' << names.str() << '\n';
-
-    // for(int i=0; i<co->consts.size(); i++){
-    //     PyObject* obj = co->consts[i];
-    //     if(is_type(obj, tp_function)){
-    //         const auto& f = CAST(Function&, obj);
-    //         ss << disassemble(f.code);
-    //     }
-    // }
-    // return Str(ss.str());
+    auto pad = [](const Str& s, const int n){
+        if(s.size() >= n) return s.substr(0, n);
+        return s + std::string(n - s.size(), ' ');
+    };
+
+    std::vector<int> jumpTargets;
+    for(auto byte : co->codes){
+        if(byte.op == OP_JUMP_ABSOLUTE || byte.op == OP_POP_JUMP_IF_FALSE){
+            jumpTargets.push_back(byte.arg);
+        }
+    }
+    StrStream ss;
+    ss << std::string(54, '-') << '\n';
+    ss << co->name << ":\n";
+    int prev_line = -1;
+    for(int i=0; i<co->codes.size(); i++){
+        const Bytecode& byte = co->codes[i];
+        if(byte.op == OP_NO_OP) continue;
+        Str line = std::to_string(byte.line);
+        if(byte.line == prev_line) line = "";
+        else{
+            if(prev_line != -1) ss << "\n";
+            prev_line = byte.line;
+        }
+
+        std::string pointer;
+        if(std::find(jumpTargets.begin(), jumpTargets.end(), i) != jumpTargets.end()){
+            pointer = "-> ";
+        }else{
+            pointer = "   ";
+        }
+        ss << pad(line, 8) << pointer << pad(std::to_string(i), 3);
+        ss << " " << pad(OP_NAMES[byte.op], 20) << " ";
+        // ss << pad(byte.arg == -1 ? "" : std::to_string(byte.arg), 5);
+        std::string argStr = byte.arg == -1 ? "" : std::to_string(byte.arg);
+        if(byte.op == OP_LOAD_CONST){
+            argStr += " (" + CAST(Str, asRepr(co->consts[byte.arg])) + ")";
+        }
+        if(byte.op == OP_LOAD_NAME || byte.op == OP_STORE_LOCAL || byte.op == OP_STORE_GLOBAL){
+            argStr += " (" + co->names[byte.arg].str().escape(true) + ")";
+        }
+        ss << argStr;
+        // ss << pad(argStr, 20);      // may overflow
+        // ss << co->blocks[byte.block].to_string();
+        if(i != co->codes.size() - 1) ss << '\n';
+    }
+    StrStream consts;
+    consts << "co_consts: ";
+    consts << CAST(Str, asRepr(VAR(co->consts)));
+
+    StrStream names;
+    names << "co_names: ";
+    List list;
+    for(int i=0; i<co->names.size(); i++){
+        list.push_back(VAR(co->names[i].str()));
+    }
+    names << CAST(Str, asRepr(VAR(list)));
+    ss << '\n' << consts.str() << '\n' << names.str() << '\n';
+
+    for(int i=0; i<co->consts.size(); i++){
+        PyObject* obj = co->consts[i];
+        if(is_type(obj, tp_function)){
+            const auto& f = CAST(Function&, obj);
+            ss << disassemble(f.code);
+        }
+    }
+    return Str(ss.str());
 }
 
 inline void VM::init_builtin_types(){
@@ -877,7 +881,7 @@ inline PyObject* VM::_exec(){
         }catch(HandledException& e){
             continue;
         }catch(UnhandledException& e){
-            PyObject* obj = frame->pop();
+            PyObject* obj = frame->popx();
             Exception& _e = CAST(Exception&, obj);
             _e.st_push(frame->snapshot());
             callstack.pop();