blueloveTH před 2 roky
rodič
revize
efb7fce3c1
18 změnil soubory, kde provedl 1500 přidání a 1366 odebrání
  1. 9 49
      build.py
  2. 3 0
      include/pocketpy.h
  3. 16 149
      include/pocketpy/codeobject.h
  4. 16 167
      include/pocketpy/dict.h
  5. 45 570
      include/pocketpy/expr.h
  6. 11 86
      include/pocketpy/frame.h
  7. 4 59
      include/pocketpy/gc.h
  8. 7 25
      include/pocketpy/obj.h
  9. 23 259
      include/pocketpy/vm.h
  10. 157 0
      src/codeobject.cpp
  11. 176 0
      src/dict.cpp
  12. 583 0
      src/expr.cpp
  13. 90 0
      src/frame.cpp
  14. 65 0
      src/gc.cpp
  15. 24 0
      src/obj.cpp
  16. 269 0
      src/vm.cpp
  17. 1 1
      src2/lib.cpp
  18. 1 1
      src2/main.cpp

+ 9 - 49
build.py

@@ -4,65 +4,25 @@ import shutil
 
 assert __name__ == "__main__"
 
-os.system("python3 preprocess.py")
-
-def DONE(code=0):
-    exit(code)
+os.system("python3 prebuild.py")
 
 src_file_list = []
 for file in os.listdir("src"):
-    if file.endswith(".cpp") and file != "main.cpp" and file != "tmp.cpp":
+    if file.endswith(".cpp"):
         src_file_list.append("src/" + file)
 
-main_src_arg = " ".join(src_file_list+["src/main.cpp"])
-tmp_src_arg = " ".join(src_file_list+["src/tmp.cpp"])
+main_src_arg = " ".join(src_file_list+["src2/main.cpp"])
+
+print(main_src_arg)
 
 linux_common = " -Wfatal-errors --std=c++17 -O2 -Wall -fno-rtti -stdlib=libc++ -Iinclude/ "
 linux_cmd = "clang++ -o pocketpy " + main_src_arg + linux_common
-linux_lib_cmd = "clang++ -fPIC -shared -o pocketpy.so " + tmp_src_arg + linux_common
-
-class LibBuildEnv:
-    def __enter__(self):
-        shutil.copy("c_bindings/pocketpy_c.h", "src/")
-        shutil.copy("c_bindings/pocketpy_c.cpp", "src/tmp.cpp")
-
-    def __exit__(self, *args):
-        if os.path.exists("src/pocketpy_c.h"):
-            os.remove("src/pocketpy_c.h")
-        if os.path.exists("src/tmp.cpp"):
-            os.remove("src/tmp.cpp")
-
-windows_common = "CL -std:c++17 /utf-8 -GR- -EHsc -O2"
-windows_cmd = windows_common + " -Fe:pocketpy src/main.cpp"
-windows_lib_cmd = windows_common + " -LD -Fe:pocketpy src/tmp.cpp"
-
-if sys.argv.__len__() == 1:
-    os.system(linux_cmd)
-    DONE()
-
-if "windows" in sys.argv:
-    if "-lib" in sys.argv:
-        with LibBuildEnv():
-            os.system(windows_lib_cmd)
-    else:
-        os.system(windows_cmd)
-    DONE()
-
-if "linux" in sys.argv:
-    if "-lib" in sys.argv:
-        with LibBuildEnv():
-            os.system(linux_lib_cmd)
-    else:
-        os.system(linux_cmd)
-    DONE()
 
 if "web" in sys.argv:
     os.system(r'''
-rm -rf web/lib/
+    rm -rf web/lib/
 mkdir -p web/lib/
-em++ src/main.cpp -fno-rtti -fexceptions -O3 -sEXPORTED_FUNCTIONS=_pkpy_new_repl,_pkpy_repl_input,_pkpy_new_vm -sEXPORTED_RUNTIME_METHODS=ccall -o web/lib/pocketpy.js
+em++ ''' + main_src_arg + '''-Iinclude/ -fno-rtti -fexceptions -O3 -sEXPORTED_FUNCTIONS=_pkpy_new_repl,_pkpy_repl_input,_pkpy_new_vm -sEXPORTED_RUNTIME_METHODS=ccall -o web/lib/pocketpy.js
 ''')
-    DONE()
-
-print("invalid usage!!")
-exit(2)
+else:
+    os.system(linux_cmd)

+ 3 - 0
include/pocketpy.h

@@ -0,0 +1,3 @@
+#pragma once
+
+#include "pocketpy/pocketpy.h"

+ 16 - 149
include/pocketpy/codeobject.h

