diff --git a/include/pocketpy/frame.h b/include/pocketpy/frame.h index 0b322171..b810b1bf 100644 --- a/include/pocketpy/frame.h +++ b/include/pocketpy/frame.h @@ -117,6 +117,8 @@ struct Frame { int _exit_block(ValueStack*, int); void jump_abs_break(ValueStack*, int); + int curr_lineno() const { return co->lines[_ip].lineno; } + void _gc_mark() const { PK_OBJ_MARK(_module); co->_gc_mark(); diff --git a/include/pocketpy/lexer.h b/include/pocketpy/lexer.h index 28edbccc..ff268454 100644 --- a/include/pocketpy/lexer.h +++ b/include/pocketpy/lexer.h @@ -26,7 +26,7 @@ constexpr const char* kTokens[] = { /** KW_BEGIN **/ "class", "import", "as", "def", "lambda", "pass", "del", "from", "with", "yield", "None", "in", "is", "and", "or", "not", "True", "False", "global", "try", "except", "finally", - "while", "for", "if", "elif", "else", "break", "continue", "return", "assert", "raise", "breakpoint" + "while", "for", "if", "elif", "else", "break", "continue", "return", "assert", "raise" }; using TokenValue = std::variant; diff --git a/include/pocketpy/opcodes.h b/include/pocketpy/opcodes.h index 39fdf75f..269cf094 100644 --- a/include/pocketpy/opcodes.h +++ b/include/pocketpy/opcodes.h @@ -165,6 +165,4 @@ OPCODE(INC_FAST) OPCODE(DEC_FAST) OPCODE(INC_GLOBAL) OPCODE(DEC_GLOBAL) -/**************************/ -OPCODE(BREAKPOINT) #endif \ No newline at end of file diff --git a/include/pocketpy/vm.h b/include/pocketpy/vm.h index 6e8403b8..c3355d80 100644 --- a/include/pocketpy/vm.h +++ b/include/pocketpy/vm.h @@ -29,6 +29,16 @@ namespace pkpy{ typedef PyObject* (*BinaryFuncC)(VM*, PyObject*, PyObject*); +struct NextBreakpoint{ + Frame* frame; + int lineno; + bool should_step_into; + NextBreakpoint(): frame(nullptr), lineno(-1), should_step_into(false) {} + NextBreakpoint(Frame* frame, int lineno, bool should_step_info): frame(frame), lineno(lineno), should_step_into(should_step_info) {} + void _step(VM* vm, LinkedFrame* lf); + bool empty() const { return frame == nullptr; } +}; + struct PyTypeInfo{ PyObject* obj; // never be garbage collected Type base; @@ -131,6 +141,7 @@ public: void (*_ceval_on_step)(VM*, Frame*, Bytecode bc) = nullptr; LineProfiler* _profiler = nullptr; + NextBreakpoint _next_breakpoint; PrintFunc _stdout; PrintFunc _stderr; diff --git a/src/ceval.cpp b/src/ceval.cpp index 2aaa18fa..adb96b08 100644 --- a/src/ceval.cpp +++ b/src/ceval.cpp @@ -73,8 +73,9 @@ PyObject* VM::_run_top_frame(){ { #define CEVAL_STEP_CALLBACK() \ - if(_ceval_on_step) _ceval_on_step(this, frame, byte); \ - if(_profiler) _profiler->_step(callstack._tail); + if(_ceval_on_step) _ceval_on_step(this, frame, byte); \ + if(_profiler) _profiler->_step(callstack._tail); \ + if(!_next_breakpoint.empty()) { _next_breakpoint._step(this, callstack._tail); } #define DISPATCH_OP_CALL() { frame = top_frame(); goto __NEXT_FRAME; } __NEXT_FRAME: @@ -907,12 +908,9 @@ __NEXT_STEP:; if(p == nullptr) vm->NameError(_name); *p = VAR(CAST(i64, *p) - 1); } DISPATCH(); - TARGET(BREAKPOINT) { - vm->_breakpoint(); - } DISPATCH(); /*****************************************/ - static_assert(OP_BREAKPOINT == 137); - case 138: case 139: case 140: case 141: case 142: case 143: case 144: case 145: case 146: case 147: case 148: case 149: + static_assert(OP_DEC_GLOBAL == 136); + case 137: case 138: case 139: case 140: case 141: case 142: case 143: case 144: case 145: case 146: case 147: case 148: case 149: case 150: case 151: case 152: case 153: case 154: case 155: case 156: case 157: case 158: case 159: case 160: case 161: case 162: case 163: case 164: case 165: case 166: case 167: case 168: case 169: case 170: case 171: case 172: case 173: case 174: case 175: case 176: case 177: case 178: case 179: case 180: case 181: case 182: case 183: case 184: case 185: case 186: case 187: case 188: case 189: case 190: case 191: case 192: case 193: case 194: diff --git a/src/compiler.cpp b/src/compiler.cpp index de52121b..3b085e05 100644 --- a/src/compiler.cpp +++ b/src/compiler.cpp @@ -873,12 +873,6 @@ __EAT_DOTS_END: case TK("@"): compile_decorated(); break; case TK("try"): compile_try_except(); break; case TK("pass"): consume_end_stmt(); break; - case TK("breakpoint"): - consume(TK("(")); - consume(TK(")")); - consume_end_stmt(); - ctx()->emit_(OP_BREAKPOINT, BC_NOARG, kw_line); - break; /*************************************************/ case TK("++"):{ consume(TK("@id")); diff --git a/src/pocketpy.cpp b/src/pocketpy.cpp index 90647f27..f0bb589a 100644 --- a/src/pocketpy.cpp +++ b/src/pocketpy.cpp @@ -72,6 +72,15 @@ void init_builtins(VM* _vm) { #undef BIND_NUM_LOGICAL_OPT // builtin functions + _vm->bind_func<0>(_vm->builtins, "breakpoint", [](VM* vm, ArgsView args) { + vm->_next_breakpoint = NextBreakpoint( + vm->top_frame(), + vm->top_frame()->curr_lineno(), + false + ); + return vm->None; + }); + _vm->bind_func<-1>(_vm->builtins, "super", [](VM* vm, ArgsView args) { PyObject* class_arg = nullptr; PyObject* self_arg = nullptr; diff --git a/src/vm.cpp b/src/vm.cpp index 8db6cc67..0212d3d9 100644 --- a/src/vm.cpp +++ b/src/vm.cpp @@ -1353,33 +1353,132 @@ PyObject* NativeFunc::call(VM *vm, ArgsView args) const { return f(vm, args); } -void VM::_breakpoint(){ - SStream ss; - Frame* frame = vm->top_frame(); - int lineno = frame->co->lines[frame->_next_ip].lineno; - auto [_0, _1] = frame->co->src->_get_line(lineno); - ss << "> " << frame->co->src->filename << '(' << lineno << ')'; - if(frame->_callable){ - ss << PK_OBJ_GET(Function, frame->_callable).decl->code->name << "()"; - } - ss << '\n'; - - if(_0 && _1){ - ss << "-> " << std::string_view(_0, _1-_0) << '\n'; +void NextBreakpoint::_step(VM* vm, LinkedFrame* lf){ + Frame* frame = &lf->frame; + int lineno = frame->co->lines[frame->_ip].lineno; + if(should_step_into){ + if(frame != this->frame || lineno != this->lineno){ + vm->_breakpoint(); + } }else{ - ss << "-> \n"; - } - - vm->stdout_write(ss.str()); - std::string line; - while(true){ - vm->stdout_write("(Pdb) "); - if(!std::getline(std::cin, line)) break; - if(line == "h" || line == "help") continue; - if(line == "q" || line == "quit") std::exit(0); - if(line == "n" || line == "next") break; - if(line == "c" || line == "continue") break; + if(frame == this->frame){ + if(lineno != this->lineno) vm->_breakpoint(); + }else{ + if(&lf->f_back->frame != this->frame) vm->_breakpoint(); + } } } +void VM::_breakpoint(){ + _next_breakpoint = NextBreakpoint(); + + bool show_where = false; + bool show_headers = true; + + while(true){ + std::vector frames; + LinkedFrame* lf = callstack._tail; + while(lf != nullptr){ + frames.push_back(&lf->frame); + lf = lf->f_back; + if(frames.size() >= 4) break; + } + + if(show_headers){ + for(int i=frames.size()-1; i>=0; i--){ + if(!show_where && i!=0) continue; + + SStream ss; + Frame* frame = frames[i]; + int lineno = frame->curr_lineno(); + auto [_0, _1] = frame->co->src->_get_line(lineno); + ss << "File \"" << frame->co->src->filename << "\", line " << lineno; + if(frame->_callable){ + ss << ", in "; + ss << PK_OBJ_GET(Function, frame->_callable).decl->code->name; + } + ss << '\n'; + if(_0 && _1){ + ss << "-> " << std::string_view(_0, _1-_0) << '\n'; + }else{ + ss << "-> \n"; + } + stdout_write(ss.str()); + } + show_headers = false; + } + + vm->stdout_write("(Pdb) "); + + std::string line; + if(!std::getline(std::cin, line)){ + stdout_write("--KeyboardInterrupt--\n"); + continue; + } + + if(line == "h" || line == "help"){ + stdout_write("h, help: show this help message\n"); + stdout_write("q, quit: exit the debugger\n"); + stdout_write("n, next: execute next line\n"); + stdout_write("s, step: step into\n"); + stdout_write("w, where: show current stack frame\n"); + stdout_write("c, continue: continue execution\n"); + stdout_write("a, args: show local variables\n"); + stdout_write("p, print : evaluate expression\n"); + stdout_write("!: execute statement\n"); + continue; + } + if(line == "q" || line == "quit") std::exit(0); + if(line == "n" || line == "next"){ + vm->_next_breakpoint = NextBreakpoint( + frames[0], + frames[0]->curr_lineno(), + false + ); + break; + } + if(line == "s" || line == "step"){ + vm->_next_breakpoint = NextBreakpoint( + frames[0], + frames[0]->curr_lineno(), + true + ); + break; + } + if(line == "w" || line == "where"){ + show_where = !show_where; + show_headers = true; + continue; + } + if(line == "c" || line == "continue") break; + if(line == "a" || line == "args"){ + int i = 0; + for(PyObject* obj: frames[0]->_locals){ + if(obj == PY_NULL) continue; + StrName name = frames[0]->co->varnames[i++]; + stdout_write(_S(name.sv(), " = ", CAST(Str&, vm->py_repr(obj)), '\n')); + } + continue; + } + + int space = line.find_first_of(' '); + if(space != -1){ + std::string cmd = line.substr(0, space); + std::string arg = line.substr(space+1); + if(arg.empty()) continue; // ignore empty command + if(cmd == "p" || cmd == "print"){ + CodeObject_ code = compile(arg, "", EVAL_MODE, true); + PyObject* retval = vm->_exec(code.get(), frames[0]->_module, frames[0]->_callable, frames[0]->_locals); + stdout_write(CAST(Str&, vm->py_repr(retval))); + stdout_write("\n"); + }else if(cmd == "!"){ + CodeObject_ code = compile(arg, "", EXEC_MODE, true); + vm->_exec(code.get(), frames[0]->_module, frames[0]->_callable, frames[0]->_locals); + } + continue; + } + } + stdout_write("\n"); +} + } // namespace pkpy \ No newline at end of file diff --git a/tests/95_pdb.py b/tests/95_pdb.py index afb09463..b44f7536 100644 --- a/tests/95_pdb.py +++ b/tests/95_pdb.py @@ -5,9 +5,15 @@ print(a, b) def f(a, b): - breakpoint() b = a + b print(a, b) return b -f(1, 2) +def g(a, b): + breakpoint() + c = f(a, b) + return c + +g(1, 2) +f(3, 4) +