blueloveTH 1 年間 前
コミット
f3d876c5ef
2 ファイル変更0 行追加2107 行削除
  1. 0 1329
      src/compiler/compiler.cpp
  2. 0 778
      src/compiler/expr.cpp

+ 0 - 1329
src/compiler/compiler.cpp

@@ -1,1329 +0,0 @@
-#include "pocketpy/compiler/compiler.hpp"
-#include "pocketpy/common/config.h"
-#include "pocketpy/compiler/expr.hpp"
-#include "pocketpy/interpreter/vm.hpp"
-#include "pocketpy/objects/codeobject.hpp"
-
-#include <cstdarg>
-
-namespace pkpy {
-
-#define consume(expected) if(!match(expected)) return SyntaxError("expected '%s', got '%s'", pk_TokenSymbols[expected], pk_TokenSymbols[curr().type]);
-#define consume_end_stmt() if(!match_end_stmt()) return SyntaxError("expected statement end")
-#define check_newlines_repl() { bool __nml; match_newlines(&__nml); if(__nml) return NeedMoreLines(); }
-#define check(B) if((err = B)) return err
-
-PrattRule Compiler::rules[TK__COUNT__];
-
-NameScope Compiler::name_scope() const noexcept{
-    auto s = contexts.size() > 1 ? NAME_LOCAL : NAME_GLOBAL;
-    if(unknown_global_scope && s == NAME_GLOBAL) s = NAME_UNKNOWN;
-    return s;
-}
-
-CodeObject* Compiler::push_global_context() noexcept{
-    CodeObject* co = CodeObject__new(lexer.src, py_Str__sv(&lexer.src->filename));
-    co->start_line = __i == 0 ? 1 : prev().line;
-    contexts.push_back(CodeEmitContext(vm, co, contexts.size()));
-    return co;
-}
-
-FuncDecl_ Compiler::push_f_context(c11_sv name, int* out_index) noexcept{
-    FuncDecl_ decl = FuncDecl__rcnew(lexer.src, name);
-    decl->code->start_line = __i == 0 ? 1 : prev().line;
-    decl->nested = name_scope() == NAME_LOCAL;
-    // add_func_decl
-    CodeEmitContext* ctx = &contexts.back();
-    c11_vector__push(FuncDecl_, &ctx->co->func_decls, decl);
-    *out_index = ctx->co->func_decls.count - 1;
-    // push new context
-    contexts.push_back(CodeEmitContext(vm, decl->code, contexts.size()));
-    contexts.back().func = decl;
-    return decl;
-}
-
-Error* Compiler::pop_context() noexcept{
-    assert(ctx()->s_size() == 0);
-    // add a `return None` in the end as a guard
-    // previously, we only do this if the last opcode is not a return
-    // however, this is buggy...since there may be a jump to the end (out of bound) even if the last opcode is a return
-    ctx()->emit_(OP_RETURN_VALUE, 1, BC_KEEPLINE, true);
-    // find the last valid token
-    int j = __i - 1;
-    while(tk(j).type == TK_EOL || tk(j).type == TK_DEDENT || tk(j).type == TK_EOF)
-        j--;
-    ctx()->co->end_line = tk(j).line;
-
-    // some check here
-    auto& codes = ctx()->co->codes;
-    if(ctx()->co->nlocals > PK_MAX_CO_VARNAMES) {
-        return SyntaxError("maximum number of local variables exceeded");
-    }
-    if(ctx()->co->consts.count > 65530) {
-        return SyntaxError("maximum number of constants exceeded");
-    }
-    // pre-compute LOOP_BREAK and LOOP_CONTINUE
-    for(int i = 0; i < codes.count; i++) {
-        Bytecode* bc = c11__at(Bytecode, &codes, i);
-        if(bc->op == OP_LOOP_CONTINUE) {
-            CodeBlock* block = c11__at(CodeBlock, &ctx()->co->blocks, bc->arg);
-            Bytecode__set_signed_arg(bc, block->start - i);
-        } else if(bc->op == OP_LOOP_BREAK) {
-            CodeBlock* block = c11__at(CodeBlock, &ctx()->co->blocks, bc->arg);
-            Bytecode__set_signed_arg(bc, (block->end2 != -1 ? block->end2 : block->end) - i);
-        }
-    }
-    // pre-compute func->is_simple
-    FuncDecl* func = contexts.back().func;
-    if(func) {
-        // check generator
-        c11__foreach(Bytecode, &func->code->codes, bc) {
-            if(bc->op == OP_YIELD_VALUE || bc->op == OP_FOR_ITER_YIELD_VALUE) {
-                func->type = FuncType_GENERATOR;
-                c11__foreach(Bytecode, &func->code->codes, bc) {
-                    if(bc->op == OP_RETURN_VALUE && bc->arg == BC_NOARG) {
-                        return SyntaxError("'return' with argument inside generator function");
-                    }
-                }
-                break;
-            }
-        }
-        if(func->type == FuncType_UNSET) {
-            bool is_simple = true;
-            if(func->kwargs.count > 0) is_simple = false;
-            if(func->starred_arg >= 0) is_simple = false;
-            if(func->starred_kwarg >= 0) is_simple = false;
-
-            if(is_simple) {
-                func->type = FuncType_SIMPLE;
-
-                bool is_empty = false;
-                if(func->code->codes.count == 1) {
-                    Bytecode bc = c11__getitem(Bytecode, &func->code->codes, 0);
-                    if(bc.op == OP_RETURN_VALUE && bc.arg == 1) {
-                        is_empty = true;
-                    }
-                }
-                if(is_empty) func->type = FuncType_EMPTY;
-            } else
-                func->type = FuncType_NORMAL;
-        }
-
-        assert(func->type != FuncType_UNSET);
-    }
-    contexts.back().s_clean();
-    contexts.pop_back();
-    return NULL;
-}
-
-void Compiler::init_pratt_rules() noexcept{
-    static bool initialized = false;
-    if(initialized) return;
-    initialized = true;
-}
-
-bool Compiler::match(TokenIndex expected) noexcept{
-    if(curr().type != expected) return false;
-    advance();
-    return true;
-}
-
-bool Compiler::match_newlines(bool* need_more_lines) noexcept{
-    bool consumed = false;
-    if(curr().type == TK_EOL) {
-        while(curr().type == TK_EOL) advance();
-        consumed = true;
-    }
-    if(need_more_lines) {
-        *need_more_lines = (mode() == REPL_MODE && curr().type == TK_EOF);
-    }
-    return consumed;
-}
-
-bool Compiler::match_end_stmt() noexcept{
-    if(match(TK_SEMICOLON)) {
-        match_newlines();
-        return true;
-    }
-    if(match_newlines() || curr().type == TK_EOF) return true;
-    if(curr().type == TK_DEDENT) return true;
-    return false;
-}
-
-Error* Compiler::EXPR_TUPLE(bool allow_slice) noexcept{
-    Error* err;
-    check(parse_expression(PREC_LOWEST + 1, allow_slice));
-    if(!match(TK_COMMA)) return NULL;
-    // tuple expression
-    int count = 1;
-    do {
-        if(curr().brackets_level) check_newlines_repl()
-        if(!is_expression(allow_slice)) break;
-        check(parse_expression(PREC_LOWEST + 1, allow_slice));
-        count += 1;
-        if(curr().brackets_level) check_newlines_repl();
-    } while(match(TK_COMMA));
-    TupleExpr* e = make_expr<TupleExpr>(count);
-    for(int i=count-1; i>=0; i--)
-        e->items[i] = ctx()->s_popx();
-    ctx()->s_push(e);
-    return NULL;
-}
-
-Error* Compiler::EXPR_VARS() noexcept{
-    int count = 0;
-    do {
-        consume(TK_ID);
-        ctx()->s_push(make_expr<NameExpr>(prev().str(), name_scope()));
-        count += 1;
-    } while(match(TK_COMMA));
-    if(count > 1){
-        TupleExpr* e = make_expr<TupleExpr>(count);
-        for(int i=count-1; i>=0; i--)
-            e->items[i] = ctx()->s_popx();
-        ctx()->s_push(e);
-    }
-    return NULL;
-}
-
-Error* Compiler::exprLiteral() noexcept{
-    ctx()->s_push(make_expr<LiteralExpr>(prev().value));
-    return NULL;
-}
-
-Error* Compiler::exprLong() noexcept{
-    ctx()->s_push(make_expr<LongExpr>(prev().str()));
-    return NULL;
-}
-
-Error* Compiler::exprImag() noexcept{
-    ctx()->s_push(make_expr<ImagExpr>(std::get<f64>(prev().value)));
-    return NULL;
-}
-
-Error* Compiler::exprBytes() noexcept{
-    ctx()->s_push(make_expr<BytesExpr>(std::get<Str>(prev().value)));
-    return NULL;
-}
-
-Error* Compiler::exprFString() noexcept{
-    ctx()->s_push(make_expr<FStringExpr>(std::get<Str>(prev().value)));
-    return NULL;
-}
-
-Error* Compiler::exprLambda() noexcept{
-    Error* err;
-    int decl_index;
-    FuncDecl_ decl = push_f_context({"<lambda>", 8}, &decl_index);
-    int line = prev().line;     // backup line
-    if(!match(TK_COLON)) {
-        check(_compile_f_args(decl, false));
-        consume(TK_COLON);
-    }
-    // https://github.com/pocketpy/pocketpy/issues/37
-    check(parse_expression(PREC_LAMBDA + 1));
-    ctx()->s_emit_top();
-    ctx()->emit_(OP_RETURN_VALUE, BC_NOARG, BC_KEEPLINE);
-    check(pop_context());
-    LambdaExpr* e = make_expr<LambdaExpr>(decl_index);
-    e->line = line;
-    ctx()->s_push(e);
-    return NULL;
-}
-
-Error* Compiler::exprOr() noexcept{
-    int line = prev().line;
-    Error* err;
-    check(parse_expression(PREC_LOGICAL_OR + 1));
-    auto e = make_expr<OrExpr>();
-    e->line = line;
-    e->rhs = ctx()->s_popx();
-    e->lhs = ctx()->s_popx();
-    ctx()->s_push(e);
-    return NULL;
-}
-
-Error* Compiler::exprAnd() noexcept{
-    int line = prev().line;
-    Error* err;
-    check(parse_expression(PREC_LOGICAL_AND + 1));
-    auto e = make_expr<AndExpr>();
-    e->line = line;
-    e->rhs = ctx()->s_popx();
-    e->lhs = ctx()->s_popx();
-    ctx()->s_push(e);
-    return NULL;
-}
-
-Error* Compiler::exprTernary() noexcept{
-    // [true_expr]
-    Error* err;
-    int line = prev().line;
-    check(parse_expression(PREC_TERNARY + 1));  // [true_expr, cond]
-    consume(TK_ELSE);
-    check(parse_expression(PREC_TERNARY + 1));  // [true_expr, cond, false_expr]
-    auto e = make_expr<TernaryExpr>();
-    e->line = line;
-    e->false_expr = ctx()->s_popx();
-    e->cond = ctx()->s_popx();
-    e->true_expr = ctx()->s_popx();
-    ctx()->s_push(e);
-    return NULL;
-}
-
-Error* Compiler::exprBinaryOp() noexcept{
-    Error* err;
-    int line = prev().line;
-    TokenIndex op = prev().type;
-    check(parse_expression(rules[op].precedence + 1));
-    BinaryExpr* e = make_expr<BinaryExpr>(op);
-    e->line = line;
-    e->rhs = ctx()->s_popx();
-    e->lhs = ctx()->s_popx();
-    ctx()->s_push(e);
-    return NULL;
-}
-
-Error* Compiler::exprNot() noexcept{
-    Error* err;
-    check(parse_expression(PREC_LOGICAL_NOT + 1));
-    NotExpr* e = make_expr<NotExpr>(ctx()->s_popx());
-    ctx()->s_push(e);
-    return NULL;
-}
-
-Error* Compiler::exprUnaryOp() noexcept{
-    Error* err;
-    TokenIndex op = prev().type;
-    check(parse_expression(PREC_UNARY + 1));
-    switch(op) {
-        case TK_SUB: ctx()->s_push(make_expr<NegatedExpr>(ctx()->s_popx())); break;
-        case TK_INVERT: ctx()->s_push(make_expr<InvertExpr>(ctx()->s_popx())); break;
-        case TK_MUL: ctx()->s_push(make_expr<StarredExpr>(ctx()->s_popx(), 1)); break;
-        case TK_POW: ctx()->s_push(make_expr<StarredExpr>(ctx()->s_popx(), 2)); break;
-        default: assert(false);
-    }
-    return NULL;
-}
-
-Error* Compiler::exprGroup() noexcept{
-    Error* err;
-    check_newlines_repl()
-    check(EXPR_TUPLE());  // () is just for change precedence
-    check_newlines_repl()
-    consume(TK_RPAREN);
-    if(ctx()->s_top()->is_tuple()) return NULL;
-    Expr* g = make_expr<GroupedExpr>(ctx()->s_popx());
-    ctx()->s_push(g);
-    return NULL;
-}
-
-Error* Compiler::consume_comp(Opcode op0, Opcode op1) noexcept{
-    // [expr]
-    Error* err;
-    bool has_cond = false;
-    check(EXPR_VARS());                         // [expr, vars]
-    consume(TK_IN);
-    check(parse_expression(PREC_TERNARY + 1));  // [expr, vars, iter]
-    check_newlines_repl()
-    if(match(TK_IF)) {
-        check(parse_expression(PREC_TERNARY + 1));  // [expr, vars, iter, cond]
-        has_cond = true;
-    }
-    CompExpr* ce = make_expr<CompExpr>(op0, op1);
-    if(has_cond) ce->cond = ctx()->s_popx();
-    ce->iter = ctx()->s_popx();
-    ce->vars = ctx()->s_popx();
-    ce->expr = ctx()->s_popx();
-    ctx()->s_push(ce);
-    check_newlines_repl()
-    return NULL;
-}
-
-Error* Compiler::exprList() noexcept{
-    Error* err;
-    int line = prev().line;
-    int count = 0;
-    do {
-        check_newlines_repl()
-        if(curr().type == TK_RBRACKET) break;
-        check(EXPR()); count += 1;
-        check_newlines_repl()
-        if(count == 1 && match(TK_FOR)) {
-            check(consume_comp(OP_BUILD_LIST, OP_LIST_APPEND));
-            consume(TK_RBRACKET);
-            return NULL;
-        }
-        check_newlines_repl()
-    } while(match(TK_COMMA));
-    consume(TK_RBRACKET);
-    ListExpr* e = make_expr<ListExpr>(count);
-    e->line = line;  // override line
-    for(int i=count-1; i>=0; i--)
-        e->items[i] = ctx()->s_popx();
-    ctx()->s_push(e);
-    return NULL;
-}
-
-Error* Compiler::exprMap() noexcept{
-    Error* err;
-    bool parsing_dict = false;  // {...} may be dict or set
-    int count = 0;
-    do {
-        check_newlines_repl()
-        if(curr().type == TK_RBRACE) break;
-        check(EXPR());  // [key]
-        int star_level = ctx()->s_top()->star_level();
-        if(star_level == 2 || curr().type == TK_COLON) { parsing_dict = true; }
-        if(parsing_dict) {
-            if(star_level == 2) {
-                DictItemExpr* dict_item = make_expr<DictItemExpr>();
-                dict_item->key = NULL;
-                dict_item->value = ctx()->s_popx();
-                ctx()->s_push(dict_item);
-            } else {
-                consume(TK_COLON);
-                check(EXPR());
-                DictItemExpr* dict_item = make_expr<DictItemExpr>();
-                dict_item->value = ctx()->s_popx();
-                dict_item->key = ctx()->s_popx();
-                ctx()->s_push(dict_item);
-            }
-        }
-        count += 1;
-        check_newlines_repl()
-        if(count == 1 && match(TK_FOR)) {
-            if(parsing_dict){
-                check(consume_comp(OP_BUILD_DICT, OP_DICT_ADD));
-            }else{
-                check(consume_comp(OP_BUILD_SET, OP_SET_ADD));
-            }
-            consume(TK_RBRACE);
-            return NULL;
-        }
-        check_newlines_repl()
-    } while(match(TK_COMMA));
-    consume(TK_RBRACE);
-
-    SequenceExpr* se;
-    if(count == 0 || parsing_dict) {
-        se = make_expr<DictExpr>(count);
-    } else {
-        se = make_expr<SetExpr>(count);
-    }
-    for(int i=count-1; i>=0; i--)
-        se->items[i] = ctx()->s_popx();
-    ctx()->s_push(se);
-    return NULL;
-}
-
-Error* Compiler::exprCall() noexcept{
-    Error* err;
-    CallExpr* e = make_expr<CallExpr>();
-    e->callable = ctx()->s_popx();
-    ctx()->s_push(e);     // push onto the stack in advance
-    do {
-        check_newlines_repl()
-        if(curr().type == TK_RPAREN) break;
-        if(curr().type == TK_ID && next().type == TK_ASSIGN) {
-            consume(TK_ID);
-            StrName key(prev().sv());
-            consume(TK_ASSIGN);
-            check(EXPR());
-            e->kwargs.push_back({key, ctx()->s_popx()});
-        } else {
-            check(EXPR());
-            if(ctx()->s_top()->star_level() == 2) {
-                // **kwargs
-                e->kwargs.push_back({"**", ctx()->s_popx()});
-            } else {
-                // positional argument
-                if(!e->kwargs.empty()) return SyntaxError("positional argument follows keyword argument");
-                e->args.push_back(ctx()->s_popx());
-            }
-        }
-        check_newlines_repl()
-    } while(match(TK_COMMA));
-    consume(TK_RPAREN);
-    return NULL;
-}
-
-Error* Compiler::exprName() noexcept{
-    StrName name(prev().sv());
-    NameScope scope = name_scope();
-    if(ctx()->global_names.contains(name)) { scope = NAME_GLOBAL; }
-    ctx()->s_push(make_expr<NameExpr>(name, scope));
-    return NULL;
-}
-
-Error* Compiler::exprAttrib() noexcept{
-    consume(TK_ID);
-    ctx()->s_push(make_expr<AttribExpr>(ctx()->s_popx(), StrName::get(prev().sv())));
-    return NULL;
-}
-
-Error* Compiler::exprSlice0() noexcept{
-    Error* err;
-    SliceExpr* slice = make_expr<SliceExpr>();
-    ctx()->s_push(slice);     // push onto the stack in advance
-    if(is_expression()) {  // :<stop>
-        check(EXPR());
-        slice->stop = ctx()->s_popx();
-        // try optional step
-        if(match(TK_COLON)) {  // :<stop>:<step>
-            check(EXPR());
-            slice->step = ctx()->s_popx();
-        }
-    } else if(match(TK_COLON)) {
-        if(is_expression()) {  // ::<step>
-            check(EXPR());
-            slice->step = ctx()->s_popx();
-        }  // else ::
-    }  // else :
-    return NULL;
-}
-
-Error* Compiler::exprSlice1() noexcept{
-    Error* err;
-    SliceExpr* slice = make_expr<SliceExpr>();
-    slice->start = ctx()->s_popx();
-    ctx()->s_push(slice);     // push onto the stack in advance
-    if(is_expression()) {  // <start>:<stop>
-        check(EXPR());
-        slice->stop = ctx()->s_popx();
-        // try optional step
-        if(match(TK_COLON)) {  // <start>:<stop>:<step>
-            check(EXPR());
-            slice->step = ctx()->s_popx();
-        }
-    } else if(match(TK_COLON)) {  // <start>::<step>
-        check(EXPR());
-        slice->step = ctx()->s_popx();
-    }  // else <start>:
-    return NULL;
-}
-
-Error* Compiler::exprSubscr() noexcept{
-    Error* err;
-    int line = prev().line;
-    check_newlines_repl()
-    check(EXPR_TUPLE(true));
-    check_newlines_repl()
-    consume(TK_RBRACKET);           // [lhs, rhs]
-    SubscrExpr* e = make_expr<SubscrExpr>();
-    e->line = line;
-    e->rhs = ctx()->s_popx();   // [lhs]
-    e->lhs = ctx()->s_popx();   // []
-    ctx()->s_push(e);
-    return NULL;
-}
-
-Error* Compiler::exprLiteral0() noexcept{
-    ctx()->s_push(make_expr<Literal0Expr>(prev().type));
-    return NULL;
-}
-
-Error* Compiler::compile_block_body(PrattCallback callback) noexcept{
-    Error* err;
-    if(!callback) callback = &Compiler::compile_stmt;
-    consume(TK_COLON);
-    if(curr().type != TK_EOL && curr().type != TK_EOF) {
-        while(true) {
-            check(compile_stmt());
-            bool possible = curr().type != TK_EOL && curr().type != TK_EOF;
-            if(prev().type != TK_SEMICOLON || !possible) break;
-        }
-        return NULL;
-    }
-
-    bool need_more_lines;
-    bool consumed = match_newlines(&need_more_lines);
-    if(need_more_lines) return NeedMoreLines();
-    if(!consumed) return SyntaxError("expected a new line after ':'");
-
-    consume(TK_INDENT);
-    while(curr().type != TK_DEDENT) {
-        match_newlines();
-        check((this->*callback)());
-        match_newlines();
-    }
-    consume(TK_DEDENT);
-    return NULL;
-}
-
-// import a [as b]
-// import a [as b], c [as d]
-Error* Compiler::compile_normal_import() noexcept{
-    do {
-        consume(TK_ID);
-        Str name = prev().str();
-        ctx()->emit_(OP_IMPORT_PATH, ctx()->add_const_string(name.sv()), prev().line);
-        if(match(TK_AS)) {
-            consume(TK_ID);
-            name = prev().str();
-        }
-        ctx()->emit_store_name(name_scope(), StrName(name), prev().line);
-    } while(match(TK_COMMA));
-    consume_end_stmt();
-    return NULL;
-}
-
-// from a import b [as c], d [as e]
-// from a.b import c [as d]
-// from . import a [as b]
-// from .a import b [as c]
-// from ..a import b [as c]
-// from .a.b import c [as d]
-// from xxx import *
-Error* Compiler::compile_from_import() noexcept{
-    int dots = 0;
-
-    while(true) {
-        switch(curr().type) {
-            case TK_DOT: dots += 1; break;
-            case TK_DOTDOT: dots += 2; break;
-            case TK_DOTDOTDOT: dots += 3; break;
-            default: goto __EAT_DOTS_END;
-        }
-        advance();
-    }
-__EAT_DOTS_END:
-    SStream ss;
-    for(int i = 0; i < dots; i++)
-        ss << '.';
-
-    if(dots > 0) {
-        // @id is optional if dots > 0
-        if(match(TK_ID)) {
-            ss << prev().sv();
-            while(match(TK_DOT)) {
-                consume(TK_ID);
-                ss << "." << prev().sv();
-            }
-        }
-    } else {
-        // @id is required if dots == 0
-        consume(TK_ID);
-        ss << prev().sv();
-        while(match(TK_DOT)) {
-            consume(TK_ID);
-            ss << "." << prev().sv();
-        }
-    }
-
-    ctx()->emit_(OP_IMPORT_PATH, ctx()->add_const_string(ss.str().sv()), prev().line);
-    consume(TK_IMPORT);
-
-    if(match(TK_MUL)) {
-        if(name_scope() != NAME_GLOBAL) return SyntaxError("from <module> import * can only be used in global scope");
-        // pop the module and import __all__
-        ctx()->emit_(OP_POP_IMPORT_STAR, BC_NOARG, prev().line);
-        consume_end_stmt();
-        return NULL;
-    }
-
-    do {
-        ctx()->emit_(OP_DUP_TOP, BC_NOARG, BC_KEEPLINE);
-        consume(TK_ID);
-        Str name = prev().str();
-        ctx()->emit_(OP_LOAD_ATTR, StrName(name).index, prev().line);
-        if(match(TK_AS)) {
-            consume(TK_ID);
-            name = prev().str();
-        }
-        ctx()->emit_store_name(name_scope(), StrName(name), prev().line);
-    } while(match(TK_COMMA));
-    ctx()->emit_(OP_POP_TOP, BC_NOARG, BC_KEEPLINE);
-    consume_end_stmt();
-    return NULL;
-}
-
-bool Compiler::is_expression(bool allow_slice) noexcept{
-    PrattCallback prefix = rules[curr().type].prefix;
-    return prefix != nullptr && (allow_slice || curr().type != TK_COLON);
-}
-
-Error* Compiler::parse_expression(int precedence, bool allow_slice) noexcept{
-    PrattCallback prefix = rules[curr().type].prefix;
-    if(prefix == nullptr || (curr().type == TK_COLON && !allow_slice)) {
-        return SyntaxError("expected an expression, got %s", pk_TokenSymbols[curr().type]);
-    }
-    advance();
-    Error* err;
-    check((this->*prefix)());
-    while(rules[curr().type].precedence >= precedence && (allow_slice || curr().type != TK_COLON)) {
-        TokenIndex op = curr().type;
-        advance();
-        PrattCallback infix = rules[op].infix;
-        assert(infix != nullptr);
-        check((this->*infix)());
-    }
-    return NULL;
-}
-
-Error* Compiler::compile_if_stmt() noexcept{
-    Error* err;
-    check(EXPR());  // condition
-    ctx()->s_emit_top();
-    int patch = ctx()->emit_(OP_POP_JUMP_IF_FALSE, BC_NOARG, prev().line);
-    err = compile_block_body();
-    if(err) return err;
-    if(match(TK_ELIF)) {
-        int exit_patch = ctx()->emit_(OP_JUMP_FORWARD, BC_NOARG, prev().line);
-        ctx()->patch_jump(patch);
-        check(compile_if_stmt());
-        ctx()->patch_jump(exit_patch);
-    } else if(match(TK_ELSE)) {
-        int exit_patch = ctx()->emit_(OP_JUMP_FORWARD, BC_NOARG, prev().line);
-        ctx()->patch_jump(patch);
-        check(compile_block_body());
-        ctx()->patch_jump(exit_patch);
-    } else {
-        ctx()->patch_jump(patch);
-    }
-    return NULL;
-}
-
-Error* Compiler::compile_while_loop() noexcept{
-    Error* err;
-    CodeBlock* block = ctx()->enter_block(CodeBlockType_WHILE_LOOP);
-    check(EXPR());  // condition
-    ctx()->s_emit_top();
-    int patch = ctx()->emit_(OP_POP_JUMP_IF_FALSE, BC_NOARG, prev().line);
-    check(compile_block_body());
-    ctx()->emit_(OP_LOOP_CONTINUE, ctx()->get_loop(), BC_KEEPLINE, true);
-    ctx()->patch_jump(patch);
-    ctx()->exit_block();
-    // optional else clause
-    if(match(TK_ELSE)) {
-        check(compile_block_body());
-        block->end2 = ctx()->co->codes.count;
-    }
-    return NULL;
-}
-
-Error* Compiler::compile_for_loop() noexcept{
-    Error* err;
-    check(EXPR_VARS());     // [vars]
-    consume(TK_IN);
-    check(EXPR_TUPLE());    // [vars, iter]
-    ctx()->s_emit_top();     // [vars]
-    ctx()->emit_(OP_GET_ITER_NEW, BC_NOARG, BC_KEEPLINE);
-    CodeBlock* block = ctx()->enter_block(CodeBlockType_FOR_LOOP);
-    int for_codei = ctx()->emit_(OP_FOR_ITER, ctx()->curr_iblock, BC_KEEPLINE);
-    Expr* vars = ctx()->s_popx();
-    bool ok = vars->emit_store(ctx());
-    delete_expr(vars);
-    if(!ok) return SyntaxError();  // this error occurs in `vars` instead of this line, but...nevermind
-    ctx()->try_merge_for_iter_store(for_codei);
-    check(compile_block_body());
-    ctx()->emit_(OP_LOOP_CONTINUE, ctx()->get_loop(), BC_KEEPLINE, true);
-    ctx()->exit_block();
-    // optional else clause
-    if(match(TK_ELSE)) {
-        check(compile_block_body());
-        block->end2 = ctx()->co->codes.count;
-    }
-    return NULL;
-}
-
-Error* Compiler::compile_try_except() noexcept{
-    Error* err;
-    ctx()->enter_block(CodeBlockType_TRY_EXCEPT);
-    ctx()->emit_(OP_TRY_ENTER, BC_NOARG, prev().line);
-    check(compile_block_body());
-    small_vector_2<int, 8> patches;
-    patches.push_back(ctx()->emit_(OP_JUMP_FORWARD, BC_NOARG, BC_KEEPLINE));
-    ctx()->exit_block();
-
-    int finally_entry = -1;
-    if(curr().type != TK_FINALLY) {
-        do {
-            StrName as_name;
-            consume(TK_EXCEPT);
-            if(is_expression()) {
-                check(EXPR());  // push assumed type on to the stack
-                ctx()->s_emit_top();
-                ctx()->emit_(OP_EXCEPTION_MATCH, BC_NOARG, prev().line);
-                if(match(TK_AS)) {
-                    consume(TK_ID);
-                    as_name = StrName(prev().sv());
-                }
-            } else {
-                ctx()->emit_(OP_LOAD_TRUE, BC_NOARG, BC_KEEPLINE);
-            }
-            int patch = ctx()->emit_(OP_POP_JUMP_IF_FALSE, BC_NOARG, BC_KEEPLINE);
-            // on match
-            if(!as_name.empty()) {
-                ctx()->emit_(OP_DUP_TOP, BC_NOARG, BC_KEEPLINE);
-                ctx()->emit_store_name(name_scope(), as_name, BC_KEEPLINE);
-            }
-            // pop the exception
-            ctx()->emit_(OP_POP_EXCEPTION, BC_NOARG, BC_KEEPLINE);
-            check(compile_block_body());
-            patches.push_back(ctx()->emit_(OP_JUMP_FORWARD, BC_NOARG, BC_KEEPLINE));
-            ctx()->patch_jump(patch);
-        } while(curr().type == TK_EXCEPT);
-    }
-
-    if(match(TK_FINALLY)) {
-        int patch = ctx()->emit_(OP_JUMP_FORWARD, BC_NOARG, BC_KEEPLINE);
-        finally_entry = ctx()->co->codes.count;
-        check(compile_block_body());
-        ctx()->emit_(OP_JUMP_ABSOLUTE_TOP, BC_NOARG, BC_KEEPLINE);
-        ctx()->patch_jump(patch);
-    }
-    // no match, re-raise
-    if(finally_entry != -1) {
-        i64 target = ctx()->co->codes.count + 2;
-        ctx()->emit_(OP_LOAD_CONST, ctx()->add_const(VAR(target)), BC_KEEPLINE);
-        int i = ctx()->emit_(OP_JUMP_FORWARD, BC_NOARG, BC_KEEPLINE);
-        Bytecode* bc = c11__at(Bytecode, &ctx()->co->codes, i);
-        Bytecode__set_signed_arg(bc, finally_entry - i);
-    }
-    ctx()->emit_(OP_RE_RAISE, BC_NOARG, BC_KEEPLINE);
-
-    // no exception or no match, jump to the end
-    for(int patch: patches)
-        ctx()->patch_jump(patch);
-    if(finally_entry != -1) {
-        i64 target = ctx()->co->codes.count + 2;
-        ctx()->emit_(OP_LOAD_CONST, ctx()->add_const(VAR(target)), BC_KEEPLINE);
-        int i = ctx()->emit_(OP_JUMP_FORWARD, BC_NOARG, BC_KEEPLINE);
-        Bytecode* bc = c11__at(Bytecode, &ctx()->co->codes, i);
-        Bytecode__set_signed_arg(bc, finally_entry - i);
-    }
-    return NULL;
-}
-
-Error* Compiler::compile_decorated() noexcept{
-    Error* err;
-    int count = 0;
-    do {
-        check(EXPR());
-        count += 1;
-        bool need_more_lines;
-        bool consumed = match_newlines(&need_more_lines);
-        if(need_more_lines) return NeedMoreLines();
-        if(!consumed) return SyntaxError("expected a newline after '@'");
-    } while(match(TK_DECORATOR));
-
-    if(match(TK_CLASS)) {
-        check(compile_class(count));
-    } else {
-        consume(TK_DEF);
-        check(compile_function(count));
-    }
-    return NULL;
-}
-
-Error* Compiler::try_compile_assignment(bool* is_assign) noexcept{
-    Error* err;
-    switch(curr().type) {
-        case TK_IADD:
-        case TK_ISUB:
-        case TK_IMUL:
-        case TK_IDIV:
-        case TK_IFLOORDIV:
-        case TK_IMOD:
-        case TK_ILSHIFT:
-        case TK_IRSHIFT:
-        case TK_IAND:
-        case TK_IOR:
-        case TK_IXOR: {
-            if(ctx()->s_top()->is_starred()) return SyntaxError();
-            if(ctx()->is_compiling_class){
-                return SyntaxError("can't use inplace operator in class definition");
-            }
-            advance();
-            // a[x] += 1;   a and x should be evaluated only once
-            // a.x += 1;    a should be evaluated only once
-            // -1 to remove =; inplace=true
-            int line = prev().line;
-            TokenIndex op = (TokenIndex)(prev().type - 1);
-            // [lhs]
-            check(EXPR_TUPLE());        // [lhs, rhs]
-            if(ctx()->s_top()->is_starred()) return SyntaxError();
-            BinaryExpr* e = make_expr<BinaryExpr>(op, true);
-            e->line = line;
-            e->rhs = ctx()->s_popx();   // [lhs]
-            e->lhs = ctx()->s_popx();   // []
-            e->emit_(ctx());
-            bool ok = e->lhs->emit_store_inplace(ctx());
-            delete_expr(e);
-            if(!ok) return SyntaxError();
-            *is_assign = true;
-            return NULL;
-        }
-        case TK_ASSIGN: {
-            int n = 0;
-            while(match(TK_ASSIGN)) {
-                check(EXPR_TUPLE());
-                n += 1;
-            }
-            // stack size is n+1
-            ctx()->s_emit_top();     // emit and pop
-            for(int j = 1; j < n; j++)
-                ctx()->emit_(OP_DUP_TOP, BC_NOARG, BC_KEEPLINE);
-            for(int j = 0; j < n; j++) {
-                if(ctx()->s_top()->is_starred()) return SyntaxError();
-                bool ok = ctx()->s_top()->emit_store(ctx());
-                ctx()->s_pop();
-                if(!ok) return SyntaxError();
-            }
-            *is_assign = true;
-            return NULL;
-        }
-        default: *is_assign = false;
-    }
-    return NULL;
-}
-
-Error* Compiler::compile_stmt() noexcept{
-    Error* err;
-    if(match(TK_CLASS)) {
-        check(compile_class());
-        return NULL;
-    }
-    advance();
-    int kw_line = prev().line;  // backup line number
-    int curr_loop_block = ctx()->get_loop();
-    switch(prev().type) {
-        case TK_BREAK:
-            if(curr_loop_block < 0) return SyntaxError("'break' outside loop");
-            ctx()->emit_(OP_LOOP_BREAK, curr_loop_block, kw_line);
-            consume_end_stmt();
-            break;
-        case TK_CONTINUE:
-            if(curr_loop_block < 0) return SyntaxError("'continue' not properly in loop");
-            ctx()->emit_(OP_LOOP_CONTINUE, curr_loop_block, kw_line);
-            consume_end_stmt();
-            break;
-        case TK_YIELD:
-            if(contexts.size() <= 1) return SyntaxError("'yield' outside function");
-            check(EXPR_TUPLE());
-            ctx()->s_emit_top();
-            ctx()->emit_(OP_YIELD_VALUE, BC_NOARG, kw_line);
-            consume_end_stmt();
-            break;
-        case TK_YIELD_FROM:
-            if(contexts.size() <= 1) return SyntaxError("'yield from' outside function");
-            check(EXPR_TUPLE());
-            ctx()->s_emit_top();
-
-            ctx()->emit_(OP_GET_ITER_NEW, BC_NOARG, kw_line);
-            ctx()->enter_block(CodeBlockType_FOR_LOOP);
-            ctx()->emit_(OP_FOR_ITER_YIELD_VALUE, BC_NOARG, kw_line);
-            ctx()->emit_(OP_LOOP_CONTINUE, ctx()->get_loop(), kw_line);
-            ctx()->exit_block();
-            consume_end_stmt();
-            break;
-        case TK_RETURN:
-            if(contexts.size() <= 1) return SyntaxError("'return' outside function");
-            if(match_end_stmt()) {
-                ctx()->emit_(OP_RETURN_VALUE, 1, kw_line);
-            } else {
-                check(EXPR_TUPLE());
-                ctx()->s_emit_top();
-                consume_end_stmt();
-                ctx()->emit_(OP_RETURN_VALUE, BC_NOARG, kw_line);
-            }
-            break;
-        /*************************************************/
-        case TK_IF: check(compile_if_stmt()); break;
-        case TK_WHILE: check(compile_while_loop()); break;
-        case TK_FOR: check(compile_for_loop()); break;
-        case TK_IMPORT: check(compile_normal_import()); break;
-        case TK_FROM: check(compile_from_import()); break;
-        case TK_DEF: check(compile_function()); break;
-        case TK_DECORATOR: check(compile_decorated()); break;
-        case TK_TRY: check(compile_try_except()); break;
-        case TK_PASS: consume_end_stmt(); break;
-        /*************************************************/
-        case TK_ASSERT: {
-            check(EXPR());  // condition
-            ctx()->s_emit_top();
-            int index = ctx()->emit_(OP_POP_JUMP_IF_TRUE, BC_NOARG, kw_line);
-            int has_msg = 0;
-            if(match(TK_COMMA)) {
-                check(EXPR());  // message
-                ctx()->s_emit_top();
-                has_msg = 1;
-            }
-            ctx()->emit_(OP_RAISE_ASSERT, has_msg, kw_line);
-            ctx()->patch_jump(index);
-            consume_end_stmt();
-            break;
-        }
-        case TK_GLOBAL:
-            do {
-                consume(TK_ID);
-                ctx()->global_names.push_back(StrName(prev().sv()));
-            } while(match(TK_COMMA));
-            consume_end_stmt();
-            break;
-        case TK_RAISE: {
-            check(EXPR());
-            ctx()->s_emit_top();
-            ctx()->emit_(OP_RAISE, BC_NOARG, kw_line);
-            consume_end_stmt();
-        } break;
-        case TK_DEL: {
-            check(EXPR_TUPLE());
-            if(!ctx()->s_top()->emit_del(ctx())) return SyntaxError();
-            ctx()->s_pop();
-            consume_end_stmt();
-        } break;
-        case TK_WITH: {
-            check(EXPR());  // [ <expr> ]
-            ctx()->s_emit_top();
-            ctx()->enter_block(CodeBlockType_CONTEXT_MANAGER);
-            Expr* as_name = nullptr;
-            if(match(TK_AS)) {
-                consume(TK_ID);
-                as_name = make_expr<NameExpr>(prev().str(), name_scope());
-            }
-            ctx()->emit_(OP_WITH_ENTER, BC_NOARG, prev().line);
-            // [ <expr> <expr>.__enter__() ]
-            if(as_name) {
-                bool ok = as_name->emit_store(ctx());
-                delete_expr(as_name);
-                if(!ok) return SyntaxError();
-            } else {
-                ctx()->emit_(OP_POP_TOP, BC_NOARG, BC_KEEPLINE);
-            }
-            check(compile_block_body());
-            ctx()->emit_(OP_WITH_EXIT, BC_NOARG, prev().line);
-            ctx()->exit_block();
-        } break;
-        /*************************************************/
-        case TK_EQ: {
-            consume(TK_ID);
-            if(mode() != EXEC_MODE) return SyntaxError("'label' is only available in EXEC_MODE");
-            if(!ctx()->add_label(prev().str())) {
-                Str escaped(prev().str().escape());
-                return SyntaxError("label %s already exists", escaped.c_str());
-            }
-            consume(TK_EQ);
-            consume_end_stmt();
-        } break;
-        case TK_ARROW:
-            consume(TK_ID);
-            if(mode() != EXEC_MODE) return SyntaxError("'goto' is only available in EXEC_MODE");
-            ctx()->emit_(OP_GOTO, StrName(prev().sv()).index, prev().line);
-            consume_end_stmt();
-            break;
-        /*************************************************/
-        // handle dangling expression or assignment
-        default: {
-            advance(-1);  // do revert since we have pre-called advance() at the beginning
-            check(EXPR_TUPLE());
-
-            bool is_typed_name = false;  // e.g. x: int
-            // eat variable's type hint if it is a single name
-            if(ctx()->s_top()->is_name()) {
-                if(match(TK_COLON)) {
-                    check(consume_type_hints());
-                    is_typed_name = true;
-
-                    if(ctx()->is_compiling_class) {
-                        NameExpr* ne = static_cast<NameExpr*>(ctx()->s_top());
-                        ctx()->emit_(OP_ADD_CLASS_ANNOTATION, ne->name.index, BC_KEEPLINE);
-                    }
-                }
-            }
-            bool is_assign = false;
-            check(try_compile_assignment(&is_assign));
-            if(!is_assign) {
-                if(ctx()->s_size() > 0 && ctx()->s_top()->is_starred()) { 
-                    return SyntaxError();
-                }
-                if(!is_typed_name) {
-                    ctx()->s_emit_top();
-                    if((mode() == CELL_MODE || mode() == REPL_MODE) && name_scope() == NAME_GLOBAL) {
-                        ctx()->emit_(OP_PRINT_EXPR, BC_NOARG, BC_KEEPLINE);
-                    } else {
-                        ctx()->emit_(OP_POP_TOP, BC_NOARG, BC_KEEPLINE);
-                    }
-                } else {
-                    ctx()->s_pop();
-                }
-            }
-            consume_end_stmt();
-            break;
-        }
-    }
-    return NULL;
-}
-
-Error* Compiler::consume_type_hints() noexcept{
-    Error* err;
-    check(EXPR());
-    ctx()->s_pop();
-    return NULL;
-}
-
-Error* Compiler::compile_class(int decorators) noexcept{
-    Error* err;
-    consume(TK_ID);
-    int namei = StrName(prev().sv()).index;
-    bool has_base = false;
-    if(match(TK_LPAREN)) {
-        if(is_expression()) {
-            check(EXPR());
-            has_base = true;    // [base]
-        }
-        consume(TK_RPAREN);
-    }
-    if(!has_base) {
-        ctx()->emit_(OP_LOAD_NONE, BC_NOARG, prev().line);
-    } else {
-        ctx()->s_emit_top();    // []
-    }
-    ctx()->emit_(OP_BEGIN_CLASS, namei, BC_KEEPLINE);
-
-    for(auto& c: this->contexts) {
-        if(c.is_compiling_class) return SyntaxError("nested class is not allowed");
-    }
-    ctx()->is_compiling_class = true;
-    check(compile_block_body());
-    ctx()->is_compiling_class = false;
-
-    if(decorators > 0) {
-        ctx()->emit_(OP_BEGIN_CLASS_DECORATION, BC_NOARG, BC_KEEPLINE);
-        ctx()->s_emit_decorators(decorators);
-        ctx()->emit_(OP_END_CLASS_DECORATION, BC_NOARG, BC_KEEPLINE);
-    }
-
-    ctx()->emit_(OP_END_CLASS, namei, BC_KEEPLINE);
-    return NULL;
-}
-
-Error* Compiler::_compile_f_args(FuncDecl* decl, bool enable_type_hints) noexcept{
-    int state = 0;  // 0 for args, 1 for *args, 2 for k=v, 3 for **kwargs
-    Error* err;
-    do {
-        if(state > 3) return SyntaxError();
-        if(state == 3) return SyntaxError("**kwargs should be the last argument");
-        match_newlines();
-        if(match(TK_MUL)) {
-            if(state < 1)
-                state = 1;
-            else
-                return SyntaxError("*args should be placed before **kwargs");
-        } else if(match(TK_POW)) {
-            state = 3;
-        }
-        consume(TK_ID);
-        StrName name(prev().sv());
-
-        // check duplicate argument name
-        uint16_t tmp_name;
-        c11__foreach(int, &decl->args, j) {
-            tmp_name = c11__getitem(uint16_t, &decl->args, *j);
-            if(tmp_name == name.index) return SyntaxError("duplicate argument name");
-        }
-        c11__foreach(FuncDeclKwArg, &decl->kwargs, kv) {
-            tmp_name = c11__getitem(uint16_t, &decl->code->varnames, kv->index);
-            if(tmp_name == name.index) return SyntaxError("duplicate argument name");
-        }
-        
-        if(decl->starred_arg != -1) {
-            tmp_name = c11__getitem(uint16_t, &decl->code->varnames, decl->starred_arg);
-            if(tmp_name == name.index) return SyntaxError("duplicate argument name");
-        }
-        if(decl->starred_kwarg != -1) {
-            tmp_name = c11__getitem(uint16_t, &decl->code->varnames, decl->starred_kwarg);
-            if(tmp_name == name.index) return SyntaxError("duplicate argument name");
-        }
-
-        // eat type hints
-        if(enable_type_hints && match(TK_COLON)) check(consume_type_hints());
-        if(state == 0 && curr().type == TK_ASSIGN) state = 2;
-        int index = ctx()->add_varname(name);
-        switch(state) {
-            case 0:
-                c11_vector__push(int, &decl->args, index);
-                break;
-            case 1:
-                decl->starred_arg = index;
-                state += 1;
-                break;
-            case 2: {
-                consume(TK_ASSIGN);
-                PyVar value;
-                check(read_literal(&value));
-                if(value == nullptr) return SyntaxError("default argument must be a literal");
-                FuncDecl__add_kwarg(decl, index, name.index, (const ::PyVar*)&value);
-            } break;
-            case 3:
-                decl->starred_kwarg = index;
-                state += 1;
-                break;
-        }
-    } while(match(TK_COMMA));
-    return NULL;
-}
-
-Error* Compiler::compile_function(int decorators) noexcept{
-    Error* err;
-    consume(TK_ID);
-    std::string_view decl_name = prev().sv();
-    int decl_index;
-    FuncDecl_ decl = push_f_context({decl_name.data(), (int)decl_name.size()}, &decl_index);
-    consume(TK_LPAREN);
-    if(!match(TK_RPAREN)) {
-        check(_compile_f_args(decl, true));
-        consume(TK_RPAREN);
-    }
-    if(match(TK_ARROW)) check(consume_type_hints());
-    check(compile_block_body());
-    check(pop_context());
-
-    if(decl->code->codes.count >= 2) {
-        Bytecode* codes = (Bytecode*)decl->code->codes.data;
-        if(codes[0].op == OP_LOAD_CONST && codes[1].op == OP_POP_TOP) {
-            // handle optional docstring
-            PyVar* c = c11__at(PyVar, &decl->code->consts, codes[0].arg);
-            if(is_type(*c, vm->tp_str)) {
-                codes[0].op = OP_NO_OP;
-                codes[1].op = OP_NO_OP;
-                decl->docstring = PK_OBJ_GET(Str, *c).c_str();
-            }
-        }
-    }
-    ctx()->emit_(OP_LOAD_FUNCTION, decl_index, prev().line);
-
-    ctx()->s_emit_decorators(decorators);
-
-    if(!ctx()->is_compiling_class) {
-        NameExpr* e = make_expr<NameExpr>(StrName(decl_name), name_scope());
-        e->emit_store(ctx());
-        delete_expr(e);
-    } else {
-        int index = StrName(decl_name).index;
-        ctx()->emit_(OP_STORE_CLASS_ATTR, index, prev().line);
-    }
-    return NULL;
-}
-
-PyVar Compiler::to_object(const TokenValue& value) noexcept{
-    PyVar obj = nullptr;
-    if(std::holds_alternative<i64>(value)) { obj = VAR(std::get<i64>(value)); }
-    if(std::holds_alternative<f64>(value)) { obj = VAR(std::get<f64>(value)); }
-    if(std::holds_alternative<Str>(value)) { obj = VAR(std::get<Str>(value)); }
-    assert(obj != nullptr);
-    return obj;
-}
-
-Error* Compiler::read_literal(PyVar* out) noexcept{
-    Error* err;
-    advance();
-    switch(prev().type) {
-        case TK_SUB: {
-            consume(TK_NUM);
-            PyVar val = to_object(prev().value);
-            *out = vm->py_negate(val);
-            return NULL;
-        }
-        case TK_NUM: *out = to_object(prev().value); return NULL;
-        case TK_STR: *out = to_object(prev().value); return NULL;
-        case TK_TRUE: *out = VAR(true);  return NULL;
-        case TK_FALSE: *out = VAR(false); return NULL;
-        case TK_NONE: *out = vm->None; return NULL;
-        case TK_DOTDOTDOT: *out = vm->Ellipsis; return NULL;
-        case TK_LPAREN: {
-            List cpnts;
-            while(true) {
-                PyVar elem;
-                check(read_literal(&elem));
-                cpnts.push_back(elem);
-                if(curr().type == TK_RPAREN) break;
-                consume(TK_COMMA);
-                if(curr().type == TK_RPAREN) break;
-            }
-            consume(TK_RPAREN);
-            *out = VAR(cpnts.to_tuple());
-            return NULL;
-        }
-        default: *out = nullptr; return NULL;
-    }
-}
-
-Compiler::Compiler(VM* vm, std::string_view source, const Str& filename, CompileMode mode, bool unknown_global_scope) noexcept:
-    lexer(vm, source, filename, mode){
-    this->vm = vm;
-    this->unknown_global_scope = unknown_global_scope;
-    init_pratt_rules();
-}
-
-Error* Compiler::compile(CodeObject** out) noexcept{
-    assert(__i == 0);  // make sure it is the first time to compile
-
-    Error* err;
-    check(lexer.run());
-
-    // if(lexer.src.filename()[0] != '<'){
-    //     printf("%s\n", lexer.src.filename().c_str());
-    //     for(int i=0; i<lexer.nexts.size(); i++){
-    //         printf("%s: %s\n", pk_TokenSymbols[tk(i).type], tk(i).str().escape().c_str());
-    //     }
-    // }
-
-    CodeObject* code = push_global_context();
-
-    assert(curr().type == TK_SOF);
-    advance();         // skip @sof, so prev() is always valid
-    match_newlines();  // skip possible leading '\n'
-
-    if(mode() == EVAL_MODE) {
-        check(EXPR_TUPLE());
-        ctx()->s_emit_top();
-        consume(TK_EOF);
-        ctx()->emit_(OP_RETURN_VALUE, BC_NOARG, BC_KEEPLINE);
-        check(pop_context());
-        *out = code;
-        return NULL;
-    } else if(mode() == JSON_MODE) {
-        check(EXPR());
-        Expr* e = ctx()->s_popx();
-        if(!e->is_json_object()) return SyntaxError("expect a JSON object, literal or array");
-        consume(TK_EOF);
-        e->emit_(ctx());
-        ctx()->emit_(OP_RETURN_VALUE, BC_NOARG, BC_KEEPLINE);
-        check(pop_context());
-        *out = code;
-        return NULL;
-    }
-
-    while(!match(TK_EOF)) {
-        check(compile_stmt());
-        match_newlines();
-    }
-    check(pop_context());
-    *out = code;
-    return NULL;
-}
-
-Compiler::~Compiler(){
-    for(CodeEmitContext& ctx: contexts){
-        ctx.s_clean();
-    }
-}
-
-Error* Compiler::SyntaxError(const char* msg, ...) noexcept{
-    va_list args;
-    va_start(args, msg);
-    Error* e = lexer._error(false, "SyntaxError", msg, &args);
-    e->lineno = err().line;
-    e->cursor = err().start;
-    va_end(args);
-    return e;
-}
-
-#undef consume
-#undef consume_end_stmt
-#undef check
-#undef check_newlines_repl
-
-}  // namespace pkpy