@@ -56,64 +56,17 @@ struct CodeObjectSerializer{
 
     static const char END = '\n';
 
-    CodeObjectSerializer(){
-        write_str(PK_VERSION);
-    }
-
-    void write_int(i64 v){
-        buffer += 'i';
-        buffer += std::to_string(v);
-        buffer += END;
-    }
-
-    void write_float(f64 v){
-        buffer += 'f';
-        buffer += std::to_string(v);
-        buffer += END;
-    }
-
-    void write_str(const Str& v){
-        buffer += 's';
-        buffer += v.escape(false).str();
-        buffer += END;
-    }
-
-    void write_none(){
-        buffer += 'N';
-        buffer += END;
-    }
-
-    void write_ellipsis(){
-        buffer += 'E';
-        buffer += END;
-    }
-
-    void write_bool(bool v){
-        buffer += 'b';
-        buffer += v ? '1' : '0';
-        buffer += END;
-    }
-
-    void write_begin_mark(){
-        buffer += '[';
-        buffer += END;
-        depth++;
-    }
-
-    void write_name(StrName name){
-        PK_ASSERT(StrName::is_valid(name.index));
-        buffer += 'n';
-        buffer += std::to_string(name.index);
-        buffer += END;
-        names.insert(name);
-    }
-
-    void write_end_mark(){
-        buffer += ']';
-        buffer += END;
-        depth--;
-        PK_ASSERT(depth >= 0);
-    }
+    CodeObjectSerializer();
+
+    void write_int(i64 v);
+    void write_float(f64 v);
+    void write_str(const Str& v);
+    void write_none();
+    void write_ellipsis();
+    void write_bool(bool v);
+    void write_begin_mark();
+    void write_name(StrName name);
+    void write_end_mark();
 
     template<typename T>
     void write_bytes(T v){
@@ -130,16 +83,7 @@ struct CodeObjectSerializer{
 
     void write_object(VM* vm, PyObject* obj);
     void write_code(VM* vm, const CodeObject* co);
-
-    std::string str(){
-        PK_ASSERT(depth == 0);
-        for(auto name: names){
-            PK_ASSERT(StrName::is_valid(name.index));
-            write_name(name);
-            write_str(name.sv());
-        }
-        return std::move(buffer);
-    }
+    std::string str();
 };
 
 
@@ -148,9 +92,6 @@ struct CodeObject {
     Str name;
     bool is_generator = false;
 
-    CodeObject(shared_ptr<SourceData> src, const Str& name):
-        src(src), name(name) {}
-
     std::vector<Bytecode> codes;
     std::vector<int> lines; // line number for each bytecode
     List consts;
@@ -160,84 +101,10 @@ struct CodeObject {
     NameDictInt labels;
     std::vector<FuncDecl_> func_decls;
 
-    void _gc_mark() const {
-        for(PyObject* v : consts) PK_OBJ_MARK(v);
-        for(auto& decl: func_decls) decl->_gc_mark();
-    }
-
-    void write(VM* vm, CodeObjectSerializer& ss) const{
-        ss.write_begin_mark();          // [
-        ss.write_str(src->filename);    // src->filename
-        ss.write_int(src->mode);        // src->mode
-        ss.write_end_mark();            // ]
-        ss.write_str(name);             // name
-        ss.write_bool(is_generator);    // is_generator
-        ss.write_begin_mark();          // [
-            for(Bytecode bc: codes){
-                if(StrName::is_valid(bc.arg)) ss.names.insert(StrName(bc.arg));
-                ss.write_bytes(bc);
-            }
-        ss.write_end_mark();            // ]
-        ss.write_begin_mark();          // [
-            for(int line: lines){
-                ss.write_int(line);         // line
-            }
-        ss.write_end_mark();            // ]
-        ss.write_begin_mark();          // [
-            for(PyObject* o: consts){
-                ss.write_object(vm, o);
-            }
-        ss.write_end_mark();            // ]
-        ss.write_begin_mark();          // [
-            for(StrName vn: varnames){
-                ss.write_name(vn);        // name
-            }
-        ss.write_end_mark();            // ]
-        ss.write_begin_mark();          // [
-            for(CodeBlock block: blocks){
-                ss.write_bytes(block);      // block
-            }
-        ss.write_end_mark();            // ]
-        ss.write_begin_mark();          // [
-            for(auto& label: labels.items()){
-                ss.write_name(label.first);     // label.first
-                ss.write_int(label.second);     // label.second
-            }
-        ss.write_end_mark();            // ]
-        ss.write_begin_mark();          // [
-            for(auto& decl: func_decls){
-                ss.write_code(vm, decl->code.get()); // decl->code
-                ss.write_begin_mark();      // [
-                    for(int arg: decl->args) ss.write_int(arg);
-                ss.write_end_mark();        // ]
-                
-                ss.write_begin_mark();      // [
-                    for(auto kw: decl->kwargs){
-                        ss.write_int(kw.key);           // kw.key
-                        ss.write_object(vm, kw.value);  // kw.value
-                    }
-                ss.write_end_mark();        // ]
-
-                ss.write_int(decl->starred_arg);
-                ss.write_int(decl->starred_kwarg);
-                ss.write_bool(decl->nested);
-            }
-        ss.write_end_mark();            // ]
-    }
-
-    Str serialize(VM* vm) const{
-        CodeObjectSerializer ss;
-        ss.write_code(vm, this);
-        return ss.str();
-    }
+    CodeObject(shared_ptr<SourceData> src, const Str& name);
+    void _gc_mark() const;
+    void write(VM* vm, CodeObjectSerializer& ss) const;
+    Str serialize(VM* vm) const;
 };
 
-inline void CodeObjectSerializer::write_code(VM* vm, const CodeObject* co){
-    buffer += '(';
-    buffer += END;
-    co->write(vm, *this);
-    buffer += ')';
-    buffer += END;
-}
-
 } // namespace pkpy

+ 16 - 167
include/pocketpy/dict.h

@@ -33,137 +33,24 @@ struct Dict{
     Item* _items;
     ItemNode* _nodes;       // for order preserving
 
-    Dict(VM* vm): vm(vm), _capacity(__Capacity),
-            _mask(__Capacity-1),
-            _size(0), _critical_size(__Capacity*__LoadFactor+0.5f), _head_idx(-1), _tail_idx(-1){
-        _items = (Item*)pool128.alloc(_capacity * sizeof(Item));
-        memset(_items, 0, _capacity * sizeof(Item));
-        _nodes = (ItemNode*)pool64.alloc(_capacity * sizeof(ItemNode));
-        memset(_nodes, -1, _capacity * sizeof(ItemNode));
-    }
-
-    int size() const { return _size; }
-
-    Dict(Dict&& other){
-        vm = other.vm;
-        _capacity = other._capacity;
-        _mask = other._mask;
-        _size = other._size;
-        _critical_size = other._critical_size;
-        _head_idx = other._head_idx;
-        _tail_idx = other._tail_idx;
-        _items = other._items;
-        _nodes = other._nodes;
-        other._items = nullptr;
-        other._nodes = nullptr;
-    }
-
-    Dict(const Dict& other){
-        vm = other.vm;
-        _capacity = other._capacity;
-        _mask = other._mask;
-        _size = other._size;
-        _critical_size = other._critical_size;
-        _head_idx = other._head_idx;
-        _tail_idx = other._tail_idx;
-        _items = (Item*)pool128.alloc(_capacity * sizeof(Item));
-        memcpy(_items, other._items, _capacity * sizeof(Item));
-        _nodes = (ItemNode*)pool64.alloc(_capacity * sizeof(ItemNode));
-        memcpy(_nodes, other._nodes, _capacity * sizeof(ItemNode));
-    }
-
+    Dict(VM* vm);
+    Dict(Dict&& other);
+    Dict(const Dict& other);
     Dict& operator=(const Dict&) = delete;
     Dict& operator=(Dict&&) = delete;
 
-    void _probe(PyObject* key, bool& ok, int& i) const;
-
-    void set(PyObject* key, PyObject* val){
-        // do possible rehash
-        if(_size+1 > _critical_size) _rehash();
-        bool ok; int i;
-        _probe(key, ok, i);
-        if(!ok) {
-            _size++;
-            _items[i].first = key;
-
-            // append to tail
-            if(_size == 0+1){
-                _head_idx = i;
-                _tail_idx = i;
-            }else{
-                _nodes[i].prev = _tail_idx;
-                _nodes[_tail_idx].next = i;
-                _tail_idx = i;
-            }
-        }
-        _items[i].second = val;
-    }
-
-    void _rehash(){
-        Item* old_items = _items;
-        int old_capacity = _capacity;
-        _capacity *= 2;
-        _mask = _capacity - 1;
-        _size = 0;
-        _critical_size = _capacity*__LoadFactor+0.5f;
-        _head_idx = -1;
-        _tail_idx = -1;
-        pool64.dealloc(_nodes);
-        _items = (Item*)pool128.alloc(_capacity * sizeof(Item));
-        memset(_items, 0, _capacity * sizeof(Item));
-        _nodes = (ItemNode*)pool64.alloc(_capacity * sizeof(ItemNode));
-        memset(_nodes, -1, _capacity * sizeof(ItemNode));
-
-        for(int i=0; i<old_capacity; i++){
-            if(old_items[i].first == nullptr) continue;
-            set(old_items[i].first, old_items[i].second);
-        }
-        pool128.dealloc(old_items);
-    }
+    int size() const { return _size; }
 
-    PyObject* try_get(PyObject* key) const{
-        bool ok; int i;
-        _probe(key, ok, i);
-        if(!ok) return nullptr;
-        return _items[i].second;
-    }
+    void _probe(PyObject* key, bool& ok, int& i) const;
 
-    bool contains(PyObject* key) const{
-        bool ok; int i;
-        _probe(key, ok, i);
-        return ok;
-    }
+    void set(PyObject* key, PyObject* val);
+    void _rehash();
 
-    void erase(PyObject* key){
-        bool ok; int i;
-        _probe(key, ok, i);
-        if(!ok) return;
-        _items[i].first = nullptr;
-        _items[i].second = nullptr;
-        _size--;
-
-        if(_size == 0){
-            _head_idx = -1;
-            _tail_idx = -1;
-        }else{
-            if(_head_idx == i){
-                _head_idx = _nodes[i].next;
-                _nodes[_head_idx].prev = -1;
-            }else if(_tail_idx == i){
-                _tail_idx = _nodes[i].prev;
-                _nodes[_tail_idx].next = -1;
-            }else{
-                _nodes[_nodes[i].prev].next = _nodes[i].next;
-                _nodes[_nodes[i].next].prev = _nodes[i].prev;
-            }
-        }
-        _nodes[i].prev = -1;
-        _nodes[i].next = -1;
-    }
+    PyObject* try_get(PyObject* key) const;
 
-    void update(const Dict& other){
-        other.apply([&](PyObject* k, PyObject* v){ set(k, v); });
-    }
+    bool contains(PyObject* key) const;
+    void erase(PyObject* key);
+    void update(const Dict& other);
 
     template<typename __Func>
     void apply(__Func f) const {
@@ -174,50 +61,12 @@ struct Dict{
         }
     }
 
-    Tuple keys() const{
-        Tuple t(_size);
-        int i = _head_idx;
-        int j = 0;
-        while(i != -1){
-            t[j++] = _items[i].first;
-            i = _nodes[i].next;
-        }
-        PK_ASSERT(j == _size);
-        return t;
-    }
+    Tuple keys() const;
+    Tuple values() const;
+    void clear();
+    ~Dict();
 
-    Tuple values() const{
-        Tuple t(_size);
-        int i = _head_idx;
-        int j = 0;
-        while(i != -1){
-            t[j++] = _items[i].second;
-            i = _nodes[i].next;
-        }
-        PK_ASSERT(j == _size);
-        return t;
-    }
-
-    void clear(){
-        _size = 0;
-        _head_idx = -1;
-        _tail_idx = -1;
-        memset(_items, 0, _capacity * sizeof(Item));
-        memset(_nodes, -1, _capacity * sizeof(ItemNode));
-    }
-
-    ~Dict(){
-        if(_items==nullptr) return;
-        pool128.dealloc(_items);
-        pool64.dealloc(_nodes);
-    }
-
-    void _gc_mark() const{
-        apply([](PyObject* k, PyObject* v){
-            PK_OBJ_MARK(k);
-            PK_OBJ_MARK(v);
-        });
-    }
+    void _gc_mark() const;
 };
 
 } // namespace pkpy

+ 45 - 570
include/pocketpy/expr.h

@@ -53,92 +53,17 @@ struct CodeEmitContext{
     bool is_compiling_class = false;
     int for_loop_depth = 0;
 
-    bool is_curr_block_loop() const {
-        return co->blocks[curr_block_i].type == FOR_LOOP || co->blocks[curr_block_i].type == WHILE_LOOP;
-    }
-
-    void enter_block(CodeBlockType type){
-        if(type == FOR_LOOP) for_loop_depth++;
-        co->blocks.push_back(CodeBlock(
-            type, curr_block_i, for_loop_depth, (int)co->codes.size()
-        ));
-        curr_block_i = co->blocks.size()-1;
-    }
-
-    void exit_block(){
-        auto curr_type = co->blocks[curr_block_i].type;
-        if(curr_type == FOR_LOOP) for_loop_depth--;
-        co->blocks[curr_block_i].end = co->codes.size();
-        curr_block_i = co->blocks[curr_block_i].parent;
-        if(curr_block_i < 0) FATAL_ERROR();
-
-        if(curr_type == FOR_LOOP){
-            // add a no op here to make block check work
-            emit(OP_NO_OP, BC_NOARG, BC_KEEPLINE);
-        }
-    }
-
-    // clear the expression stack and generate bytecode
-    void emit_expr(){
-        if(s_expr.size() != 1){
-            throw std::runtime_error("s_expr.size() != 1\n" + _log_s_expr());
-        }
-        Expr_ expr = s_expr.popx();
-        expr->emit(this);
-    }
-
-    std::string _log_s_expr(){
-        std::stringstream ss;
-        for(auto& e: s_expr.data()) ss << e->str() << " ";
-        return ss.str();
-    }
-
-    int emit(Opcode opcode, int arg, int line) {
-        co->codes.push_back(
-            Bytecode{(uint16_t)opcode, (uint16_t)curr_block_i, arg}
-        );
-        co->lines.push_back(line);
-        int i = co->codes.size() - 1;
-        if(line==BC_KEEPLINE){
-            if(i>=1) co->lines[i] = co->lines[i-1];
-            else co->lines[i] = 1;
-        }
-        return i;
-    }
-
-    void patch_jump(int index) {
-        int target = co->codes.size();
-        co->codes[index].arg = target;
-    }
-
-    bool add_label(StrName name){
-        if(co->labels.contains(name)) return false;
-        co->labels.set(name, co->codes.size());
-        return true;
-    }
-
-    int add_varname(StrName name){
-        int index = co->varnames_inv.try_get(name);
-        if(index >= 0) return index;
-        co->varnames.push_back(name);
-        index = co->varnames.size() - 1;
-        co->varnames_inv.set(name, index);
-        return index;
-    }
-
-    int add_const(PyObject* v){
-        // simple deduplication, only works for int/float
-        for(int i=0; i<co->consts.size(); i++){
-            if(co->consts[i] == v) return i;
-        }
-        co->consts.push_back(v);
-        return co->consts.size() - 1;
-    }
-
-    int add_func_decl(FuncDecl_ decl){
-        co->func_decls.push_back(decl);
-        return co->func_decls.size() - 1;
-    }
+    bool is_curr_block_loop() const;
+    void enter_block(CodeBlockType type);
+    void exit_block();
+    void emit_expr();   // clear the expression stack and generate bytecode
+    std::string _log_s_expr();
+    int emit(Opcode opcode, int arg, int line);
+    void patch_jump(int index);
+    bool add_label(StrName name);
+    int add_varname(StrName name);
+    int add_const(PyObject* v);
+    int add_func_decl(FuncDecl_ decl);
 };
 
 struct NameExpr: Expr{
@@ -148,54 +73,9 @@ struct NameExpr: Expr{
 
     std::string str() const override { return fmt("Name(", name.escape(), ")"); }
 
-    void emit(CodeEmitContext* ctx) override {
-        int index = ctx->co->varnames_inv.try_get(name);
-        if(scope == NAME_LOCAL && index >= 0){
-            ctx->emit(OP_LOAD_FAST, index, line);
-        }else{
-            Opcode op = ctx->level <= 1 ? OP_LOAD_GLOBAL : OP_LOAD_NONLOCAL;
-            // we cannot determine the scope when calling exec()/eval()
-            if(scope == NAME_GLOBAL_UNKNOWN) op = OP_LOAD_NAME;
-            ctx->emit(op, StrName(name).index, line);
-        }
-    }
-
-    bool emit_del(CodeEmitContext* ctx) override {
-        switch(scope){
-            case NAME_LOCAL:
-                ctx->emit(OP_DELETE_FAST, ctx->add_varname(name), line);
-                break;
-            case NAME_GLOBAL:
-                ctx->emit(OP_DELETE_GLOBAL, StrName(name).index, line);
-                break;
-            case NAME_GLOBAL_UNKNOWN:
-                ctx->emit(OP_DELETE_NAME, StrName(name).index, line);
-                break;
-            default: FATAL_ERROR(); break;
-        }
-        return true;
-    }
-
-    bool emit_store(CodeEmitContext* ctx) override {
-        if(ctx->is_compiling_class){
-            int index = StrName(name).index;
-            ctx->emit(OP_STORE_CLASS_ATTR, index, line);
-            return true;
-        }
-        switch(scope){
-            case NAME_LOCAL:
-                ctx->emit(OP_STORE_FAST, ctx->add_varname(name), line);
-                break;
-            case NAME_GLOBAL:
-                ctx->emit(OP_STORE_GLOBAL, StrName(name).index, line);
-                break;
-            case NAME_GLOBAL_UNKNOWN:
-                ctx->emit(OP_STORE_NAME, StrName(name).index, line);
-                break;
-            default: FATAL_ERROR(); break;
-        }
-        return true;
-    }
+    void emit(CodeEmitContext* ctx) override;
+    bool emit_del(CodeEmitContext* ctx) override;
+    bool emit_store(CodeEmitContext* ctx) override;
 };
 
 struct StarredExpr: Expr{
@@ -203,19 +83,9 @@ struct StarredExpr: Expr{
     Expr_ child;
     StarredExpr(int level, Expr_&& child): level(level), child(std::move(child)) {}
     std::string str() const override { return fmt("Starred(level=", level, ")"); }
-
     int star_level() const override { return level; }
-
-    void emit(CodeEmitContext* ctx) override {
-        child->emit(ctx);
-        ctx->emit(OP_UNARY_STAR, level, line);
-    }
-
-    bool emit_store(CodeEmitContext* ctx) override {
-        if(level != 1) return false;
-        // simply proxy to child
-        return child->emit_store(ctx);
-    }
+    void emit(CodeEmitContext* ctx) override;
+    bool emit_store(CodeEmitContext* ctx) override;
 };
 
 struct NotExpr: Expr{
@@ -223,36 +93,21 @@ struct NotExpr: Expr{
     NotExpr(Expr_&& child): child(std::move(child)) {}
     std::string str() const override { return "Not()"; }
 
-    void emit(CodeEmitContext* ctx) override {
-        child->emit(ctx);
-        ctx->emit(OP_UNARY_NOT, BC_NOARG, line);
-    }
+    void emit(CodeEmitContext* ctx) override;
 };
 
 struct AndExpr: Expr{
     Expr_ lhs;
     Expr_ rhs;
     std::string str() const override { return "And()"; }
-
-    void emit(CodeEmitContext* ctx) override {
-        lhs->emit(ctx);
-        int patch = ctx->emit(OP_JUMP_IF_FALSE_OR_POP, BC_NOARG, line);
-        rhs->emit(ctx);
-        ctx->patch_jump(patch);
-    }
+    void emit(CodeEmitContext* ctx) override;
 };
 
 struct OrExpr: Expr{
     Expr_ lhs;
     Expr_ rhs;
     std::string str() const override { return "Or()"; }
-
-    void emit(CodeEmitContext* ctx) override {
-        lhs->emit(ctx);
-        int patch = ctx->emit(OP_JUMP_IF_TRUE_OR_POP, BC_NOARG, line);
-        rhs->emit(ctx);
-        ctx->patch_jump(patch);
-    }
+    void emit(CodeEmitContext* ctx) override;
 };
 
 // [None, True, False, ...]
@@ -260,71 +115,24 @@ struct Literal0Expr: Expr{
     TokenIndex token;
     Literal0Expr(TokenIndex token): token(token) {}
     std::string str() const override { return TK_STR(token); }
-
-    void emit(CodeEmitContext* ctx) override {
-        switch (token) {
-            case TK("None"):    ctx->emit(OP_LOAD_NONE, BC_NOARG, line); break;
-            case TK("True"):    ctx->emit(OP_LOAD_TRUE, BC_NOARG, line); break;
-            case TK("False"):   ctx->emit(OP_LOAD_FALSE, BC_NOARG, line); break;
-            case TK("..."):     ctx->emit(OP_LOAD_ELLIPSIS, BC_NOARG, line); break;
-            default: FATAL_ERROR();
-        }
-    }
-
     bool is_json_object() const override { return true; }
+
+    void emit(CodeEmitContext* ctx) override;
 };
 
 struct LongExpr: Expr{
     Str s;
     LongExpr(const Str& s): s(s) {}
     std::string str() const override { return s.str(); }
-
-    void emit(CodeEmitContext* ctx) override {
-        VM* vm = ctx->vm;
-        ctx->emit(OP_LOAD_CONST, ctx->add_const(VAR(s)), line);
-        ctx->emit(OP_BUILD_LONG, BC_NOARG, line);
-    }
+    void emit(CodeEmitContext* ctx) override;
 };
 
 // @num, @str which needs to invoke OP_LOAD_CONST
 struct LiteralExpr: Expr{
     TokenValue value;
     LiteralExpr(TokenValue value): value(value) {}
-    std::string str() const override {
-        if(std::holds_alternative<i64>(value)){
-            return std::to_string(std::get<i64>(value));
-        }
-        if(std::holds_alternative<f64>(value)){
-            return std::to_string(std::get<f64>(value));
-        }
-        if(std::holds_alternative<Str>(value)){
-            Str s = std::get<Str>(value).escape();
-            return s.str();
-        }
-        FATAL_ERROR();
-    }
-
-    void emit(CodeEmitContext* ctx) override {
-        VM* vm = ctx->vm;
-        PyObject* obj = nullptr;
-        if(std::holds_alternative<i64>(value)){
-            i64 _val = std::get<i64>(value);
-            if(_val >= INT16_MIN && _val <= INT16_MAX){
-                ctx->emit(OP_LOAD_INTEGER, (int)_val, line);
-                return;
-            }
-            obj = VAR(_val);
-        }
-        if(std::holds_alternative<f64>(value)){
-            obj = VAR(std::get<f64>(value));
-        }
-        if(std::holds_alternative<Str>(value)){
-            obj = VAR(std::get<Str>(value));
-        }
-        if(obj == nullptr) FATAL_ERROR();
-        ctx->emit(OP_LOAD_CONST, ctx->add_const(obj), line);
-    }
-
+    std::string str() const override;
+    void emit(CodeEmitContext* ctx) override;
     bool is_literal() const override { return true; }
     bool is_json_object() const override { return true; }
 };
@@ -334,33 +142,8 @@ struct NegatedExpr: Expr{
     NegatedExpr(Expr_&& child): child(std::move(child)) {}
     std::string str() const override { return "Negated()"; }
 
-    void emit(CodeEmitContext* ctx) override {
-        VM* vm = ctx->vm;
-        // if child is a int of float, do constant folding
-        if(child->is_literal()){
-            LiteralExpr* lit = static_cast<LiteralExpr*>(child.get());
-            if(std::holds_alternative<i64>(lit->value)){
-                i64 _val = -std::get<i64>(lit->value);
-                if(_val >= INT16_MIN && _val <= INT16_MAX){
-                    ctx->emit(OP_LOAD_INTEGER, (int)_val, line);
-                }else{
-                    ctx->emit(OP_LOAD_CONST, ctx->add_const(VAR(_val)), line);
-                }
-                return;
-            }
-            if(std::holds_alternative<f64>(lit->value)){
-                PyObject* obj = VAR(-std::get<f64>(lit->value));
-                ctx->emit(OP_LOAD_CONST, ctx->add_const(obj), line);
-                return;
-            }
-        }
-        child->emit(ctx);
-        ctx->emit(OP_UNARY_NEGATIVE, BC_NOARG, line);
-    }
-
-    bool is_json_object() const override {
-        return child->is_literal();
-    }
+    void emit(CodeEmitContext* ctx) override;
+    bool is_json_object() const override { return child->is_literal(); }
 };
 
 struct SliceExpr: Expr{
@@ -368,47 +151,15 @@ struct SliceExpr: Expr{
     Expr_ stop;
     Expr_ step;
     std::string str() const override { return "Slice()"; }
-
-    void emit(CodeEmitContext* ctx) override {
-        if(start){
-            start->emit(ctx);
-        }else{
-            ctx->emit(OP_LOAD_NONE, BC_NOARG, line);
-        }
-
-        if(stop){
-            stop->emit(ctx);
-        }else{
-            ctx->emit(OP_LOAD_NONE, BC_NOARG, line);
-        }
-
-        if(step){
-            step->emit(ctx);
-        }else{
-            ctx->emit(OP_LOAD_NONE, BC_NOARG, line);
-        }
-
-        ctx->emit(OP_BUILD_SLICE, BC_NOARG, line);
-    }
+    void emit(CodeEmitContext* ctx) override;
 };
 
 struct DictItemExpr: Expr{
     Expr_ key;      // maybe nullptr if it is **kwargs
     Expr_ value;
     std::string str() const override { return "DictItem()"; }
-
     int star_level() const override { return value->star_level(); }
-
-    void emit(CodeEmitContext* ctx) override {
-        if(is_starred()){
-            PK_ASSERT(key == nullptr);
-            value->emit(ctx);
-        }else{
-            value->emit(ctx);
-            key->emit(ctx);     // reverse order
-            ctx->emit(OP_BUILD_TUPLE, 2, line);
-        }
-    }
+    void emit(CodeEmitContext* ctx) override;
 };
 
 struct SequenceExpr: Expr{
@@ -463,49 +214,8 @@ struct TupleExpr: SequenceExpr{
         return OP_BUILD_TUPLE;
     }
 
-    bool emit_store(CodeEmitContext* ctx) override {
-        // TOS is an iterable
-        // items may contain StarredExpr, we should check it
-        int starred_i = -1;
-        for(int i=0; i<items.size(); i++){
-            if(!items[i]->is_starred()) continue;
-            if(starred_i == -1) starred_i = i;
-            else return false;  // multiple StarredExpr not allowed
-        }
-
-        if(starred_i == -1){
-            Bytecode& prev = ctx->co->codes.back();
-            if(prev.op == OP_BUILD_TUPLE && prev.arg == items.size()){
-                // build tuple and unpack it is meaningless
-                prev.op = OP_NO_OP;
-                prev.arg = BC_NOARG;
-            }else{
-                ctx->emit(OP_UNPACK_SEQUENCE, items.size(), line);
-            }
-        }else{
-            // starred assignment target must be in a tuple
-            if(items.size() == 1) return false;
-            // starred assignment target must be the last one (differ from cpython)
-            if(starred_i != items.size()-1) return false;
-            // a,*b = [1,2,3]
-            // stack is [1,2,3] -> [1,[2,3]]
-            ctx->emit(OP_UNPACK_EX, items.size()-1, line);
-        }
-        // do reverse emit
-        for(int i=items.size()-1; i>=0; i--){
-            bool ok = items[i]->emit_store(ctx);
-            if(!ok) return false;
-        }
-        return true;
-    }
-
-    bool emit_del(CodeEmitContext* ctx) override{
-        for(auto& e: items){
-            bool ok = e->emit_del(ctx);
-            if(!ok) return false;
-        }
-        return true;
-    }
+    bool emit_store(CodeEmitContext* ctx) override;
+    bool emit_del(CodeEmitContext* ctx) override;
 };
 
 struct CompExpr: Expr{
@@ -517,28 +227,7 @@ struct CompExpr: Expr{
     virtual Opcode op0() = 0;
     virtual Opcode op1() = 0;
 
-    void emit(CodeEmitContext* ctx){
-        ctx->emit(op0(), 0, line);
-        iter->emit(ctx);
-        ctx->emit(OP_GET_ITER, BC_NOARG, BC_KEEPLINE);
-        ctx->enter_block(FOR_LOOP);
-        ctx->emit(OP_FOR_ITER, BC_NOARG, BC_KEEPLINE);
-        bool ok = vars->emit_store(ctx);
-        // this error occurs in `vars` instead of this line, but...nevermind
-        PK_ASSERT(ok);  // TODO: raise a SyntaxError instead
-        if(cond){
-            cond->emit(ctx);
-            int patch = ctx->emit(OP_POP_JUMP_IF_FALSE, BC_NOARG, BC_KEEPLINE);
-            expr->emit(ctx);
-            ctx->emit(op1(), BC_NOARG, BC_KEEPLINE);
-            ctx->patch_jump(patch);
-        }else{
-            expr->emit(ctx);
-            ctx->emit(op1(), BC_NOARG, BC_KEEPLINE);
-        }
-        ctx->emit(OP_LOOP_CONTINUE, BC_NOARG, BC_KEEPLINE);
-        ctx->exit_block();
-    }
+    void emit(CodeEmitContext* ctx) override;
 };
 
 struct ListCompExpr: CompExpr{
@@ -578,52 +267,8 @@ struct FStringExpr: Expr{
         return fmt("f", src.escape());
     }
 
-    void _load_simple_expr(CodeEmitContext* ctx, Str expr){
-        // TODO: pre compile this into a function
-        int dot = expr.index(".");
-        if(dot < 0){
-            ctx->emit(OP_LOAD_NAME, StrName(expr.sv()).index, line);
-        }else{
-            StrName name(expr.substr(0, dot).sv());
-            StrName attr(expr.substr(dot+1).sv());
-            ctx->emit(OP_LOAD_NAME, name.index, line);
-            ctx->emit(OP_LOAD_ATTR, attr.index, line);
-        }
-    }
-
-    void emit(CodeEmitContext* ctx) override {
-        VM* vm = ctx->vm;
-        static const std::regex pattern(R"(\{(.*?)\})");
-        std::cregex_iterator begin(src.begin(), src.end(), pattern);
-        std::cregex_iterator end;
-        int size = 0;
-        int i = 0;
-        for(auto it = begin; it != end; it++) {
-            std::cmatch m = *it;
-            if (i < m.position()) {
-                Str literal = src.substr(i, m.position() - i);
-                ctx->emit(OP_LOAD_CONST, ctx->add_const(VAR(literal)), line);
-                size++;
-            }
-            Str expr = m[1].str();
-            int conon = expr.index(":");
-            if(conon >= 0){
-                _load_simple_expr(ctx, expr.substr(0, conon));
-                Str spec = expr.substr(conon+1);
-                ctx->emit(OP_FORMAT_STRING, ctx->add_const(VAR(spec)), line);
-            }else{
-                _load_simple_expr(ctx, expr);
-            }
-            size++;
-            i = (int)(m.position() + m.length());
-        }
-        if (i < src.length()) {
-            Str literal = src.substr(i, src.length() - i);
-            ctx->emit(OP_LOAD_CONST, ctx->add_const(VAR(literal)), line);
-            size++;
-        }
-        ctx->emit(OP_BUILD_STRING, size, line);
-    }
+    void _load_simple_expr(CodeEmitContext* ctx, Str expr);
+    void emit(CodeEmitContext* ctx) override;
 };
 
 struct SubscrExpr: Expr{
@@ -631,25 +276,9 @@ struct SubscrExpr: Expr{
     Expr_ b;
     std::string str() const override { return "Subscr()"; }
 
-    void emit(CodeEmitContext* ctx) override{
-        a->emit(ctx);
-        b->emit(ctx);
-        ctx->emit(OP_LOAD_SUBSCR, BC_NOARG, line);
-    }
-
-    bool emit_del(CodeEmitContext* ctx) override {
-        a->emit(ctx);
-        b->emit(ctx);
-        ctx->emit(OP_DELETE_SUBSCR, BC_NOARG, line);
-        return true;
-    }
-
-    bool emit_store(CodeEmitContext* ctx) override {
-        a->emit(ctx);
-        b->emit(ctx);
-        ctx->emit(OP_STORE_SUBSCR, BC_NOARG, line);
-        return true;
-    }
+    void emit(CodeEmitContext* ctx) override;
+    bool emit_del(CodeEmitContext* ctx) override;
+    bool emit_store(CodeEmitContext* ctx) override;
 };
 
 struct AttribExpr: Expr{
@@ -659,32 +288,10 @@ struct AttribExpr: Expr{
     AttribExpr(Expr_ a, Str&& b): a(std::move(a)), b(std::move(b)) {}
     std::string str() const override { return "Attrib()"; }
 
-    void emit(CodeEmitContext* ctx) override{
-        a->emit(ctx);
-        int index = StrName(b).index;
-        ctx->emit(OP_LOAD_ATTR, index, line);
-    }
-
-    bool emit_del(CodeEmitContext* ctx) override {
-        a->emit(ctx);
-        int index = StrName(b).index;
-        ctx->emit(OP_DELETE_ATTR, index, line);
-        return true;
-    }
-
-    bool emit_store(CodeEmitContext* ctx) override {
-        a->emit(ctx);
-        int index = StrName(b).index;
-        ctx->emit(OP_STORE_ATTR, index, line);
-        return true;
-    }
-
-    void emit_method(CodeEmitContext* ctx) {
-        a->emit(ctx);
-        int index = StrName(b).index;
-        ctx->emit(OP_LOAD_METHOD, index, line);
-    }
-
+    void emit(CodeEmitContext* ctx) override;
+    bool emit_del(CodeEmitContext* ctx) override;
+    bool emit_store(CodeEmitContext* ctx) override;
+    void emit_method(CodeEmitContext* ctx);
     bool is_attrib() const override { return true; }
 };
 
@@ -694,57 +301,7 @@ struct CallExpr: Expr{
     // **a will be interpreted as a special keyword argument: {"**": a}
     std::vector<std::pair<Str, Expr_>> kwargs;
     std::string str() const override { return "Call()"; }
-
-    void emit(CodeEmitContext* ctx) override {
-        bool vargs = false;
-        bool vkwargs = false;
-        for(auto& arg: args) if(arg->is_starred()) vargs = true;
-        for(auto& item: kwargs) if(item.second->is_starred()) vkwargs = true;
-
-        // if callable is a AttrExpr, we should try to use `fast_call` instead of use `boundmethod` proxy
-        if(callable->is_attrib()){
-            auto p = static_cast<AttribExpr*>(callable.get());
-            p->emit_method(ctx);    // OP_LOAD_METHOD
-        }else{
-            callable->emit(ctx);
-            ctx->emit(OP_LOAD_NULL, BC_NOARG, BC_KEEPLINE);
-        }
-
-        if(vargs || vkwargs){
-            for(auto& item: args) item->emit(ctx);
-            ctx->emit(OP_BUILD_TUPLE_UNPACK, (int)args.size(), line);
-
-            if(!kwargs.empty()){
-                for(auto& item: kwargs){
-                    if(item.second->is_starred()){
-                        if(item.second->star_level() != 2) FATAL_ERROR();
-                        item.second->emit(ctx);
-                    }else{
-                        // k=v
-                        int index = ctx->add_const(py_var(ctx->vm, item.first));
-                        ctx->emit(OP_LOAD_CONST, index, line);
-                        item.second->emit(ctx);
-                        ctx->emit(OP_BUILD_TUPLE, 2, line);
-                    }
-                }
-                ctx->emit(OP_BUILD_DICT_UNPACK, (int)kwargs.size(), line);
-                ctx->emit(OP_CALL_TP, 1, line);
-            }else{
-                ctx->emit(OP_CALL_TP, 0, line);
-            }
-        }else{
-            // vectorcall protocal
-            for(auto& item: args) item->emit(ctx);
-            for(auto& item: kwargs){
-                int index = StrName(item.first.sv()).index;
-                ctx->emit(OP_LOAD_INTEGER, index, line);
-                item.second->emit(ctx);
-            }
-            int KWARGC = (int)kwargs.size();
-            int ARGC = (int)args.size();
-            ctx->emit(OP_CALL, (KWARGC<<16)|ARGC, line);
-        }
-    }
+    void emit(CodeEmitContext* ctx) override;
 };
 
 struct GroupedExpr: Expr{
@@ -772,82 +329,9 @@ struct BinaryExpr: Expr{
     Expr_ rhs;
     std::string str() const override { return TK_STR(op); }
 
-    bool is_compare() const override {
-        switch(op){
-            case TK("<"): case TK("<="): case TK("=="):
-            case TK("!="): case TK(">"): case TK(">="): return true;
-            default: return false;
-        }
-    }
-
-    void _emit_compare(CodeEmitContext* ctx, std::vector<int>& jmps){
-        if(lhs->is_compare()){
-            static_cast<BinaryExpr*>(lhs.get())->_emit_compare(ctx, jmps);
-        }else{
-            lhs->emit(ctx); // [a]
-        }
-        rhs->emit(ctx); // [a, b]
-        ctx->emit(OP_DUP_TOP, BC_NOARG, line);      // [a, b, b]
-        ctx->emit(OP_ROT_THREE, BC_NOARG, line);    // [b, a, b]
-        switch(op){
-            case TK("<"):   ctx->emit(OP_COMPARE_LT, BC_NOARG, line);  break;
-            case TK("<="):  ctx->emit(OP_COMPARE_LE, BC_NOARG, line);  break;
-            case TK("=="):  ctx->emit(OP_COMPARE_EQ, BC_NOARG, line);  break;
-            case TK("!="):  ctx->emit(OP_COMPARE_NE, BC_NOARG, line);  break;
-            case TK(">"):   ctx->emit(OP_COMPARE_GT, BC_NOARG, line);  break;
-            case TK(">="):  ctx->emit(OP_COMPARE_GE, BC_NOARG, line);  break;
-            default: UNREACHABLE();
-        }
-        // [b, RES]
-        int index = ctx->emit(OP_SHORTCUT_IF_FALSE_OR_POP, BC_NOARG, line);
-        jmps.push_back(index);
-    }
-
-    void emit(CodeEmitContext* ctx) override {
-        std::vector<int> jmps;
-        if(is_compare() && lhs->is_compare()){
-            // (a < b) < c
-            static_cast<BinaryExpr*>(lhs.get())->_emit_compare(ctx, jmps);
-            // [b, RES]
-        }else{
-            // (1 + 2) < c
-            lhs->emit(ctx);
-        }
-
-        rhs->emit(ctx);
-        switch (op) {
-            case TK("+"):   ctx->emit(OP_BINARY_ADD, BC_NOARG, line);  break;
-            case TK("-"):   ctx->emit(OP_BINARY_SUB, BC_NOARG, line);  break;
-            case TK("*"):   ctx->emit(OP_BINARY_MUL, BC_NOARG, line);  break;
-            case TK("/"):   ctx->emit(OP_BINARY_TRUEDIV, BC_NOARG, line);  break;
-            case TK("//"):  ctx->emit(OP_BINARY_FLOORDIV, BC_NOARG, line);  break;
-            case TK("%"):   ctx->emit(OP_BINARY_MOD, BC_NOARG, line);  break;
-            case TK("**"):  ctx->emit(OP_BINARY_POW, BC_NOARG, line);  break;
-
-            case TK("<"):   ctx->emit(OP_COMPARE_LT, BC_NOARG, line);  break;
-            case TK("<="):  ctx->emit(OP_COMPARE_LE, BC_NOARG, line);  break;
-            case TK("=="):  ctx->emit(OP_COMPARE_EQ, BC_NOARG, line);  break;
-            case TK("!="):  ctx->emit(OP_COMPARE_NE, BC_NOARG, line);  break;
-            case TK(">"):   ctx->emit(OP_COMPARE_GT, BC_NOARG, line);  break;
-            case TK(">="):  ctx->emit(OP_COMPARE_GE, BC_NOARG, line);  break;
-
-            case TK("in"):      ctx->emit(OP_CONTAINS_OP, 0, line);   break;
-            case TK("not in"):  ctx->emit(OP_CONTAINS_OP, 1, line);   break;
-            case TK("is"):      ctx->emit(OP_IS_OP, 0, line);         break;
-            case TK("is not"):  ctx->emit(OP_IS_OP, 1, line);         break;
-
-            case TK("<<"):  ctx->emit(OP_BITWISE_LSHIFT, BC_NOARG, line);  break;
-            case TK(">>"):  ctx->emit(OP_BITWISE_RSHIFT, BC_NOARG, line);  break;
-            case TK("&"):   ctx->emit(OP_BITWISE_AND, BC_NOARG, line);  break;
-            case TK("|"):   ctx->emit(OP_BITWISE_OR, BC_NOARG, line);  break;
-            case TK("^"):   ctx->emit(OP_BITWISE_XOR, BC_NOARG, line);  break;
-
-            case TK("@"):   ctx->emit(OP_BINARY_MATMUL, BC_NOARG, line);  break;
-            default: FATAL_ERROR();
-        }
-
-        for(int i: jmps) ctx->patch_jump(i);
-    }
+    bool is_compare() const override;
+    void _emit_compare(CodeEmitContext* ctx, std::vector<int>& jmps);
+    void emit(CodeEmitContext* ctx) override;
 };
 
 
@@ -856,16 +340,7 @@ struct TernaryExpr: Expr{
     Expr_ true_expr;
     Expr_ false_expr;
     std::string str() const override { return "Ternary()"; }
-
-    void emit(CodeEmitContext* ctx) override {
-        cond->emit(ctx);
-        int patch = ctx->emit(OP_POP_JUMP_IF_FALSE, BC_NOARG, cond->line);
-        true_expr->emit(ctx);
-        int patch_2 = ctx->emit(OP_JUMP_ABSOLUTE, BC_NOARG, true_expr->line);
-        ctx->patch_jump(patch);
-        false_expr->emit(ctx);
-        ctx->patch_jump(patch_2);
-    }
+    void emit(CodeEmitContext* ctx) override;
 };
 
 

+ 11 - 86
include/pocketpy/frame.h

@@ -14,9 +14,7 @@ struct FastLocals{
     const NameDictInt* varnames_inv;
     PyObject** a;
 
-    int size() const{
-        return varnames_inv->size();
-    }
+    int size() const{ return varnames_inv->size();}
 
     PyObject*& operator[](int i){ return a[i]; }
     PyObject* operator[](int i) const { return a[i]; }
@@ -24,37 +22,11 @@ struct FastLocals{
     FastLocals(const CodeObject* co, PyObject** a): varnames_inv(&co->varnames_inv), a(a) {}
     FastLocals(const FastLocals& other): varnames_inv(other.varnames_inv), a(other.a) {}
 
-    PyObject* try_get(StrName name){
-        int index = varnames_inv->try_get(name);
-        if(index == -1) return nullptr;
-        return a[index];
-    }
-
-    bool contains(StrName name){
-        return varnames_inv->contains(name);
-    }
-
-    void erase(StrName name){
-        int index = varnames_inv->try_get(name);
-        if(index == -1) FATAL_ERROR();
-        a[index] = nullptr;
-    }
-
-    bool try_set(StrName name, PyObject* value){
-        int index = varnames_inv->try_get(name);
-        if(index == -1) return false;
-        a[index] = value;
-        return true;
-    }
-
-    NameDict_ to_namedict(){
-        NameDict_ dict = make_sp<NameDict>();
-        varnames_inv->apply([&](StrName name, int index){
-            PyObject* value = a[index];
-            if(value != PY_NULL) dict->set(name, value);
-        });
-        return dict;
-    }
+    PyObject* try_get(StrName name);
+    bool contains(StrName name);
+    void erase(StrName name);
+    bool try_set(StrName name, PyObject* value);
+    NameDict_ to_namedict();
 };
 
 template<size_t MAX_SIZE>
@@ -116,12 +88,7 @@ struct Frame {
 
     NameDict& f_globals() noexcept { return _module->attr(); }
     
-    PyObject* f_closure_try_get(StrName name){
-        if(_callable == nullptr) return nullptr;
-        Function& fn = PK_OBJ_GET(Function, _callable);
-        if(fn._closure == nullptr) return nullptr;
-        return fn._closure->try_get(name);
-    }
+    PyObject* f_closure_try_get(StrName name);
 
     Frame(ValueStack* _s, PyObject** p0, const CodeObject* co, PyObject* _module, PyObject* _callable)
             : _s(_s), _sp_base(p0), co(co), _module(_module), _callable(_callable), _locals(co, p0) { }
@@ -140,58 +107,16 @@ struct Frame {
         return co->codes[_ip];
     }
 
-    Str snapshot(){
-        int line = co->lines[_ip];
-        return co->src->snapshot(line);
-    }
+    Str snapshot();
 
     PyObject** actual_sp_base() const { return _locals.a; }
     int stack_size() const { return _s->_sp - actual_sp_base(); }
     ArgsView stack_view() const { return ArgsView(actual_sp_base(), _s->_sp); }
 
     void jump_abs(int i){ _next_ip = i; }
-    // void jump_rel(int i){ _next_ip += i; }
-
-    bool jump_to_exception_handler(){
-        // try to find a parent try block
-        int block = co->codes[_ip].block;
-        while(block >= 0){
-            if(co->blocks[block].type == TRY_EXCEPT) break;
-            block = co->blocks[block].parent;
-        }
-        if(block < 0) return false;
-        PyObject* obj = _s->popx();         // pop exception object
-        // get the stack size of the try block (depth of for loops)
-        int _stack_size = co->blocks[block].for_loop_depth;
-        if(stack_size() < _stack_size) throw std::runtime_error("invalid stack size");
-        _s->reset(actual_sp_base() + _locals.size() + _stack_size);          // rollback the stack   
-        _s->push(obj);                                      // push exception object
-        _next_ip = co->blocks[block].end;
-        return true;
-    }
-
-    int _exit_block(int i){
-        if(co->blocks[i].type == FOR_LOOP) _s->pop();
-        return co->blocks[i].parent;
-    }
-
-    void jump_abs_break(int target){
-        const Bytecode& prev = co->codes[_ip];
-        int i = prev.block;
-        _next_ip = target;
-        if(_next_ip >= co->codes.size()){
-            while(i>=0) i = _exit_block(i);
-        }else{
-            // BUG!!!
-            // for i in range(4):
-            //     _ = 0
-            // # if there is no op here, the block check will fail
-            // while i: --i
-            const Bytecode& next = co->codes[target];
-            while(i>=0 && i!=next.block) i = _exit_block(i);
-            if(i!=next.block) throw std::runtime_error("invalid jump");
-        }
-    }
+    bool jump_to_exception_handler();
+    int _exit_block(int i);
+    void jump_abs_break(int target);
 
     void _gc_mark() const {
         PK_OBJ_MARK(_module);

+ 4 - 59
include/pocketpy/gc.h

@@ -69,66 +69,11 @@ struct ManagedHeap{
     inline static std::map<Type, int> deleted;
 #endif
 
-    int sweep(){
-        std::vector<PyObject*> alive;
-        for(PyObject* obj: gen){
-            if(obj->gc.marked){
-                obj->gc.marked = false;
-                alive.push_back(obj);
-            }else{
-#if PK_DEBUG_GC_STATS
-                deleted[obj->type] += 1;
-#endif
-                if(_gc_on_delete) _gc_on_delete(vm, obj);
-                obj->~PyObject();
-                pool64.dealloc(obj);
-            }
-        }
-
-        // clear _no_gc marked flag
-        for(PyObject* obj: _no_gc) obj->gc.marked = false;
-
-        int freed = gen.size() - alive.size();
-        // std::cout << "GC: " << alive.size() << "/" << gen.size() << " (" << freed << " freed)" << std::endl;
-        gen.clear();
-        gen.swap(alive);
-        return freed;
-    }
-
-    void _auto_collect(){
-#if !PK_DEBUG_NO_AUTO_GC
-        if(_gc_lock_counter > 0) return;
-        if(gc_counter < gc_threshold) return;
-        gc_counter = 0;
-        collect();
-        gc_threshold = gen.size() * 2;
-        if(gc_threshold < kMinGCThreshold) gc_threshold = kMinGCThreshold;
-#endif
-    }
-
-    int collect(){
-        if(_gc_lock_counter > 0) FATAL_ERROR();
-        mark();
-        int freed = sweep();
-        return freed;
-    }
-
+    int sweep();
+    void _auto_collect();
+    int collect();
     void mark();
-
-    ~ManagedHeap(){
-        for(PyObject* obj: _no_gc) { obj->~PyObject(); pool64.dealloc(obj); }
-        for(PyObject* obj: gen) { obj->~PyObject(); pool64.dealloc(obj); }
-#if PK_DEBUG_GC_STATS
-        for(auto& [type, count]: deleted){
-            std::cout << "GC: " << obj_type_name(vm, type) << "=" << count << std::endl;
-        }
-#endif
-    }
+    ~ManagedHeap();
 };
 
-inline void FuncDecl::_gc_mark() const{
-    code->_gc_mark();
-    for(int i=0; i<kwargs.size(); i++) PK_OBJ_MARK(kwargs[i].value);
-}
-
 }   // namespace pkpy

+ 7 - 25
include/pocketpy/obj.h

@@ -7,8 +7,9 @@
 namespace pkpy {
     
 struct CodeObject;
+typedef shared_ptr<CodeObject> CodeObject_;
+
 struct Frame;
-struct Function;
 class VM;
 
 #if PK_ENABLE_STD_FUNCTION
@@ -17,8 +18,6 @@ using NativeFuncC = std::function<PyObject*(VM*, ArgsView)>;
 typedef PyObject* (*NativeFuncC)(VM*, ArgsView);
 #endif
 
-typedef shared_ptr<CodeObject> CodeObject_;
-
 struct FuncDecl {
     struct KwArg {
         int key;                // index in co->varnames
@@ -70,19 +69,8 @@ struct NativeFunc {
         return reinterpret_cast<const T&>(_userdata);
     }
     
-    NativeFunc(NativeFuncC f, int argc, bool method){
-        this->f = f;
-        this->argc = argc;
-        if(argc != -1) this->argc += (int)method;
-        _has_userdata = false;
-    }
-
-    NativeFunc(NativeFuncC f, FuncDecl_ decl){
-        this->f = f;
-        this->argc = -1;
-        this->decl = decl;
-        _has_userdata = false;
-    }
+    NativeFunc(NativeFuncC f, int argc, bool method);
+    NativeFunc(NativeFuncC f, FuncDecl_ decl);
 
     void check_size(VM* vm, ArgsView args) const;
     PyObject* call(VM* vm, ArgsView args) const;
@@ -175,13 +163,9 @@ struct PyObject{
 
     PyObject(Type type) : type(type), _attr(nullptr) {}
 
-    virtual ~PyObject() {
-        if(_attr == nullptr) return;
-        _attr->~NameDict();
-        pool64.dealloc(_attr);
-    }
+    virtual ~PyObject();
 
-    void enable_instance_dict(float lf=kInstAttrLoadFactor) noexcept {
+    void enable_instance_dict(float lf=kInstAttrLoadFactor) {
         _attr = new(pool64.alloc<NameDict>()) NameDict(lf);
     }
 };
@@ -444,11 +428,9 @@ struct Py_<DummyModule> final: PyObject {
 };
 
 template<typename T>
-inline T lambda_get_userdata(PyObject** p){
+T lambda_get_userdata(PyObject** p){
     if(p[-1] != PY_NULL) return PK_OBJ_GET(NativeFunc, p[-1]).get_userdata<T>();
     else return PK_OBJ_GET(NativeFunc, p[-2]).get_userdata<T>();
 }
 
-
-
 }   // namespace pkpy

+ 23 - 259
include/pocketpy/vm.h

@@ -147,109 +147,19 @@ public:
 
     const bool enable_os;
 
-    VM(bool enable_os=true) : heap(this), enable_os(enable_os) {
-        this->vm = this;
-        _stdout = [](VM* vm, const Str& s) {
-            PK_UNUSED(vm);
-            std::cout << s;
-        };
-        _stderr = [](VM* vm, const Str& s) {
-            PK_UNUSED(vm);
-            std::cerr << s;
-        };
-        callstack.reserve(8);
-        _main = nullptr;
-        _last_exception = nullptr;
-        _import_handler = [](const Str& name) {
-            PK_UNUSED(name);
-            return Bytes();
-        };
-        init_builtin_types();
-    }
-
-    FrameId top_frame() {
-#if PK_DEBUG_EXTRA_CHECK
-        if(callstack.empty()) FATAL_ERROR();
-#endif
-        return FrameId(&callstack.data(), callstack.size()-1);
-    }
-
-    PyObject* py_str(PyObject* obj){
-        const PyTypeInfo* ti = _inst_type_info(obj);
-        if(ti->m__str__) return ti->m__str__(this, obj);
-        PyObject* self;
-        PyObject* f = get_unbound_method(obj, __str__, &self, false);
-        if(self != PY_NULL) return call_method(self, f);
-        return py_repr(obj);
-    }
-
-    PyObject* py_repr(PyObject* obj){
-        const PyTypeInfo* ti = _inst_type_info(obj);
-        if(ti->m__repr__) return ti->m__repr__(this, obj);
-        return call_method(obj, __repr__);
-    }
-
-    PyObject* py_json(PyObject* obj){
-        const PyTypeInfo* ti = _inst_type_info(obj);
-        if(ti->m__json__) return ti->m__json__(this, obj);
-        return call_method(obj, __json__);
-    }
-
-    PyObject* py_iter(PyObject* obj){
-        const PyTypeInfo* ti = _inst_type_info(obj);
-        if(ti->m__iter__) return ti->m__iter__(this, obj);
-        PyObject* self;
-        PyObject* iter_f = get_unbound_method(obj, __iter__, &self, false);
-        if(self != PY_NULL) return call_method(self, iter_f);
-        TypeError(OBJ_NAME(_t(obj)).escape() + " object is not iterable");
-        return nullptr;
-    }
+    VM(bool enable_os=true);
 
-    PyObject* find_name_in_mro(PyObject* cls, StrName name){
-        PyObject* val;
-        do{
-            val = cls->attr().try_get(name);
-            if(val != nullptr) return val;
-            Type base = _all_types[PK_OBJ_GET(Type, cls)].base;
-            if(base.index == -1) break;
-            cls = _all_types[base].obj;
-        }while(true);
-        return nullptr;
-    }
+    FrameId top_frame();
+    void _pop_frame();
 
-    bool isinstance(PyObject* obj, Type cls_t){
-        Type obj_t = PK_OBJ_GET(Type, _t(obj));
-        do{
-            if(obj_t == cls_t) return true;
-            Type base = _all_types[obj_t].base;
-            if(base.index == -1) break;
-            obj_t = base;
-        }while(true);
-        return false;
-    }
+    PyObject* py_str(PyObject* obj);
+    PyObject* py_repr(PyObject* obj);
+    PyObject* py_json(PyObject* obj);
+    PyObject* py_iter(PyObject* obj);
 
-    PyObject* exec(Str source, Str filename, CompileMode mode, PyObject* _module=nullptr){
-        if(_module == nullptr) _module = _main;
-        try {
-            CodeObject_ code = compile(source, filename, mode);
-#if PK_DEBUG_DIS_EXEC
-            if(_module == _main) std::cout << disassemble(code) << '\n';
-#endif
-            return _exec(code, _module);
-        }catch (const Exception& e){
-            _stderr(this, e.summary() + "\n");
-        }
-#if !PK_DEBUG_FULL_EXCEPTION
-        catch (const std::exception& e) {
-            Str msg = "An std::exception occurred! It could be a bug.\n";
-            msg = msg + e.what();
-            _stderr(this, msg + "\n");
-        }
-#endif
-        callstack.clear();
-        s_data.clear();
-        return nullptr;
-    }
+    PyObject* find_name_in_mro(PyObject* cls, StrName name);
+    bool isinstance(PyObject* obj, Type cls_t);
+    PyObject* exec(Str source, Str filename, CompileMode mode, PyObject* _module=nullptr);
 
     template<typename ...Args>
     PyObject* _exec(Args&&... args){
@@ -257,12 +167,6 @@ public:
         return _run_top_frame();
     }
 
-    void _pop_frame(){
-        Frame* frame = &callstack.top();
-        s_data.reset(frame->_sp_base);
-        callstack.pop();
-    }
-
     void _push_varargs(){ }
     void _push_varargs(PyObject* _0){ PUSH(_0); }
     void _push_varargs(PyObject* _0, PyObject* _1){ PUSH(_0); PUSH(_1); }
@@ -291,68 +195,15 @@ public:
         return call_method(self, callable, args...);
     }
 
-    PyObject* property(NativeFuncC fget, NativeFuncC fset=nullptr){
-        PyObject* _0 = heap.gcnew(tp_native_func, NativeFunc(fget, 1, false));
-        PyObject* _1 = vm->None;
-        if(fset != nullptr) _1 = heap.gcnew(tp_native_func, NativeFunc(fset, 2, false));
-        return call(_t(tp_property), _0, _1);
-    }
+    PyObject* property(NativeFuncC fget, NativeFuncC fset=nullptr);
+    PyObject* new_type_object(PyObject* mod, StrName name, Type base, bool subclass_enabled=true);
+    Type _new_type_object(StrName name, Type base=0);
+    PyObject* _find_type_object(const Str& type);
 
-    PyObject* new_type_object(PyObject* mod, StrName name, Type base, bool subclass_enabled=true){
-        PyObject* obj = heap._new<Type>(tp_type, _all_types.size());
-        const PyTypeInfo& base_info = _all_types[base];
-        if(!base_info.subclass_enabled){
-            TypeError(fmt("type ", base_info.name.escape(), " is not `subclass_enabled`"));
-        }
-        PyTypeInfo info{
-            obj,
-            base,
-            (mod!=nullptr && mod!=builtins) ? Str(OBJ_NAME(mod)+"."+name.sv()): name.sv(),
-            subclass_enabled,
-        };
-        if(mod != nullptr) mod->attr().set(name, obj);
-        _all_types.push_back(info);
-        return obj;
-    }
-
-    Type _new_type_object(StrName name, Type base=0) {
-        PyObject* obj = new_type_object(nullptr, name, base, false);
-        return PK_OBJ_GET(Type, obj);
-    }
-
-    PyObject* _find_type_object(const Str& type){
-        PyObject* obj = builtins->attr().try_get(type);
-        if(obj == nullptr){
-            for(auto& t: _all_types) if(t.name == type) return t.obj;
-            throw std::runtime_error(fmt("type not found: ", type));
-        }
-        check_non_tagged_type(obj, tp_type);
-        return obj;
-    }
-
-    Type _type(const Str& type){
-        PyObject* obj = _find_type_object(type);
-        return PK_OBJ_GET(Type, obj);
-    }
-
-    PyTypeInfo* _type_info(const Str& type){
-        PyObject* obj = builtins->attr().try_get(type);
-        if(obj == nullptr){
-            for(auto& t: _all_types) if(t.name == type) return &t;
-            FATAL_ERROR();
-        }
-        return &_all_types[PK_OBJ_GET(Type, obj)];
-    }
-
-    PyTypeInfo* _type_info(Type type){
-        return &_all_types[type];
-    }
-
-    const PyTypeInfo* _inst_type_info(PyObject* obj){
-        if(is_int(obj)) return &_all_types[tp_int];
-        if(is_float(obj)) return &_all_types[tp_float];
-        return &_all_types[obj->type];
-    }
+    Type _type(const Str& type);
+    PyTypeInfo* _type_info(const Str& type);
+    PyTypeInfo* _type_info(Type type);
+    const PyTypeInfo* _inst_type_info(PyObject* obj);
 
 #define BIND_UNARY_SPECIAL(name)                                                        \
     void bind##name(Type type, PyObject* (*f)(VM*, PyObject*)){                         \
@@ -439,26 +290,7 @@ public:
         PK_OBJ_GET(NativeFunc, nf).set_userdata(f);
     }
 
-    bool py_equals(PyObject* lhs, PyObject* rhs){
-        if(lhs == rhs) return true;
-        const PyTypeInfo* ti = _inst_type_info(lhs);
-        PyObject* res;
-        if(ti->m__eq__){
-            res = ti->m__eq__(this, lhs, rhs);
-            if(res != vm->NotImplemented) return res == vm->True;
-        }
-        res = call_method(lhs, __eq__, rhs);
-        if(res != vm->NotImplemented) return res == vm->True;
-
-        ti = _inst_type_info(rhs);
-        if(ti->m__eq__){
-            res = ti->m__eq__(this, rhs, lhs);
-            if(res != vm->NotImplemented) return res == vm->True;
-        }
-        res = call_method(rhs, __eq__, lhs);
-        if(res != vm->NotImplemented) return res == vm->True;
-        return false;
-    }
+    bool py_equals(PyObject* lhs, PyObject* rhs);
 
     template<int ARGC>
     PyObject* bind_func(Str type, Str name, NativeFuncC fn) {
@@ -498,19 +330,8 @@ public:
         return bind_func<ARGC>(builtins, name, fn);
     }
 
-    int normalized_index(int index, int size){
-        if(index < 0) index += size;
-        if(index < 0 || index >= size){
-            IndexError(std::to_string(index) + " not in [0, " + std::to_string(size) + ")");
-        }
-        return index;
-    }
-
-    PyObject* py_next(PyObject* obj){
-        const PyTypeInfo* ti = _inst_type_info(obj);
-        if(ti->m__next__) return ti->m__next__(this, obj);
-        return call_method(obj, __next__);
-    }
+    int normalized_index(int index, int size);
+    PyObject* py_next(PyObject* obj);
     
     /***** Error Reporter *****/
     void _error(StrName name, const Str& msg){
@@ -600,66 +421,9 @@ public:
     };
 
     ImportContext _import_context;
+    PyObject* py_import(StrName name, bool relative=false);
+    ~VM();
 
-    PyObject* py_import(StrName name, bool relative=false){
-        Str filename;
-        int type;
-        if(relative){
-            ImportContext* ctx = &_import_context;
-            type = 2;
-            for(auto it=ctx->pending.rbegin(); it!=ctx->pending.rend(); ++it){
-                if(it->second == 2) continue;
-                if(it->second == 1){
-                    filename = fmt(it->first, kPlatformSep, name, ".py");
-                    name = fmt(it->first, '.', name).c_str();
-                    break;
-                }
-            }
-            if(filename.length() == 0) _error("ImportError", "relative import outside of package");
-        }else{
-            type = 0;
-            filename = fmt(name, ".py");
-        }
-        for(auto& [k, v]: _import_context.pending){
-            if(k == name){
-                vm->_error("ImportError", fmt("circular import ", name.escape()));
-            }
-        }
-        PyObject* ext_mod = _modules.try_get(name);
-        if(ext_mod == nullptr){
-            Str source;
-            auto it = _lazy_modules.find(name);
-            if(it == _lazy_modules.end()){
-                Bytes b = _import_handler(filename);
-                if(!relative && !b){
-                    filename = fmt(name, kPlatformSep, "__init__.py");
-                    b = _import_handler(filename);
-                    if(b) type = 1;
-                }
-                if(!b) _error("ImportError", fmt("module ", name.escape(), " not found"));
-                source = Str(b.str());
-            }else{
-                source = it->second;
-                _lazy_modules.erase(it);
-            }
-            auto _ = _import_context.temp(this, name, type);
-            CodeObject_ code = compile(source, filename, EXEC_MODE);
-            PyObject* new_mod = new_module(name);
-            _exec(code, new_mod);
-            new_mod->attr()._try_perfect_rehash();
-            return new_mod;
-        }else{
-            return ext_mod;
-        }
-    }
-
-    ~VM() {
-        callstack.clear();
-        s_data.clear();
-        _all_types.clear();
-        _modules.clear();
-        _lazy_modules.clear();
-    }
 #if PK_DEBUG_CEVAL_STEP
     void _log_s_data(const char* title = nullptr);
 #endif

+ 157 - 0
src/codeobject.cpp

@@ -0,0 +1,157 @@
+#include "pocketpy/codeobject.h"
+
+namespace pkpy{
+
+    CodeObject::CodeObject(shared_ptr<SourceData> src, const Str& name):
+        src(src), name(name) {}
+
+    void CodeObject::_gc_mark() const {
+        for(PyObject* v : consts) PK_OBJ_MARK(v);
+        for(auto& decl: func_decls) decl->_gc_mark();
+    }
+
+    void CodeObject::write(VM* vm, CodeObjectSerializer& ss) const{
+        ss.write_begin_mark();          // [
+        ss.write_str(src->filename);    // src->filename
+        ss.write_int(src->mode);        // src->mode
+        ss.write_end_mark();            // ]
+        ss.write_str(name);             // name
+        ss.write_bool(is_generator);    // is_generator
+        ss.write_begin_mark();          // [
+            for(Bytecode bc: codes){
+                if(StrName::is_valid(bc.arg)) ss.names.insert(StrName(bc.arg));
+                ss.write_bytes(bc);
+            }
+        ss.write_end_mark();            // ]
+        ss.write_begin_mark();          // [
+            for(int line: lines){
+                ss.write_int(line);         // line
+            }
+        ss.write_end_mark();            // ]
+        ss.write_begin_mark();          // [
+            for(PyObject* o: consts){
+                ss.write_object(vm, o);
+            }
+        ss.write_end_mark();            // ]
+        ss.write_begin_mark();          // [
+            for(StrName vn: varnames){
+                ss.write_name(vn);        // name
+            }
+        ss.write_end_mark();            // ]
+        ss.write_begin_mark();          // [
+            for(CodeBlock block: blocks){
+                ss.write_bytes(block);      // block
+            }
+        ss.write_end_mark();            // ]
+        ss.write_begin_mark();          // [
+            for(auto& label: labels.items()){
+                ss.write_name(label.first);     // label.first
+                ss.write_int(label.second);     // label.second
+            }
+        ss.write_end_mark();            // ]
+        ss.write_begin_mark();          // [
+            for(auto& decl: func_decls){
+                ss.write_code(vm, decl->code.get()); // decl->code
+                ss.write_begin_mark();      // [
+                    for(int arg: decl->args) ss.write_int(arg);
+                ss.write_end_mark();        // ]
+                
+                ss.write_begin_mark();      // [
+                    for(auto kw: decl->kwargs){
+                        ss.write_int(kw.key);           // kw.key
+                        ss.write_object(vm, kw.value);  // kw.value
+                    }
+                ss.write_end_mark();        // ]
+
+                ss.write_int(decl->starred_arg);
+                ss.write_int(decl->starred_kwarg);
+                ss.write_bool(decl->nested);
+            }
+        ss.write_end_mark();            // ]
+    }
+
+    Str CodeObject::serialize(VM* vm) const{
+        CodeObjectSerializer ss;
+        ss.write_code(vm, this);
+        return ss.str();
+    }
+
+
+    void CodeObjectSerializer::write_int(i64 v){
+        buffer += 'i';
+        buffer += std::to_string(v);
+        buffer += END;
+    }
+
+    void CodeObjectSerializer::write_float(f64 v){
+        buffer += 'f';
+        buffer += std::to_string(v);
+        buffer += END;
+    }
+
+    void CodeObjectSerializer::write_str(const Str& v){
+        buffer += 's';
+        buffer += v.escape(false).str();
+        buffer += END;
+    }
+
+    void CodeObjectSerializer::write_none(){
+        buffer += 'N';
+        buffer += END;
+    }
+
+    void CodeObjectSerializer::write_ellipsis(){
+        buffer += 'E';
+        buffer += END;
+    }
+
+    void CodeObjectSerializer::write_bool(bool v){
+        buffer += 'b';
+        buffer += v ? '1' : '0';
+        buffer += END;
+    }
+
+    void CodeObjectSerializer::write_begin_mark(){
+        buffer += '[';
+        buffer += END;
+        depth++;
+    }
+
+    void CodeObjectSerializer::write_name(StrName name){
+        PK_ASSERT(StrName::is_valid(name.index));
+        buffer += 'n';
+        buffer += std::to_string(name.index);
+        buffer += END;
+        names.insert(name);
+    }
+
+    void CodeObjectSerializer::write_end_mark(){
+        buffer += ']';
+        buffer += END;
+        depth--;
+        PK_ASSERT(depth >= 0);
+    }
+
+    std::string CodeObjectSerializer::str(){
+        PK_ASSERT(depth == 0);
+        for(auto name: names){
+            PK_ASSERT(StrName::is_valid(name.index));
+            write_name(name);
+            write_str(name.sv());
+        }
+        return std::move(buffer);
+    }
+
+    CodeObjectSerializer::CodeObjectSerializer(){
+        write_str(PK_VERSION);
+    }
+
+void CodeObjectSerializer::write_code(VM* vm, const CodeObject* co){
+    buffer += '(';
+    buffer += END;
+    co->write(vm, *this);
+    buffer += ')';
+    buffer += END;
+}
+
+}   // namespace pkpy

+ 176 - 0
src/dict.cpp

@@ -0,0 +1,176 @@
+#include "pocketpy/dict.h"
+
+namespace pkpy{
+
+    Dict::Dict(VM* vm): vm(vm), _capacity(__Capacity),
+            _mask(__Capacity-1),
+            _size(0), _critical_size(__Capacity*__LoadFactor+0.5f), _head_idx(-1), _tail_idx(-1){
+        _items = (Item*)pool128.alloc(_capacity * sizeof(Item));
+        memset(_items, 0, _capacity * sizeof(Item));
+        _nodes = (ItemNode*)pool64.alloc(_capacity * sizeof(ItemNode));
+        memset(_nodes, -1, _capacity * sizeof(ItemNode));
+    }
+
+    Dict::Dict(Dict&& other){
+        vm = other.vm;
+        _capacity = other._capacity;
+        _mask = other._mask;
+        _size = other._size;
+        _critical_size = other._critical_size;
+        _head_idx = other._head_idx;
+        _tail_idx = other._tail_idx;
+        _items = other._items;
+        _nodes = other._nodes;
+        other._items = nullptr;
+        other._nodes = nullptr;
+    }
+
+    Dict::Dict(const Dict& other){
+        vm = other.vm;
+        _capacity = other._capacity;
+        _mask = other._mask;
+        _size = other._size;
+        _critical_size = other._critical_size;
+        _head_idx = other._head_idx;
+        _tail_idx = other._tail_idx;
+        _items = (Item*)pool128.alloc(_capacity * sizeof(Item));
+        memcpy(_items, other._items, _capacity * sizeof(Item));
+        _nodes = (ItemNode*)pool64.alloc(_capacity * sizeof(ItemNode));
+        memcpy(_nodes, other._nodes, _capacity * sizeof(ItemNode));
+    }
+
+    void Dict::set(PyObject* key, PyObject* val){
+        // do possible rehash
+        if(_size+1 > _critical_size) _rehash();
+        bool ok; int i;
+        _probe(key, ok, i);
+        if(!ok) {
+            _size++;
+            _items[i].first = key;
+
+            // append to tail
+            if(_size == 0+1){
+                _head_idx = i;
+                _tail_idx = i;
+            }else{
+                _nodes[i].prev = _tail_idx;
+                _nodes[_tail_idx].next = i;
+                _tail_idx = i;
+            }
+        }
+        _items[i].second = val;
+    }
+
+    void Dict::_rehash(){
+        Item* old_items = _items;
+        int old_capacity = _capacity;
+        _capacity *= 2;
+        _mask = _capacity - 1;
+        _size = 0;
+        _critical_size = _capacity*__LoadFactor+0.5f;
+        _head_idx = -1;
+        _tail_idx = -1;
+        pool64.dealloc(_nodes);
+        _items = (Item*)pool128.alloc(_capacity * sizeof(Item));
+        memset(_items, 0, _capacity * sizeof(Item));
+        _nodes = (ItemNode*)pool64.alloc(_capacity * sizeof(ItemNode));
+        memset(_nodes, -1, _capacity * sizeof(ItemNode));
+
+        for(int i=0; i<old_capacity; i++){
+            if(old_items[i].first == nullptr) continue;
+            set(old_items[i].first, old_items[i].second);
+        }
+        pool128.dealloc(old_items);
+    }
+
+
+    PyObject* Dict::try_get(PyObject* key) const{
+        bool ok; int i;
+        _probe(key, ok, i);
+        if(!ok) return nullptr;
+        return _items[i].second;
+    }
+
+    bool Dict::contains(PyObject* key) const{
+        bool ok; int i;
+        _probe(key, ok, i);
+        return ok;
+    }
+
+    void Dict::erase(PyObject* key){
+        bool ok; int i;
+        _probe(key, ok, i);
+        if(!ok) return;
+        _items[i].first = nullptr;
+        _items[i].second = nullptr;
+        _size--;
+
+        if(_size == 0){
+            _head_idx = -1;
+            _tail_idx = -1;
+        }else{
+            if(_head_idx == i){
+                _head_idx = _nodes[i].next;
+                _nodes[_head_idx].prev = -1;
+            }else if(_tail_idx == i){
+                _tail_idx = _nodes[i].prev;
+                _nodes[_tail_idx].next = -1;
+            }else{
+                _nodes[_nodes[i].prev].next = _nodes[i].next;
+                _nodes[_nodes[i].next].prev = _nodes[i].prev;
+            }
+        }
+        _nodes[i].prev = -1;
+        _nodes[i].next = -1;
+    }
+
+    void Dict::update(const Dict& other){
+        other.apply([&](PyObject* k, PyObject* v){ set(k, v); });
+    }
+
+    Tuple Dict::keys() const{
+        Tuple t(_size);
+        int i = _head_idx;
+        int j = 0;
+        while(i != -1){
+            t[j++] = _items[i].first;
+            i = _nodes[i].next;
+        }
+        PK_ASSERT(j == _size);
+        return t;
+    }
+
+    Tuple Dict::values() const{
+        Tuple t(_size);
+        int i = _head_idx;
+        int j = 0;
+        while(i != -1){
+            t[j++] = _items[i].second;
+            i = _nodes[i].next;
+        }
+        PK_ASSERT(j == _size);
+        return t;
+    }
+
+    void Dict::clear(){
+        _size = 0;
+        _head_idx = -1;
+        _tail_idx = -1;
+        memset(_items, 0, _capacity * sizeof(Item));
+        memset(_nodes, -1, _capacity * sizeof(ItemNode));
+    }
+
+    Dict::~Dict(){
+        if(_items==nullptr) return;
+        pool128.dealloc(_items);
+        pool64.dealloc(_nodes);
+    }
+
+    void Dict::_gc_mark() const{
+        apply([](PyObject* k, PyObject* v){
+            PK_OBJ_MARK(k);
+            PK_OBJ_MARK(v);
+        });
+    }
+
+}   // namespace pkpy

+ 583 - 0
src/expr.cpp

@@ -0,0 +1,583 @@
+#include "pocketpy/expr.h"
+
+namespace pkpy{
+
+    bool CodeEmitContext::is_curr_block_loop() const {
+        return co->blocks[curr_block_i].type == FOR_LOOP || co->blocks[curr_block_i].type == WHILE_LOOP;
+    }
+
+    void CodeEmitContext::enter_block(CodeBlockType type){
+        if(type == FOR_LOOP) for_loop_depth++;
+        co->blocks.push_back(CodeBlock(
+            type, curr_block_i, for_loop_depth, (int)co->codes.size()
+        ));
+        curr_block_i = co->blocks.size()-1;
+    }
+
+    void CodeEmitContext::exit_block(){
+        auto curr_type = co->blocks[curr_block_i].type;
+        if(curr_type == FOR_LOOP) for_loop_depth--;
+        co->blocks[curr_block_i].end = co->codes.size();
+        curr_block_i = co->blocks[curr_block_i].parent;
+        if(curr_block_i < 0) FATAL_ERROR();
+
+        if(curr_type == FOR_LOOP){
+            // add a no op here to make block check work
+            emit(OP_NO_OP, BC_NOARG, BC_KEEPLINE);
+        }
+    }
+
+    // clear the expression stack and generate bytecode
+    void CodeEmitContext::emit_expr(){
+        if(s_expr.size() != 1){
+            throw std::runtime_error("s_expr.size() != 1\n" + _log_s_expr());
+        }
+        Expr_ expr = s_expr.popx();
+        expr->emit(this);
+    }
+
+    std::string CodeEmitContext::_log_s_expr(){
+        std::stringstream ss;
+        for(auto& e: s_expr.data()) ss << e->str() << " ";
+        return ss.str();
+    }
+
+    int CodeEmitContext::emit(Opcode opcode, int arg, int line) {
+        co->codes.push_back(
+            Bytecode{(uint16_t)opcode, (uint16_t)curr_block_i, arg}
+        );
+        co->lines.push_back(line);
+        int i = co->codes.size() - 1;
+        if(line==BC_KEEPLINE){
+            if(i>=1) co->lines[i] = co->lines[i-1];
+            else co->lines[i] = 1;
+        }
+        return i;
+    }
+
+    void CodeEmitContext::patch_jump(int index) {
+        int target = co->codes.size();
+        co->codes[index].arg = target;
+    }
+
+    bool CodeEmitContext::add_label(StrName name){
+        if(co->labels.contains(name)) return false;
+        co->labels.set(name, co->codes.size());
+        return true;
+    }
+
+    int CodeEmitContext::add_varname(StrName name){
+        int index = co->varnames_inv.try_get(name);
+        if(index >= 0) return index;
+        co->varnames.push_back(name);
+        index = co->varnames.size() - 1;
+        co->varnames_inv.set(name, index);
+        return index;
+    }
+
+    int CodeEmitContext::add_const(PyObject* v){
+        // simple deduplication, only works for int/float
+        for(int i=0; i<co->consts.size(); i++){
+            if(co->consts[i] == v) return i;
+        }
+        co->consts.push_back(v);
+        return co->consts.size() - 1;
+    }
+
+    int CodeEmitContext::add_func_decl(FuncDecl_ decl){
+        co->func_decls.push_back(decl);
+        return co->func_decls.size() - 1;
+    }
+
+
+    void NameExpr::emit(CodeEmitContext* ctx) {
+        int index = ctx->co->varnames_inv.try_get(name);
+        if(scope == NAME_LOCAL && index >= 0){
+            ctx->emit(OP_LOAD_FAST, index, line);
+        }else{
+            Opcode op = ctx->level <= 1 ? OP_LOAD_GLOBAL : OP_LOAD_NONLOCAL;
+            // we cannot determine the scope when calling exec()/eval()
+            if(scope == NAME_GLOBAL_UNKNOWN) op = OP_LOAD_NAME;
+            ctx->emit(op, StrName(name).index, line);
+        }
+    }
+
+    bool NameExpr::emit_del(CodeEmitContext* ctx) {
+        switch(scope){
+            case NAME_LOCAL:
+                ctx->emit(OP_DELETE_FAST, ctx->add_varname(name), line);
+                break;
+            case NAME_GLOBAL:
+                ctx->emit(OP_DELETE_GLOBAL, StrName(name).index, line);
+                break;
+            case NAME_GLOBAL_UNKNOWN:
+                ctx->emit(OP_DELETE_NAME, StrName(name).index, line);
+                break;
+            default: FATAL_ERROR(); break;
+        }
+        return true;
+    }
+
+    bool NameExpr::emit_store(CodeEmitContext* ctx) {
+        if(ctx->is_compiling_class){
+            int index = StrName(name).index;
+            ctx->emit(OP_STORE_CLASS_ATTR, index, line);
+            return true;
+        }
+        switch(scope){
+            case NAME_LOCAL:
+                ctx->emit(OP_STORE_FAST, ctx->add_varname(name), line);
+                break;
+            case NAME_GLOBAL:
+                ctx->emit(OP_STORE_GLOBAL, StrName(name).index, line);
+                break;
+            case NAME_GLOBAL_UNKNOWN:
+                ctx->emit(OP_STORE_NAME, StrName(name).index, line);
+                break;
+            default: FATAL_ERROR(); break;
+        }
+        return true;
+    }
+
+
+    void StarredExpr::emit(CodeEmitContext* ctx) {
+        child->emit(ctx);
+        ctx->emit(OP_UNARY_STAR, level, line);
+    }
+
+    bool StarredExpr::emit_store(CodeEmitContext* ctx) {
+        if(level != 1) return false;
+        // simply proxy to child
+        return child->emit_store(ctx);
+    }
+
+    void NotExpr::emit(CodeEmitContext* ctx) {
+        child->emit(ctx);
+        ctx->emit(OP_UNARY_NOT, BC_NOARG, line);
+    }
+
+    void AndExpr::emit(CodeEmitContext* ctx) {
+        lhs->emit(ctx);
+        int patch = ctx->emit(OP_JUMP_IF_FALSE_OR_POP, BC_NOARG, line);
+        rhs->emit(ctx);
+        ctx->patch_jump(patch);
+    }
+
+    void OrExpr::emit(CodeEmitContext* ctx) {
+        lhs->emit(ctx);
+        int patch = ctx->emit(OP_JUMP_IF_TRUE_OR_POP, BC_NOARG, line);
+        rhs->emit(ctx);
+        ctx->patch_jump(patch);
+    }
+
+    void Literal0Expr::emit(CodeEmitContext* ctx){
+        switch (token) {
+            case TK("None"):    ctx->emit(OP_LOAD_NONE, BC_NOARG, line); break;
+            case TK("True"):    ctx->emit(OP_LOAD_TRUE, BC_NOARG, line); break;
+            case TK("False"):   ctx->emit(OP_LOAD_FALSE, BC_NOARG, line); break;
+            case TK("..."):     ctx->emit(OP_LOAD_ELLIPSIS, BC_NOARG, line); break;
+            default: FATAL_ERROR();
+        }
+    }
+
+    void LongExpr::emit(CodeEmitContext* ctx) {
+        VM* vm = ctx->vm;
+        ctx->emit(OP_LOAD_CONST, ctx->add_const(VAR(s)), line);
+        ctx->emit(OP_BUILD_LONG, BC_NOARG, line);
+    }
+
+    std::string LiteralExpr::str() const{
+        if(std::holds_alternative<i64>(value)){
+            return std::to_string(std::get<i64>(value));
+        }
+        if(std::holds_alternative<f64>(value)){
+            return std::to_string(std::get<f64>(value));
+        }
+        if(std::holds_alternative<Str>(value)){
+            Str s = std::get<Str>(value).escape();
+            return s.str();
+        }
+        FATAL_ERROR();
+    }
+
+    void LiteralExpr::emit(CodeEmitContext* ctx) {
+        VM* vm = ctx->vm;
+        PyObject* obj = nullptr;
+        if(std::holds_alternative<i64>(value)){
+            i64 _val = std::get<i64>(value);
+            if(_val >= INT16_MIN && _val <= INT16_MAX){
+                ctx->emit(OP_LOAD_INTEGER, (int)_val, line);
+                return;
+            }
+            obj = VAR(_val);
+        }
+        if(std::holds_alternative<f64>(value)){
+            obj = VAR(std::get<f64>(value));
+        }
+        if(std::holds_alternative<Str>(value)){
+            obj = VAR(std::get<Str>(value));
+        }
+        if(obj == nullptr) FATAL_ERROR();
+        ctx->emit(OP_LOAD_CONST, ctx->add_const(obj), line);
+    }
+
+    void NegatedExpr::emit(CodeEmitContext* ctx){
+        VM* vm = ctx->vm;
+        // if child is a int of float, do constant folding
+        if(child->is_literal()){
+            LiteralExpr* lit = static_cast<LiteralExpr*>(child.get());
+            if(std::holds_alternative<i64>(lit->value)){
+                i64 _val = -std::get<i64>(lit->value);
+                if(_val >= INT16_MIN && _val <= INT16_MAX){
+                    ctx->emit(OP_LOAD_INTEGER, (int)_val, line);
+                }else{
+                    ctx->emit(OP_LOAD_CONST, ctx->add_const(VAR(_val)), line);
+                }
+                return;
+            }
+            if(std::holds_alternative<f64>(lit->value)){
+                PyObject* obj = VAR(-std::get<f64>(lit->value));
+                ctx->emit(OP_LOAD_CONST, ctx->add_const(obj), line);
+                return;
+            }
+        }
+        child->emit(ctx);
+        ctx->emit(OP_UNARY_NEGATIVE, BC_NOARG, line);
+    }
+
+
+    void SliceExpr::emit(CodeEmitContext* ctx){
+        if(start){
+            start->emit(ctx);
+        }else{
+            ctx->emit(OP_LOAD_NONE, BC_NOARG, line);
+        }
+
+        if(stop){
+            stop->emit(ctx);
+        }else{
+            ctx->emit(OP_LOAD_NONE, BC_NOARG, line);
+        }
+
+        if(step){
+            step->emit(ctx);
+        }else{
+            ctx->emit(OP_LOAD_NONE, BC_NOARG, line);
+        }
+
+        ctx->emit(OP_BUILD_SLICE, BC_NOARG, line);
+    }
+
+    void DictItemExpr::emit(CodeEmitContext* ctx) {
+        if(is_starred()){
+            PK_ASSERT(key == nullptr);
+            value->emit(ctx);
+        }else{
+            value->emit(ctx);
+            key->emit(ctx);     // reverse order
+            ctx->emit(OP_BUILD_TUPLE, 2, line);
+        }
+    }
+
+    bool TupleExpr::emit_store(CodeEmitContext* ctx) {
+        // TOS is an iterable
+        // items may contain StarredExpr, we should check it
+        int starred_i = -1;
+        for(int i=0; i<items.size(); i++){
+            if(!items[i]->is_starred()) continue;
+            if(starred_i == -1) starred_i = i;
+            else return false;  // multiple StarredExpr not allowed
+        }
+
+        if(starred_i == -1){
+            Bytecode& prev = ctx->co->codes.back();
+            if(prev.op == OP_BUILD_TUPLE && prev.arg == items.size()){
+                // build tuple and unpack it is meaningless
+                prev.op = OP_NO_OP;
+                prev.arg = BC_NOARG;
+            }else{
+                ctx->emit(OP_UNPACK_SEQUENCE, items.size(), line);
+            }
+        }else{
+            // starred assignment target must be in a tuple
+            if(items.size() == 1) return false;
+            // starred assignment target must be the last one (differ from cpython)
+            if(starred_i != items.size()-1) return false;
+            // a,*b = [1,2,3]
+            // stack is [1,2,3] -> [1,[2,3]]
+            ctx->emit(OP_UNPACK_EX, items.size()-1, line);
+        }
+        // do reverse emit
+        for(int i=items.size()-1; i>=0; i--){
+            bool ok = items[i]->emit_store(ctx);
+            if(!ok) return false;
+        }
+        return true;
+    }
+
+    bool TupleExpr::emit_del(CodeEmitContext* ctx){
+        for(auto& e: items){
+            bool ok = e->emit_del(ctx);
+            if(!ok) return false;
+        }
+        return true;
+    }
+
+    void CompExpr::emit(CodeEmitContext* ctx){
+        ctx->emit(op0(), 0, line);
+        iter->emit(ctx);
+        ctx->emit(OP_GET_ITER, BC_NOARG, BC_KEEPLINE);
+        ctx->enter_block(FOR_LOOP);
+        ctx->emit(OP_FOR_ITER, BC_NOARG, BC_KEEPLINE);
+        bool ok = vars->emit_store(ctx);
+        // this error occurs in `vars` instead of this line, but...nevermind
+        PK_ASSERT(ok);  // TODO: raise a SyntaxError instead
+        if(cond){
+            cond->emit(ctx);
+            int patch = ctx->emit(OP_POP_JUMP_IF_FALSE, BC_NOARG, BC_KEEPLINE);
+            expr->emit(ctx);
+            ctx->emit(op1(), BC_NOARG, BC_KEEPLINE);
+            ctx->patch_jump(patch);
+        }else{
+            expr->emit(ctx);
+            ctx->emit(op1(), BC_NOARG, BC_KEEPLINE);
+        }
+        ctx->emit(OP_LOOP_CONTINUE, BC_NOARG, BC_KEEPLINE);
+        ctx->exit_block();
+    }
+
+
+    void FStringExpr::_load_simple_expr(CodeEmitContext* ctx, Str expr){
+        // TODO: pre compile this into a function
+        int dot = expr.index(".");
+        if(dot < 0){
+            ctx->emit(OP_LOAD_NAME, StrName(expr.sv()).index, line);
+        }else{
+            StrName name(expr.substr(0, dot).sv());
+            StrName attr(expr.substr(dot+1).sv());
+            ctx->emit(OP_LOAD_NAME, name.index, line);
+            ctx->emit(OP_LOAD_ATTR, attr.index, line);
+        }
+    }
+
+    void FStringExpr::emit(CodeEmitContext* ctx){
+        VM* vm = ctx->vm;
+        static const std::regex pattern(R"(\{(.*?)\})");
+        std::cregex_iterator begin(src.begin(), src.end(), pattern);
+        std::cregex_iterator end;
+        int size = 0;
+        int i = 0;
+        for(auto it = begin; it != end; it++) {
+            std::cmatch m = *it;
+            if (i < m.position()) {
+                Str literal = src.substr(i, m.position() - i);
+                ctx->emit(OP_LOAD_CONST, ctx->add_const(VAR(literal)), line);
+                size++;
+            }
+            Str expr = m[1].str();
+            int conon = expr.index(":");
+            if(conon >= 0){
+                _load_simple_expr(ctx, expr.substr(0, conon));
+                Str spec = expr.substr(conon+1);
+                ctx->emit(OP_FORMAT_STRING, ctx->add_const(VAR(spec)), line);
+            }else{
+                _load_simple_expr(ctx, expr);
+            }
+            size++;
+            i = (int)(m.position() + m.length());
+        }
+        if (i < src.length()) {
+            Str literal = src.substr(i, src.length() - i);
+            ctx->emit(OP_LOAD_CONST, ctx->add_const(VAR(literal)), line);
+            size++;
+        }
+        ctx->emit(OP_BUILD_STRING, size, line);
+    }
+
+
+    void SubscrExpr::emit(CodeEmitContext* ctx){
+        a->emit(ctx);
+        b->emit(ctx);
+        ctx->emit(OP_LOAD_SUBSCR, BC_NOARG, line);
+    }
+
+    bool SubscrExpr::emit_del(CodeEmitContext* ctx){
+        a->emit(ctx);
+        b->emit(ctx);
+        ctx->emit(OP_DELETE_SUBSCR, BC_NOARG, line);
+        return true;
+    }
+
+    bool SubscrExpr::emit_store(CodeEmitContext* ctx){
+        a->emit(ctx);
+        b->emit(ctx);
+        ctx->emit(OP_STORE_SUBSCR, BC_NOARG, line);
+        return true;
+    }
+
+    void AttribExpr::emit(CodeEmitContext* ctx){
+        a->emit(ctx);
+        int index = StrName(b).index;
+        ctx->emit(OP_LOAD_ATTR, index, line);
+    }
+
+    bool AttribExpr::emit_del(CodeEmitContext* ctx) {
+        a->emit(ctx);
+        int index = StrName(b).index;
+        ctx->emit(OP_DELETE_ATTR, index, line);
+        return true;
+    }
+
+    bool AttribExpr::emit_store(CodeEmitContext* ctx){
+        a->emit(ctx);
+        int index = StrName(b).index;
+        ctx->emit(OP_STORE_ATTR, index, line);
+        return true;
+    }
+
+    void AttribExpr::emit_method(CodeEmitContext* ctx) {
+        a->emit(ctx);
+        int index = StrName(b).index;
+        ctx->emit(OP_LOAD_METHOD, index, line);
+    }
+
+    void CallExpr::emit(CodeEmitContext* ctx) {
+        bool vargs = false;
+        bool vkwargs = false;
+        for(auto& arg: args) if(arg->is_starred()) vargs = true;
+        for(auto& item: kwargs) if(item.second->is_starred()) vkwargs = true;
+
+        // if callable is a AttrExpr, we should try to use `fast_call` instead of use `boundmethod` proxy
+        if(callable->is_attrib()){
+            auto p = static_cast<AttribExpr*>(callable.get());
+            p->emit_method(ctx);    // OP_LOAD_METHOD
+        }else{
+            callable->emit(ctx);
+            ctx->emit(OP_LOAD_NULL, BC_NOARG, BC_KEEPLINE);
+        }
+
+        if(vargs || vkwargs){
+            for(auto& item: args) item->emit(ctx);
+            ctx->emit(OP_BUILD_TUPLE_UNPACK, (int)args.size(), line);
+
+            if(!kwargs.empty()){
+                for(auto& item: kwargs){
+                    if(item.second->is_starred()){
+                        if(item.second->star_level() != 2) FATAL_ERROR();
+                        item.second->emit(ctx);
+                    }else{
+                        // k=v
+                        int index = ctx->add_const(py_var(ctx->vm, item.first));
+                        ctx->emit(OP_LOAD_CONST, index, line);
+                        item.second->emit(ctx);
+                        ctx->emit(OP_BUILD_TUPLE, 2, line);
+                    }
+                }
+                ctx->emit(OP_BUILD_DICT_UNPACK, (int)kwargs.size(), line);
+                ctx->emit(OP_CALL_TP, 1, line);
+            }else{
+                ctx->emit(OP_CALL_TP, 0, line);
+            }
+        }else{
+            // vectorcall protocal
+            for(auto& item: args) item->emit(ctx);
+            for(auto& item: kwargs){
+                int index = StrName(item.first.sv()).index;
+                ctx->emit(OP_LOAD_INTEGER, index, line);
+                item.second->emit(ctx);
+            }
+            int KWARGC = (int)kwargs.size();
+            int ARGC = (int)args.size();
+            ctx->emit(OP_CALL, (KWARGC<<16)|ARGC, line);
+        }
+    }
+
+
+    bool BinaryExpr::is_compare() const {
+        switch(op){
+            case TK("<"): case TK("<="): case TK("=="):
+            case TK("!="): case TK(">"): case TK(">="): return true;
+            default: return false;
+        }
+    }
+
+    void BinaryExpr::_emit_compare(CodeEmitContext* ctx, std::vector<int>& jmps){
+        if(lhs->is_compare()){
+            static_cast<BinaryExpr*>(lhs.get())->_emit_compare(ctx, jmps);
+        }else{
+            lhs->emit(ctx); // [a]
+        }
+        rhs->emit(ctx); // [a, b]
+        ctx->emit(OP_DUP_TOP, BC_NOARG, line);      // [a, b, b]
+        ctx->emit(OP_ROT_THREE, BC_NOARG, line);    // [b, a, b]
+        switch(op){
+            case TK("<"):   ctx->emit(OP_COMPARE_LT, BC_NOARG, line);  break;
+            case TK("<="):  ctx->emit(OP_COMPARE_LE, BC_NOARG, line);  break;
+            case TK("=="):  ctx->emit(OP_COMPARE_EQ, BC_NOARG, line);  break;
+            case TK("!="):  ctx->emit(OP_COMPARE_NE, BC_NOARG, line);  break;
+            case TK(">"):   ctx->emit(OP_COMPARE_GT, BC_NOARG, line);  break;
+            case TK(">="):  ctx->emit(OP_COMPARE_GE, BC_NOARG, line);  break;
+            default: UNREACHABLE();
+        }
+        // [b, RES]
+        int index = ctx->emit(OP_SHORTCUT_IF_FALSE_OR_POP, BC_NOARG, line);
+        jmps.push_back(index);
+    }
+
+    void BinaryExpr::emit(CodeEmitContext* ctx) {
+        std::vector<int> jmps;
+        if(is_compare() && lhs->is_compare()){
+            // (a < b) < c
+            static_cast<BinaryExpr*>(lhs.get())->_emit_compare(ctx, jmps);
+            // [b, RES]
+        }else{
+            // (1 + 2) < c
+            lhs->emit(ctx);
+        }
+
+        rhs->emit(ctx);
+        switch (op) {
+            case TK("+"):   ctx->emit(OP_BINARY_ADD, BC_NOARG, line);  break;
+            case TK("-"):   ctx->emit(OP_BINARY_SUB, BC_NOARG, line);  break;
+            case TK("*"):   ctx->emit(OP_BINARY_MUL, BC_NOARG, line);  break;
+            case TK("/"):   ctx->emit(OP_BINARY_TRUEDIV, BC_NOARG, line);  break;
+            case TK("//"):  ctx->emit(OP_BINARY_FLOORDIV, BC_NOARG, line);  break;
+            case TK("%"):   ctx->emit(OP_BINARY_MOD, BC_NOARG, line);  break;
+            case TK("**"):  ctx->emit(OP_BINARY_POW, BC_NOARG, line);  break;
+
+            case TK("<"):   ctx->emit(OP_COMPARE_LT, BC_NOARG, line);  break;
+            case TK("<="):  ctx->emit(OP_COMPARE_LE, BC_NOARG, line);  break;
+            case TK("=="):  ctx->emit(OP_COMPARE_EQ, BC_NOARG, line);  break;
+            case TK("!="):  ctx->emit(OP_COMPARE_NE, BC_NOARG, line);  break;
+            case TK(">"):   ctx->emit(OP_COMPARE_GT, BC_NOARG, line);  break;
+            case TK(">="):  ctx->emit(OP_COMPARE_GE, BC_NOARG, line);  break;
+
+            case TK("in"):      ctx->emit(OP_CONTAINS_OP, 0, line);   break;
+            case TK("not in"):  ctx->emit(OP_CONTAINS_OP, 1, line);   break;
+            case TK("is"):      ctx->emit(OP_IS_OP, 0, line);         break;
+            case TK("is not"):  ctx->emit(OP_IS_OP, 1, line);         break;
+
+            case TK("<<"):  ctx->emit(OP_BITWISE_LSHIFT, BC_NOARG, line);  break;
+            case TK(">>"):  ctx->emit(OP_BITWISE_RSHIFT, BC_NOARG, line);  break;
+            case TK("&"):   ctx->emit(OP_BITWISE_AND, BC_NOARG, line);  break;
+            case TK("|"):   ctx->emit(OP_BITWISE_OR, BC_NOARG, line);  break;
+            case TK("^"):   ctx->emit(OP_BITWISE_XOR, BC_NOARG, line);  break;
+
+            case TK("@"):   ctx->emit(OP_BINARY_MATMUL, BC_NOARG, line);  break;
+            default: FATAL_ERROR();
+        }
+
+        for(int i: jmps) ctx->patch_jump(i);
+    }
+
+    void TernaryExpr::emit(CodeEmitContext* ctx){
+        cond->emit(ctx);
+        int patch = ctx->emit(OP_POP_JUMP_IF_FALSE, BC_NOARG, cond->line);
+        true_expr->emit(ctx);
+        int patch_2 = ctx->emit(OP_JUMP_ABSOLUTE, BC_NOARG, true_expr->line);
+        ctx->patch_jump(patch);
+        false_expr->emit(ctx);
+        ctx->patch_jump(patch_2);
+    }
+
+}   // namespace pkpy

+ 90 - 0
src/frame.cpp

@@ -0,0 +1,90 @@
+#include "pocketpy/frame.h"
+
+namespace pkpy{
+
+    PyObject* FastLocals::try_get(StrName name){
+        int index = varnames_inv->try_get(name);
+        if(index == -1) return nullptr;
+        return a[index];
+    }
+
+    bool FastLocals::contains(StrName name){
+        return varnames_inv->contains(name);
+    }
+
+    void FastLocals::erase(StrName name){
+        int index = varnames_inv->try_get(name);
+        if(index == -1) FATAL_ERROR();
+        a[index] = nullptr;
+    }
+
+    bool FastLocals::try_set(StrName name, PyObject* value){
+        int index = varnames_inv->try_get(name);
+        if(index == -1) return false;
+        a[index] = value;
+        return true;
+    }
+
+    NameDict_ FastLocals::to_namedict(){
+        NameDict_ dict = make_sp<NameDict>();
+        varnames_inv->apply([&](StrName name, int index){
+            PyObject* value = a[index];
+            if(value != PY_NULL) dict->set(name, value);
+        });
+        return dict;
+    }
+
+    PyObject* Frame::f_closure_try_get(StrName name){
+        if(_callable == nullptr) return nullptr;
+        Function& fn = PK_OBJ_GET(Function, _callable);
+        if(fn._closure == nullptr) return nullptr;
+        return fn._closure->try_get(name);
+    }
+
+    Str Frame::snapshot(){
+        int line = co->lines[_ip];
+        return co->src->snapshot(line);
+    }
+
+    bool Frame::jump_to_exception_handler(){
+        // try to find a parent try block
+        int block = co->codes[_ip].block;
+        while(block >= 0){
+            if(co->blocks[block].type == TRY_EXCEPT) break;
+            block = co->blocks[block].parent;
+        }
+        if(block < 0) return false;
+        PyObject* obj = _s->popx();         // pop exception object
+        // get the stack size of the try block (depth of for loops)
+        int _stack_size = co->blocks[block].for_loop_depth;
+        if(stack_size() < _stack_size) throw std::runtime_error("invalid stack size");
+        _s->reset(actual_sp_base() + _locals.size() + _stack_size);          // rollback the stack   
+        _s->push(obj);                                      // push exception object
+        _next_ip = co->blocks[block].end;
+        return true;
+    }
+
+    int Frame::_exit_block(int i){
+        if(co->blocks[i].type == FOR_LOOP) _s->pop();
+        return co->blocks[i].parent;
+    }
+
+    void Frame::jump_abs_break(int target){
+        const Bytecode& prev = co->codes[_ip];
+        int i = prev.block;
+        _next_ip = target;
+        if(_next_ip >= co->codes.size()){
+            while(i>=0) i = _exit_block(i);
+        }else{
+            // BUG!!!
+            // for i in range(4):
+            //     _ = 0
+            // # if there is no op here, the block check will fail
+            // while i: --i
+            const Bytecode& next = co->codes[target];
+            while(i>=0 && i!=next.block) i = _exit_block(i);
+            if(i!=next.block) throw std::runtime_error("invalid jump");
+        }
+    }
+
+}   // namespace pkpy

+ 65 - 0
src/gc.cpp

@@ -0,0 +1,65 @@
+#include "pocketpy/gc.h"
+
+namespace pkpy{
+
+    int ManagedHeap::sweep(){
+        std::vector<PyObject*> alive;
+        for(PyObject* obj: gen){
+            if(obj->gc.marked){
+                obj->gc.marked = false;
+                alive.push_back(obj);
+            }else{
+#if PK_DEBUG_GC_STATS
+                deleted[obj->type] += 1;
+#endif
+                if(_gc_on_delete) _gc_on_delete(vm, obj);
+                obj->~PyObject();
+                pool64.dealloc(obj);
+            }
+        }
+
+        // clear _no_gc marked flag
+        for(PyObject* obj: _no_gc) obj->gc.marked = false;
+
+        int freed = gen.size() - alive.size();
+        // std::cout << "GC: " << alive.size() << "/" << gen.size() << " (" << freed << " freed)" << std::endl;
+        gen.clear();
+        gen.swap(alive);
+        return freed;
+    }
+
+    void ManagedHeap::_auto_collect(){
+#if !PK_DEBUG_NO_AUTO_GC
+        if(_gc_lock_counter > 0) return;
+        if(gc_counter < gc_threshold) return;
+        gc_counter = 0;
+        collect();
+        gc_threshold = gen.size() * 2;
+        if(gc_threshold < kMinGCThreshold) gc_threshold = kMinGCThreshold;
+#endif
+    }
+
+    int ManagedHeap::collect(){
+        if(_gc_lock_counter > 0) FATAL_ERROR();
+        mark();
+        int freed = sweep();
+        return freed;
+    }
+
+    ManagedHeap::~ManagedHeap(){
+        for(PyObject* obj: _no_gc) { obj->~PyObject(); pool64.dealloc(obj); }
+        for(PyObject* obj: gen) { obj->~PyObject(); pool64.dealloc(obj); }
+#if PK_DEBUG_GC_STATS
+        for(auto& [type, count]: deleted){
+            std::cout << "GC: " << obj_type_name(vm, type) << "=" << count << std::endl;
+        }
+#endif
+    }
+
+
+void FuncDecl::_gc_mark() const{
+    code->_gc_mark();
+    for(int i=0; i<kwargs.size(); i++) PK_OBJ_MARK(kwargs[i].value);
+}
+
+}   // namespace pkpy

+ 24 - 0
src/obj.cpp

@@ -0,0 +1,24 @@
+#include "pocketpy/obj.h"
+
+namespace pkpy{
+
+    NativeFunc::NativeFunc(NativeFuncC f, int argc, bool method){
+        this->f = f;
+        this->argc = argc;
+        if(argc != -1) this->argc += (int)method;
+        _has_userdata = false;
+    }
+
+    NativeFunc::NativeFunc(NativeFuncC f, FuncDecl_ decl){
+        this->f = f;
+        this->argc = -1;
+        this->decl = decl;
+        _has_userdata = false;
+    }
+
+    PyObject::~PyObject() {
+        if(_attr == nullptr) return;
+        _attr->~NameDict();
+        pool64.dealloc(_attr);
+    }
+}   // namespace pkpy

+ 269 - 0
src/vm.cpp

@@ -2,6 +2,275 @@
 
 namespace pkpy{
 
+    VM::VM(bool enable_os) : heap(this), enable_os(enable_os) {
+        this->vm = this;
+        _stdout = [](VM* vm, const Str& s) {
+            PK_UNUSED(vm);
+            std::cout << s;
+        };
+        _stderr = [](VM* vm, const Str& s) {
+            PK_UNUSED(vm);
+            std::cerr << s;
+        };
+        callstack.reserve(8);
+        _main = nullptr;
+        _last_exception = nullptr;
+        _import_handler = [](const Str& name) {
+            PK_UNUSED(name);
+            return Bytes();
+        };
+        init_builtin_types();
+    }
+
+    PyObject* VM::py_str(PyObject* obj){
+        const PyTypeInfo* ti = _inst_type_info(obj);
+        if(ti->m__str__) return ti->m__str__(this, obj);
+        PyObject* self;
+        PyObject* f = get_unbound_method(obj, __str__, &self, false);
+        if(self != PY_NULL) return call_method(self, f);
+        return py_repr(obj);
+    }
+
+    PyObject* VM::py_repr(PyObject* obj){
+        const PyTypeInfo* ti = _inst_type_info(obj);
+        if(ti->m__repr__) return ti->m__repr__(this, obj);
+        return call_method(obj, __repr__);
+    }
+
+    PyObject* VM::py_json(PyObject* obj){
+        const PyTypeInfo* ti = _inst_type_info(obj);
+        if(ti->m__json__) return ti->m__json__(this, obj);
+        return call_method(obj, __json__);
+    }
+
+    PyObject* VM::py_iter(PyObject* obj){
+        const PyTypeInfo* ti = _inst_type_info(obj);
+        if(ti->m__iter__) return ti->m__iter__(this, obj);
+        PyObject* self;
+        PyObject* iter_f = get_unbound_method(obj, __iter__, &self, false);
+        if(self != PY_NULL) return call_method(self, iter_f);
+        TypeError(OBJ_NAME(_t(obj)).escape() + " object is not iterable");
+        return nullptr;
+    }
+
+    FrameId VM::top_frame(){
+#if PK_DEBUG_EXTRA_CHECK
+        if(callstack.empty()) FATAL_ERROR();
+#endif
+        return FrameId(&callstack.data(), callstack.size()-1);
+    }
+
+    void VM::_pop_frame(){
+        Frame* frame = &callstack.top();
+        s_data.reset(frame->_sp_base);
+        callstack.pop();
+    }
+
+    PyObject* VM::find_name_in_mro(PyObject* cls, StrName name){
+        PyObject* val;
+        do{
+            val = cls->attr().try_get(name);
+            if(val != nullptr) return val;
+            Type base = _all_types[PK_OBJ_GET(Type, cls)].base;
+            if(base.index == -1) break;
+            cls = _all_types[base].obj;
+        }while(true);
+        return nullptr;
+    }
+
+    bool VM::isinstance(PyObject* obj, Type cls_t){
+        Type obj_t = PK_OBJ_GET(Type, _t(obj));
+        do{
+            if(obj_t == cls_t) return true;
+            Type base = _all_types[obj_t].base;
+            if(base.index == -1) break;
+            obj_t = base;
+        }while(true);
+        return false;
+    }
+
+    PyObject* VM::exec(Str source, Str filename, CompileMode mode, PyObject* _module){
+        if(_module == nullptr) _module = _main;
+        try {
+            CodeObject_ code = compile(source, filename, mode);
+#if PK_DEBUG_DIS_EXEC
+            if(_module == _main) std::cout << disassemble(code) << '\n';
+#endif
+            return _exec(code, _module);
+        }catch (const Exception& e){
+            _stderr(this, e.summary() + "\n");
+        }
+#if !PK_DEBUG_FULL_EXCEPTION
+        catch (const std::exception& e) {
+            Str msg = "An std::exception occurred! It could be a bug.\n";
+            msg = msg + e.what();
+            _stderr(this, msg + "\n");
+        }
+#endif
+        callstack.clear();
+        s_data.clear();
+        return nullptr;
+    }
+
+    PyObject* VM::property(NativeFuncC fget, NativeFuncC fset){
+        PyObject* _0 = heap.gcnew(tp_native_func, NativeFunc(fget, 1, false));
+        PyObject* _1 = vm->None;
+        if(fset != nullptr) _1 = heap.gcnew(tp_native_func, NativeFunc(fset, 2, false));
+        return call(_t(tp_property), _0, _1);
+    }
+
+    PyObject* VM::new_type_object(PyObject* mod, StrName name, Type base, bool subclass_enabled){
+        PyObject* obj = heap._new<Type>(tp_type, _all_types.size());
+        const PyTypeInfo& base_info = _all_types[base];
+        if(!base_info.subclass_enabled){
+            TypeError(fmt("type ", base_info.name.escape(), " is not `subclass_enabled`"));
+        }
+        PyTypeInfo info{
+            obj,
+            base,
+            (mod!=nullptr && mod!=builtins) ? Str(OBJ_NAME(mod)+"."+name.sv()): name.sv(),
+            subclass_enabled,
+        };
+        if(mod != nullptr) mod->attr().set(name, obj);
+        _all_types.push_back(info);
+        return obj;
+    }
+
+    Type VM::_new_type_object(StrName name, Type base) {
+        PyObject* obj = new_type_object(nullptr, name, base, false);
+        return PK_OBJ_GET(Type, obj);
+    }
+
+    PyObject* VM::_find_type_object(const Str& type){
+        PyObject* obj = builtins->attr().try_get(type);
+        if(obj == nullptr){
+            for(auto& t: _all_types) if(t.name == type) return t.obj;
+            throw std::runtime_error(fmt("type not found: ", type));
+        }
+        check_non_tagged_type(obj, tp_type);
+        return obj;
+    }
+
+
+    Type VM::_type(const Str& type){
+        PyObject* obj = _find_type_object(type);
+        return PK_OBJ_GET(Type, obj);
+    }
+
+    PyTypeInfo* VM::_type_info(const Str& type){
+        PyObject* obj = builtins->attr().try_get(type);
+        if(obj == nullptr){
+            for(auto& t: _all_types) if(t.name == type) return &t;
+            FATAL_ERROR();
+        }
+        return &_all_types[PK_OBJ_GET(Type, obj)];
+    }
+
+    PyTypeInfo* VM::_type_info(Type type){
+        return &_all_types[type];
+    }
+
+    const PyTypeInfo* VM::_inst_type_info(PyObject* obj){
+        if(is_int(obj)) return &_all_types[tp_int];
+        if(is_float(obj)) return &_all_types[tp_float];
+        return &_all_types[obj->type];
+    }
+
+    bool VM::py_equals(PyObject* lhs, PyObject* rhs){
+        if(lhs == rhs) return true;
+        const PyTypeInfo* ti = _inst_type_info(lhs);
+        PyObject* res;
+        if(ti->m__eq__){
+            res = ti->m__eq__(this, lhs, rhs);
+            if(res != vm->NotImplemented) return res == vm->True;
+        }
+        res = call_method(lhs, __eq__, rhs);
+        if(res != vm->NotImplemented) return res == vm->True;
+
+        ti = _inst_type_info(rhs);
+        if(ti->m__eq__){
+            res = ti->m__eq__(this, rhs, lhs);
+            if(res != vm->NotImplemented) return res == vm->True;
+        }
+        res = call_method(rhs, __eq__, lhs);
+        if(res != vm->NotImplemented) return res == vm->True;
+        return false;
+    }
+
+
+    int VM::normalized_index(int index, int size){
+        if(index < 0) index += size;
+        if(index < 0 || index >= size){
+            IndexError(std::to_string(index) + " not in [0, " + std::to_string(size) + ")");
+        }
+        return index;
+    }
+
+    PyObject* VM::py_next(PyObject* obj){
+        const PyTypeInfo* ti = _inst_type_info(obj);
+        if(ti->m__next__) return ti->m__next__(this, obj);
+        return call_method(obj, __next__);
+    }
+
+    PyObject* VM::py_import(StrName name, bool relative){
+        Str filename;
+        int type;
+        if(relative){
+            ImportContext* ctx = &_import_context;
+            type = 2;
+            for(auto it=ctx->pending.rbegin(); it!=ctx->pending.rend(); ++it){
+                if(it->second == 2) continue;
+                if(it->second == 1){
+                    filename = fmt(it->first, kPlatformSep, name, ".py");
+                    name = fmt(it->first, '.', name).c_str();
+                    break;
+                }
+            }
+            if(filename.length() == 0) _error("ImportError", "relative import outside of package");
+        }else{
+            type = 0;
+            filename = fmt(name, ".py");
+        }
+        for(auto& [k, v]: _import_context.pending){
+            if(k == name){
+                vm->_error("ImportError", fmt("circular import ", name.escape()));
+            }
+        }
+        PyObject* ext_mod = _modules.try_get(name);
+        if(ext_mod == nullptr){
+            Str source;
+            auto it = _lazy_modules.find(name);
+            if(it == _lazy_modules.end()){
+                Bytes b = _import_handler(filename);
+                if(!relative && !b){
+                    filename = fmt(name, kPlatformSep, "__init__.py");
+                    b = _import_handler(filename);
+                    if(b) type = 1;
+                }
+                if(!b) _error("ImportError", fmt("module ", name.escape(), " not found"));
+                source = Str(b.str());
+            }else{
+                source = it->second;
+                _lazy_modules.erase(it);
+            }
+            auto _ = _import_context.temp(this, name, type);
+            CodeObject_ code = compile(source, filename, EXEC_MODE);
+            PyObject* new_mod = new_module(name);
+            _exec(code, new_mod);
+            new_mod->attr()._try_perfect_rehash();
+            return new_mod;
+        }else{
+            return ext_mod;
+        }
+    }
+
+    VM::~VM() {
+        callstack.clear();
+        s_data.clear();
+        _all_types.clear();
+        _modules.clear();
+        _lazy_modules.clear();
+    }
 
 PyObject* VM::py_negate(PyObject* obj){
     const PyTypeInfo* ti = _inst_type_info(obj);

+ 1 - 1
src2/lib.cpp

@@ -1 +1 @@
-#include "pocketpy/pocketpy.h"
+#include "pocketpy.h"

+ 1 - 1
src2/main.cpp

@@ -1,7 +1,7 @@
 #include <fstream>
 #include <filesystem>
 
-#include "pocketpy/pocketpy.h"
+#include "pocketpy.h"
 
 #ifndef __EMSCRIPTEN__