+ 0 - 778
src/compiler/expr.cpp

@@ -1,778 +0,0 @@
-#include "pocketpy/compiler/expr.hpp"
-#include "pocketpy/interpreter/vm.hpp"
-#include "pocketpy/objects/codeobject.h"
-#include "pocketpy/objects/public.h"
-
-namespace pkpy {
-
-inline bool is_identifier(std::string_view s) {
-    if(s.empty()) return false;
-    if(!isalpha(s[0]) && s[0] != '_') return false;
-    for(char c: s)
-        if(!isalnum(c) && c != '_') return false;
-    return true;
-}
-
-inline bool is_small_int(i64 value) { return value >= INT16_MIN && value <= INT16_MAX; }
-
-int CodeEmitContext::get_loop() const noexcept{
-    int index = curr_iblock;
-    while(index >= 0) {
-        CodeBlock* block = c11__at(CodeBlock, &co->blocks, index);
-        if(block->type == CodeBlockType_FOR_LOOP) break;
-        if(block->type == CodeBlockType_WHILE_LOOP) break;
-        index = block->parent;
-    }
-    return index;
-}
-
-CodeBlock* CodeEmitContext::enter_block(CodeBlockType type) noexcept{
-    CodeBlock block = {type, curr_iblock, co->codes.count, -1, -1};
-    c11_vector__push(CodeBlock, &co->blocks, block);
-    curr_iblock = co->blocks.count - 1;
-    return c11__at(CodeBlock, &co->blocks, curr_iblock);
-}
-
-void CodeEmitContext::exit_block() noexcept{
-    CodeBlock* block = c11__at(CodeBlock, &co->blocks, curr_iblock);
-    CodeBlockType curr_type = block->type;
-    block->end = co->codes.count;
-    curr_iblock = block->parent;
-    assert(curr_iblock >= 0);
-    if(curr_type == CodeBlockType_FOR_LOOP) {
-        // add a no op here to make block check work
-        emit_(OP_NO_OP, BC_NOARG, BC_KEEPLINE, true);
-    }
-}
-
-void CodeEmitContext::s_emit_decorators(int count) noexcept{
-    assert(s_size() >= count);
-    // [obj]
-    for(int i=0; i<count; i++) {
-        Expr* deco = s_popx();
-        deco->emit_(this);                           // [obj, f]
-        emit_(OP_ROT_TWO, BC_NOARG, deco->line);     // [f, obj]
-        emit_(OP_LOAD_NULL, BC_NOARG, BC_KEEPLINE);  // [f, obj, NULL]
-        emit_(OP_ROT_TWO, BC_NOARG, BC_KEEPLINE);    // [obj, NULL, f]
-        emit_(OP_CALL, 1, deco->line);               // [obj]
-        delete_expr(deco);
-    }
-}
-
-int CodeEmitContext::emit_(Opcode opcode, uint16_t arg, int line, bool is_virtual) noexcept{
-    c11_vector__push(Bytecode, &co->codes, (Bytecode{(uint8_t)opcode, arg}));
-    c11_vector__push(BytecodeEx, &co->codes_ex, (BytecodeEx{line, is_virtual, curr_iblock}));
-    int i = co->codes.count - 1;
-    BytecodeEx* codes_ex = (BytecodeEx*)co->codes_ex.data;
-    if(line == BC_KEEPLINE) {
-        codes_ex[i].lineno = i>=1 ? codes_ex[i-1].lineno : 1;
-    }
-    return i;
-}
-
-void CodeEmitContext::revert_last_emit_() noexcept{
-    c11_vector__pop(&co->codes);
-    c11_vector__pop(&co->codes_ex);
-}
-
-void CodeEmitContext::try_merge_for_iter_store(int i) noexcept{
-    // [FOR_ITER, STORE_?, ]
-    Bytecode* co_codes = (Bytecode*)co->codes.data;
-    if(co_codes[i].op != OP_FOR_ITER) return;
-    if(co->codes.count - i != 2) return;
-    uint16_t arg = co_codes[i + 1].arg;
-    if(co_codes[i + 1].op == OP_STORE_FAST) {
-        revert_last_emit_();
-        co_codes[i].op = OP_FOR_ITER_STORE_FAST;
-        co_codes[i].arg = arg;
-        return;
-    }
-    if(co_codes[i + 1].op == OP_STORE_GLOBAL) {
-        revert_last_emit_();
-        co_codes[i].op = OP_FOR_ITER_STORE_GLOBAL;
-        co_codes[i].arg = arg;
-        return;
-    }
-}
-
-int CodeEmitContext::emit_int(i64 value, int line) noexcept{
-    if(is_small_int(value)) {
-        return emit_(OP_LOAD_SMALL_INT, (uint16_t)value, line);
-    } else {
-        return emit_(OP_LOAD_CONST, add_const(VAR(value)), line);
-    }
-}
-
-void CodeEmitContext::patch_jump(int index) noexcept{
-    Bytecode* co_codes = (Bytecode*)co->codes.data;
-    int target = co->codes.count;
-    Bytecode__set_signed_arg(&co_codes[index], target - index);
-}
-
-bool CodeEmitContext::add_label(StrName name) noexcept{
-    bool ok = c11_smallmap_n2i__contains(&co->labels, name.index);
-    if(ok) return false;
-    c11_smallmap_n2i__set(&co->labels, name.index, co->codes.count);
-    return true;
-}
-
-int CodeEmitContext::add_varname(StrName name) noexcept{
-    // PK_MAX_CO_VARNAMES will be checked when pop_context(), not here
-    int index = c11_smallmap_n2i__get(&co->varnames_inv, name.index, -1);
-    if(index >= 0) return index;
-    c11_vector__push(uint16_t, &co->varnames, name.index);
-    co->nlocals++;
-    index = co->varnames.count - 1;
-    c11_smallmap_n2i__set(&co->varnames_inv, name.index, index);
-    return index;
-}
-
-int CodeEmitContext::add_const_string(std::string_view key) noexcept{
-    uint16_t* val = c11_smallmap_s2n__try_get(&_co_consts_string_dedup_map, {key.data(), (int)key.size()});
-    if(val) {
-        return *val;
-    } else {
-        // co->consts.push_back(VAR(key));
-        c11_vector__push(PyVar, &co->consts, VAR(key));
-        int index = co->consts.count - 1;
-        key = c11__getitem(PyVar, &co->consts, index).obj_get<Str>().sv();
-        c11_smallmap_s2n__set(&_co_consts_string_dedup_map, {key.data(), (int)key.size()}, index);
-        return index;
-    }
-}
-
-int CodeEmitContext::add_const(PyVar v) noexcept{
-    assert(!is_type(v, VM::tp_str));
-    c11_vector__push(PyVar, &co->consts, v);
-    return co->consts.count - 1;
-}
-
-void CodeEmitContext::emit_store_name(NameScope scope, StrName name, int line) noexcept{
-    switch(scope) {
-        case NAME_LOCAL: emit_(OP_STORE_FAST, add_varname(name), line); break;
-        case NAME_GLOBAL: emit_(OP_STORE_GLOBAL, StrName(name).index, line); break;
-        case NAME_GLOBAL_UNKNOWN: emit_(OP_STORE_NAME, StrName(name).index, line); break;
-        default: assert(false); break;
-    }
-}
-
-void NameExpr::emit_(CodeEmitContext* ctx) {
-    int index = c11_smallmap_n2i__get(&ctx->co->varnames_inv, name.index, -1);
-    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;
-        if(ctx->is_compiling_class && scope == NAME_GLOBAL) {
-            // if we are compiling a class, we should use OP_LOAD_ATTR_GLOBAL instead of OP_LOAD_GLOBAL
-            // this supports @property.setter
-            op = OP_LOAD_CLASS_GLOBAL;
-            // exec()/eval() won't work with OP_LOAD_ATTR_GLOBAL in class body
-        } else {
-            // 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: assert(false); break;
-    }
-    return true;
-}
-
-bool NameExpr::emit_store(CodeEmitContext* ctx) {
-    if(ctx->is_compiling_class) {
-        ctx->emit_(OP_STORE_CLASS_ATTR, name.index, line);
-        return true;
-    }
-    ctx->emit_store_name(scope, name, line);
-    return true;
-}
-
-void InvertExpr::emit_(CodeEmitContext* ctx) {
-    child->emit_(ctx);
-    ctx->emit_(OP_UNARY_INVERT, BC_NOARG, line);
-}
-
-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_DOTDOTDOT: ctx->emit_(OP_LOAD_ELLIPSIS, BC_NOARG, line); break;
-        default: assert(false);
-    }
-}
-
-void LongExpr::emit_(CodeEmitContext* ctx) {
-    ctx->emit_(OP_LOAD_CONST, ctx->add_const_string(s.sv()), line);
-    ctx->emit_(OP_BUILD_LONG, BC_NOARG, line);
-}
-
-void ImagExpr::emit_(CodeEmitContext* ctx) {
-    VM* vm = ctx->vm;
-    ctx->emit_(OP_LOAD_CONST, ctx->add_const(VAR(value)), line);
-    ctx->emit_(OP_BUILD_IMAG, BC_NOARG, line);
-}
-
-void BytesExpr::emit_(CodeEmitContext* ctx) {
-    ctx->emit_(OP_LOAD_CONST, ctx->add_const_string(s.sv()), line);
-    ctx->emit_(OP_BUILD_BYTES, BC_NOARG, line);
-}
-
-void LiteralExpr::emit_(CodeEmitContext* ctx) {
-    VM* vm = ctx->vm;
-    if(std::holds_alternative<i64>(value)) {
-        i64 _val = std::get<i64>(value);
-        ctx->emit_int(_val, line);
-        return;
-    }
-    if(std::holds_alternative<f64>(value)) {
-        f64 _val = std::get<f64>(value);
-        ctx->emit_(OP_LOAD_CONST, ctx->add_const(VAR(_val)), line);
-        return;
-    }
-    if(std::holds_alternative<Str>(value)) {
-        std::string_view key = std::get<Str>(value).sv();
-        ctx->emit_(OP_LOAD_CONST, ctx->add_const_string(key), line);
-        return;
-    }
-}
-
-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);
-        if(std::holds_alternative<i64>(lit->value)) {
-            i64 _val = -std::get<i64>(lit->value);
-            ctx->emit_int(_val, line);
-            return;
-        }
-        if(std::holds_alternative<f64>(lit->value)) {
-            f64 _val = -std::get<f64>(lit->value);
-            ctx->emit_(OP_LOAD_CONST, ctx->add_const(VAR(_val)), 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()) {
-        assert(key == nullptr);
-        value->emit_(ctx);
-    } else {
-        key->emit_(ctx);
-        value->emit_(ctx);
-        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 = c11__at(Bytecode, &ctx->co->codes, ctx->co->codes.count - 1);
-        if(prev->op == OP_BUILD_TUPLE && prev->arg == items.size()) {
-            // build tuple and unpack it is meaningless
-            ctx->revert_last_emit_();
-        } else {
-            if(prev->op == OP_FOR_ITER) {
-                prev->op = OP_FOR_ITER_UNPACK;
-                prev->arg = items.size();
-            } 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(CodeBlockType_FOR_LOOP);
-    int curr_iblock = ctx->curr_iblock;
-    int for_codei = ctx->emit_(OP_FOR_ITER, curr_iblock, BC_KEEPLINE);
-    bool ok = vars->emit_store(ctx);
-    // this error occurs in `vars` instead of this line, but...nevermind
-    assert(ok);  // this should raise a SyntaxError, but we just assert it
-    ctx->try_merge_for_iter_store(for_codei);
-    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, curr_iblock, BC_KEEPLINE);
-    ctx->exit_block();
-}
-
-void FStringExpr::_load_simple_expr(CodeEmitContext* ctx, Str expr) {
-    bool repr = false;
-    if(expr.size >= 2 && expr.end()[-2] == '!') {
-        switch(expr.end()[-1]) {
-            case 'r':
-                repr = true;
-                expr = expr.slice(0, expr.size - 2);
-                break;
-            case 's':
-                repr = false;
-                expr = expr.slice(0, expr.size - 2);
-                break;
-            default: break;  // nothing happens
-        }
-    }
-    // name or name.name
-    bool is_fastpath = false;
-    if(is_identifier(expr.sv())) {
-        ctx->emit_(OP_LOAD_NAME, StrName(expr.sv()).index, line);
-        is_fastpath = true;
-    } else {
-        int dot = expr.index(".");
-        if(dot > 0) {
-            std::string_view a = expr.sv().substr(0, dot);
-            std::string_view b = expr.sv().substr(dot + 1);
-            if(is_identifier(a) && is_identifier(b)) {
-                ctx->emit_(OP_LOAD_NAME, StrName(a).index, line);
-                ctx->emit_(OP_LOAD_ATTR, StrName(b).index, line);
-                is_fastpath = true;
-            }
-        }
-    }
-
-    if(!is_fastpath) {
-        int index = ctx->add_const_string(expr.sv());
-        ctx->emit_(OP_FSTRING_EVAL, index, line);
-    }
-
-    if(repr) { ctx->emit_(OP_REPR, BC_NOARG, line); }
-}
-
-static bool is_fmt_valid_char(char c) {
-    switch(c) {
-            // clang-format off
-            case '-': case '=': case '*': case '#': case '@': case '!': case '~':
-            case '<': case '>': case '^':
-            case '.': case 'f': case 'd': case 's':
-            case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
-            return true;
-            default: return false;
-            // clang-format on
-    }
-}
-
-void FStringExpr::emit_(CodeEmitContext* ctx) {
-    int i = 0;          // left index
-    int j = 0;          // right index
-    int count = 0;      // how many string parts
-    bool flag = false;  // true if we are in a expression
-
-    while(j < src.size) {
-        if(flag) {
-            if(src[j] == '}') {
-                // add expression
-                Str expr = src.slice(i, j);
-                // BUG: ':' is not a format specifier in f"{stack[2:]}"
-                int conon = expr.index(":");
-                if(conon >= 0) {
-                    Str spec = expr.substr(conon + 1);
-                    // filter some invalid spec
-                    bool ok = true;
-                    for(char c: spec)
-                        if(!is_fmt_valid_char(c)) {
-                            ok = false;
-                            break;
-                        }
-                    if(ok) {
-                        _load_simple_expr(ctx, expr.slice(0, conon));
-                        ctx->emit_(OP_FORMAT_STRING, ctx->add_const_string(spec.sv()), line);
-                    } else {
-                        // ':' is not a spec indicator
-                        _load_simple_expr(ctx, expr);
-                    }
-                } else {
-                    _load_simple_expr(ctx, expr);
-                }
-                flag = false;
-                count++;
-            }
-        } else {
-            if(src[j] == '{') {
-                // look at next char
-                if(j + 1 < src.size && src[j + 1] == '{') {
-                    // {{ -> {
-                    j++;
-                    ctx->emit_(OP_LOAD_CONST, ctx->add_const_string("{"), line);
-                    count++;
-                } else {
-                    // { -> }
-                    flag = true;
-                    i = j + 1;
-                }
-            } else if(src[j] == '}') {
-                // look at next char
-                if(j + 1 < src.size && src[j + 1] == '}') {
-                    // }} -> }
-                    j++;
-                    ctx->emit_(OP_LOAD_CONST, ctx->add_const_string("}"), line);
-                    count++;
-                } else {
-                    // } -> error
-                    // throw std::runtime_error("f-string: unexpected }");
-                    // just ignore
-                }
-            } else {
-                // literal
-                i = j;
-                while(j < src.size && src[j] != '{' && src[j] != '}')
-                    j++;
-                Str literal = src.slice(i, j);
-                ctx->emit_(OP_LOAD_CONST, ctx->add_const_string(literal.sv()), line);
-                count++;
-                continue;  // skip j++
-            }
-        }
-        j++;
-    }
-
-    if(flag) {
-        // literal
-        Str literal = src.slice(i, src.size);
-        ctx->emit_(OP_LOAD_CONST, ctx->add_const_string(literal.sv()), line);
-        count++;
-    }
-
-    ctx->emit_(OP_BUILD_STRING, count, line);
-}
-
-void SubscrExpr::emit_(CodeEmitContext* ctx) {
-    lhs->emit_(ctx);
-    rhs->emit_(ctx);
-    Bytecode last_bc = c11__getitem(Bytecode, &ctx->co->codes, ctx->co->codes.count-1);
-    if(rhs->is_name() && last_bc.op == OP_LOAD_FAST) {
-        ctx->revert_last_emit_();
-        ctx->emit_(OP_LOAD_SUBSCR_FAST, last_bc.arg, line);
-    } else if(rhs->is_literal() && last_bc.op == OP_LOAD_SMALL_INT) {
-        ctx->revert_last_emit_();
-        ctx->emit_(OP_LOAD_SUBSCR_SMALL_INT, last_bc.arg, line);
-    } else {
-        ctx->emit_(OP_LOAD_SUBSCR, BC_NOARG, line);
-    }
-}
-
-bool SubscrExpr::emit_store(CodeEmitContext* ctx) {
-    lhs->emit_(ctx);
-    rhs->emit_(ctx);
-    Bytecode last_bc = c11__getitem(Bytecode, &ctx->co->codes, ctx->co->codes.count-1);
-    if(rhs->is_name() && last_bc.op == OP_LOAD_FAST) {
-        ctx->revert_last_emit_();
-        ctx->emit_(OP_STORE_SUBSCR_FAST, last_bc.arg, line);
-    } else {
-        ctx->emit_(OP_STORE_SUBSCR, BC_NOARG, line);
-    }
-    return true;
-}
-
-void SubscrExpr::emit_inplace(CodeEmitContext* ctx) {
-    lhs->emit_(ctx);
-    rhs->emit_(ctx);
-    ctx->emit_(OP_DUP_TOP_TWO, BC_NOARG, line);
-    ctx->emit_(OP_LOAD_SUBSCR, BC_NOARG, line);
-}
-
-bool SubscrExpr::emit_store_inplace(CodeEmitContext* ctx) {
-    // [a, b, val] -> [val, a, b]
-    ctx->emit_(OP_ROT_THREE, BC_NOARG, line);
-    ctx->emit_(OP_STORE_SUBSCR, BC_NOARG, line);
-    return true;
-}
-
-bool SubscrExpr::emit_del(CodeEmitContext* ctx) {
-    lhs->emit_(ctx);
-    rhs->emit_(ctx);
-    ctx->emit_(OP_DELETE_SUBSCR, BC_NOARG, line);
-    return true;
-}
-
-void AttribExpr::emit_(CodeEmitContext* ctx) {
-    child->emit_(ctx);
-    ctx->emit_(OP_LOAD_ATTR, name.index, line);
-}
-
-bool AttribExpr::emit_del(CodeEmitContext* ctx) {
-    child->emit_(ctx);
-    ctx->emit_(OP_DELETE_ATTR, name.index, line);
-    return true;
-}
-
-bool AttribExpr::emit_store(CodeEmitContext* ctx) {
-    child->emit_(ctx);
-    ctx->emit_(OP_STORE_ATTR, name.index, line);
-    return true;
-}
-
-void AttribExpr::emit_method(CodeEmitContext* ctx) {
-    child->emit_(ctx);
-    ctx->emit_(OP_LOAD_METHOD, name.index, line);
-}
-
-void AttribExpr::emit_inplace(CodeEmitContext* ctx) {
-    child->emit_(ctx);
-    ctx->emit_(OP_DUP_TOP, BC_NOARG, line);
-    ctx->emit_(OP_LOAD_ATTR, name.index, line);
-}
-
-bool AttribExpr::emit_store_inplace(CodeEmitContext* ctx) {
-    // [a, val] -> [val, a]
-    ctx->emit_(OP_ROT_TWO, BC_NOARG, line);
-    ctx->emit_(OP_STORE_ATTR, name.index, line);
-    return true;
-}
-
-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);
-        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, (uint16_t)args.size(), line);
-
-        if(!kwargs.empty()) {
-            for(auto& item: kwargs) {
-                if(item.second->is_starred()) {
-                    assert(item.second->star_level() == 2);
-                    item.second->emit_(ctx);
-                } else {
-                    // k=v
-                    int index = ctx->add_const_string(item.first.sv());
-                    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 protocol
-        for(auto& item: args)
-            item->emit_(ctx);
-        for(auto& item: kwargs) {
-            i64 _val = StrName(item.first.sv()).index;
-            ctx->emit_int(_val, line);
-            item.second->emit_(ctx);
-        }
-        int KWARGC = kwargs.size();
-        int ARGC = args.size();
-        ctx->emit_(OP_CALL, (KWARGC << 8) | ARGC, line);
-    }
-}
-
-bool BinaryExpr::is_compare() const {
-    switch(op) {
-        case TK_LT:
-        case TK_LE:
-        case TK_EQ:
-        case TK_NE:
-        case TK_GT:
-        case TK_GE: return true;
-        default: return false;
-    }
-}
-
-void BinaryExpr::_emit_compare(CodeEmitContext* ctx, small_vector_2<int, 8>& jmps) {
-    if(lhs->is_compare()) {
-        static_cast<BinaryExpr*>(lhs)->_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_LT: ctx->emit_(OP_COMPARE_LT, BC_NOARG, line); break;
-        case TK_LE: ctx->emit_(OP_COMPARE_LE, BC_NOARG, line); break;
-        case TK_EQ: ctx->emit_(OP_COMPARE_EQ, BC_NOARG, line); break;
-        case TK_NE: ctx->emit_(OP_COMPARE_NE, BC_NOARG, line); break;
-        case TK_GT: ctx->emit_(OP_COMPARE_GT, BC_NOARG, line); break;
-        case TK_GE: ctx->emit_(OP_COMPARE_GE, BC_NOARG, line); break;
-        default: PK_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) {
-    small_vector_2<int, 8> jmps;
-    if(is_compare() && lhs->is_compare()) {
-        // (a < b) < c
-        static_cast<BinaryExpr*>(lhs)->_emit_compare(ctx, jmps);
-        // [b, RES]
-    } else {
-        // (1 + 2) < c
-        if(inplace) {
-            lhs->emit_inplace(ctx);
-        } else {
-            lhs->emit_(ctx);
-        }
-    }
-
-    rhs->emit_(ctx);
-    switch(op) {
-        case TK_ADD: ctx->emit_(OP_BINARY_ADD, BC_NOARG, line); break;
-        case TK_SUB: ctx->emit_(OP_BINARY_SUB, BC_NOARG, line); break;
-        case TK_MUL: ctx->emit_(OP_BINARY_MUL, BC_NOARG, line); break;
-        case TK_DIV: ctx->emit_(OP_BINARY_TRUEDIV, BC_NOARG, line); break;
-        case TK_FLOORDIV: ctx->emit_(OP_BINARY_FLOORDIV, BC_NOARG, line); break;
-        case TK_MOD: ctx->emit_(OP_BINARY_MOD, BC_NOARG, line); break;
-        case TK_POW: ctx->emit_(OP_BINARY_POW, BC_NOARG, line); break;
-
-        case TK_LT: ctx->emit_(OP_COMPARE_LT, BC_NOARG, line); break;
-        case TK_LE: ctx->emit_(OP_COMPARE_LE, BC_NOARG, line); break;
-        case TK_EQ: ctx->emit_(OP_COMPARE_EQ, BC_NOARG, line); break;
-        case TK_NE: ctx->emit_(OP_COMPARE_NE, BC_NOARG, line); break;
-        case TK_GT: ctx->emit_(OP_COMPARE_GT, BC_NOARG, line); break;
-        case TK_GE: 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, BC_NOARG, line); break;
-        case TK_IS_NOT: ctx->emit_(OP_IS_NOT_OP, BC_NOARG, line); break;
-
-        case TK_LSHIFT: ctx->emit_(OP_BITWISE_LSHIFT, BC_NOARG, line); break;
-        case TK_RSHIFT: ctx->emit_(OP_BITWISE_RSHIFT, BC_NOARG, line); break;
-        case TK_AND: ctx->emit_(OP_BITWISE_AND, BC_NOARG, line); break;
-        case TK_OR: ctx->emit_(OP_BITWISE_OR, BC_NOARG, line); break;
-        case TK_XOR: ctx->emit_(OP_BITWISE_XOR, BC_NOARG, line); break;
-
-        case TK_DECORATOR: ctx->emit_(OP_BINARY_MATMUL, BC_NOARG, line); break;
-        default: PK_FATAL_ERROR("unknown binary operator: %s\n", pk_TokenSymbols[op]);
-    }
-
-    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_FORWARD, BC_NOARG, true_expr->line);
-    ctx->patch_jump(patch);
-    false_expr->emit_(ctx);
-    ctx->patch_jump(patch_2);
-}
-
-}  // namespace pkpy