diff --git a/include/pocketpy/interpreter/vm.h b/include/pocketpy/interpreter/vm.h index ac683785..3a01e6ff 100644 --- a/include/pocketpy/interpreter/vm.h +++ b/include/pocketpy/interpreter/vm.h @@ -44,7 +44,7 @@ typedef struct pk_VM { void (*_stderr)(const char*, ...); py_TValue last_retval; - py_TValue last_exception; + py_TValue curr_exception; bool is_stopiteration; py_TValue reg[8]; // users' registers diff --git a/include/pocketpy/objects/error.h b/include/pocketpy/objects/error.h index cfbc44f2..801f1c19 100644 --- a/include/pocketpy/objects/error.h +++ b/include/pocketpy/objects/error.h @@ -2,38 +2,15 @@ #include "pocketpy/common/str.h" #include "pocketpy/common/strname.h" +#include "pocketpy/objects/codeobject.h" #include "pocketpy/objects/sourcedata.h" #include "pocketpy/objects/object.h" +#include "pocketpy/pocketpy.h" #ifdef __cplusplus extern "C" { #endif -// typedef struct pkpy_ExceptionFrame { -// pk_SourceData_ src; -// int lineno; -// const char* cursor; -// c11_string* name; -// } pkpy_ExceptionFrame; - -// typedef struct pkpy_Exception { -// py_Name type; -// c11_string* msg; -// bool is_re; - -// int _ip_on_error; -// void* _code_on_error; - -// PyObject* self; // weak reference - -// c11_vector/*T=pkpy_ExceptionFrame*/ stacktrace; -// } pkpy_Exception; - -// void pkpy_Exception__ctor(pkpy_Exception* self, py_Name type); -// void pkpy_Exception__dtor(pkpy_Exception* self); -// void pkpy_Exception__stpush(pkpy_Exception* self, pk_SourceData_ src, int lineno, const char* cursor, const char* name); -// py_Str pkpy_Exception__summary(pkpy_Exception* self); - struct Error{ const char* type; pk_SourceData_ src; @@ -43,6 +20,9 @@ struct Error{ int64_t userdata; }; +void py_BaseException__record(py_Ref, const Bytecode* ip, const CodeObject* code); +void py_BaseException__stpush(py_Ref, pk_SourceData_ src, int lineno, const char* func_name); + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/include/pocketpy/pocketpy.h b/include/pocketpy/pocketpy.h index bd7e0f3a..764c7a9b 100644 --- a/include/pocketpy/pocketpy.h +++ b/include/pocketpy/pocketpy.h @@ -241,12 +241,15 @@ py_TmpRef py_getmodule(const char* name); bool py_import(const char* name); /************* Errors *************/ +/// Raise an exception by name and message. Always returns false. bool py_exception(const char* name, const char* fmt, ...); +/// Raise an expection object. Always returns false. +bool py_raise(py_Ref); /// Print the last error to the console. void py_printexc(); /// Format the last error to a string. -void py_formatexc(char* out); -/// Check if an error is set. +char* py_formatexc(); +/// Check if an exception is raised. bool py_checkexc(); #define NameError(n) py_exception("NameError", "name '%n' is not defined", (n)) diff --git a/include/pocketpy/xmacros/opcodes.h b/include/pocketpy/xmacros/opcodes.h index b41cf867..d353ecc9 100644 --- a/include/pocketpy/xmacros/opcodes.h +++ b/include/pocketpy/xmacros/opcodes.h @@ -105,6 +105,7 @@ OPCODE(EXCEPTION_MATCH) OPCODE(RAISE) OPCODE(RAISE_ASSERT) OPCODE(RE_RAISE) +OPCODE(PUSH_EXCEPTION) OPCODE(POP_EXCEPTION) /**************************/ OPCODE(FSTRING_EVAL) diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index 2a6873c2..c033e8cc 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -8,6 +8,7 @@ #include "pocketpy/common/config.h" #include "pocketpy/common/memorypool.h" #include +#include /* expr.h */ typedef struct Expr Expr; @@ -2492,7 +2493,53 @@ __EAT_DOTS_END: } static Error* compile_try_except(Compiler* self) { - assert(false); + Error* err; + Ctx__enter_block(ctx(), CodeBlockType_TRY_EXCEPT); + Ctx__emit_(ctx(), OP_TRY_ENTER, BC_NOARG, prev()->line); + check(compile_block_body(self, compile_stmt)); + + int patches[8]; + int patches_length = 0; + + patches[patches_length++] = Ctx__emit_(ctx(), OP_JUMP_FORWARD, BC_NOARG, BC_KEEPLINE); + Ctx__exit_block(ctx()); + + if(curr()->type == TK_FINALLY) { return SyntaxError("finally clause is not supported yet"); } + + do { + if(patches_length == 8) { return SyntaxError("maximum number of except clauses reached"); } + py_Name as_name = 0; + consume(TK_EXCEPT); + if(is_expression(self, false)) { + check(EXPR(self)); // push assumed type on to the stack + Ctx__s_emit_top(ctx()); + Ctx__emit_(ctx(), OP_EXCEPTION_MATCH, BC_NOARG, prev()->line); + if(match(TK_AS)) { + consume(TK_ID); + as_name = py_namev(Token__sv(prev())); + } + } else { + Ctx__emit_(ctx(), OP_LOAD_TRUE, BC_NOARG, BC_KEEPLINE); + } + int patch = Ctx__emit_(ctx(), OP_POP_JUMP_IF_FALSE, BC_NOARG, BC_KEEPLINE); + // on match + if(as_name) { + Ctx__emit_(ctx(), OP_PUSH_EXCEPTION, BC_NOARG, BC_KEEPLINE); + Ctx__emit_store_name(ctx(), name_scope(self), as_name, BC_KEEPLINE); + } + // pop the exception + Ctx__emit_(ctx(), OP_POP_EXCEPTION, BC_NOARG, BC_KEEPLINE); + check(compile_block_body(self, compile_stmt)); + patches[patches_length++] = Ctx__emit_(ctx(), OP_JUMP_FORWARD, BC_NOARG, BC_KEEPLINE); + Ctx__patch_jump(ctx(), patch); + } while(curr()->type == TK_EXCEPT); + + // no match, re-raise + Ctx__emit_(ctx(), OP_RE_RAISE, BC_NOARG, BC_KEEPLINE); + + // no exception or no match, jump to the end + for(int i = 0; i < patches_length; i++) + Ctx__patch_jump(ctx(), patches[i]); return NULL; } diff --git a/src/interpreter/ceval.c b/src/interpreter/ceval.c index 747879ac..360894fa 100644 --- a/src/interpreter/ceval.c +++ b/src/interpreter/ceval.c @@ -1,10 +1,12 @@ #include "pocketpy/common/config.h" +#include "pocketpy/common/utils.h" +#include "pocketpy/interpreter/frame.h" #include "pocketpy/interpreter/vm.h" #include "pocketpy/common/memorypool.h" #include "pocketpy/common/sstream.h" #include "pocketpy/objects/codeobject.h" #include "pocketpy/pocketpy.h" -#include +#include "pocketpy/objects/error.h" static bool stack_unpack_sequence(pk_VM* self, uint16_t arg); static bool format_object(py_Ref obj, c11_sv spec); @@ -850,6 +852,30 @@ pk_FrameResult pk_VM__run_top_frame(pk_VM* self) { DISPATCH(); } /////////// + case OP_TRY_ENTER: { + Frame__set_unwind_target(frame, SP()); + DISPATCH(); + } + case OP_EXCEPTION_MATCH: { + if(!py_checktype(TOP(), tp_type)) goto __ERROR; + bool ok = py_isinstance(TOP(), py_totype(&self->curr_exception)); + POP(); + py_newbool(TOP(), ok); + DISPATCH(); + } + case OP_RAISE: { + // [exception] + if(py_istype(TOP(), tp_type)) { + if(!py_tpcall(py_totype(TOP()), 0, NULL)) goto __ERROR; + py_assign(TOP(), py_retval()); + } + if(!py_isinstance(TOP(), tp_BaseException)) { + TypeError("exceptions must derive from BaseException"); + goto __ERROR; + } + py_raise(TOP()); + goto __ERROR; + } case OP_RAISE_ASSERT: { if(byte.arg) { if(!py_str(TOP())) goto __ERROR; @@ -860,6 +886,20 @@ pk_FrameResult pk_VM__run_top_frame(pk_VM* self) { } goto __ERROR; } + case OP_RE_RAISE: { + py_raise(&self->curr_exception); + goto __ERROR; + } + case OP_PUSH_EXCEPTION: { + assert(self->curr_exception.type); + PUSH(&self->curr_exception); + DISPATCH(); + } + case OP_POP_EXCEPTION: { + assert(self->curr_exception.type); + self->curr_exception = *py_NIL; + DISPATCH(); + } ////////////////// case OP_FSTRING_EVAL: { py_TValue* tmp = c11__at(py_TValue, &frame->co->consts, byte.arg); @@ -879,13 +919,28 @@ pk_FrameResult pk_VM__run_top_frame(pk_VM* self) { default: c11__unreachedable(); } - assert(false); // should never reach here + c11__unreachedable(); __ERROR: - // 1. Exception can be handled inside the current frame - // 2. Exception need to be propagated to the upper frame printf("error.op: %s, line: %d\n", pk_opname(byte.op), Frame__lineno(frame)); - return RES_ERROR; + py_BaseException__record(&self->curr_exception, frame->ip, frame->co); + // TODO: lineno bug on re-raise + int lineno = Frame__lineno(frame); + py_BaseException__stpush( + &self->curr_exception, + frame->co->src, + lineno, + frame->function ? frame->co->name->data : NULL + ); + + int target = Frame__prepare_jump_exception_handler(frame, &self->stack); + if(target >= 0) { + // 1. Exception can be handled inside the current frame + DISPATCH_JUMP_ABSOLUTE(target); + } else { + // 2. Exception need to be propagated to the upper frame + return RES_ERROR; + } } return RES_RETURN; diff --git a/src/interpreter/vm.c b/src/interpreter/vm.c index 74d2ee7d..e25c0033 100644 --- a/src/interpreter/vm.c +++ b/src/interpreter/vm.c @@ -68,7 +68,7 @@ void pk_VM__ctor(pk_VM* self) { self->_stderr = pk_default_stderr; self->last_retval = *py_NIL; - self->last_exception = *py_NIL; + self->curr_exception = *py_NIL; self->is_stopiteration = false; self->__curr_class = NULL; @@ -540,14 +540,14 @@ void pk_ManagedHeap__mark(pk_ManagedHeap* self) { } // mark vm's registers mark_value(&vm->last_retval); - mark_value(&vm->last_exception); + mark_value(&vm->curr_exception); for(int i = 0; i < c11__count_array(vm->reg); i++) { mark_value(&vm->reg[i]); } } void pk_print_stack(pk_VM* self, Frame* frame, Bytecode byte) { - return; + // return; py_TValue* sp = self->stack.sp; c11_sbuf buf; diff --git a/src/public/cast.c b/src/public/cast.c index 81bf6a6d..f8cbbc16 100644 --- a/src/public/cast.c +++ b/src/public/cast.c @@ -46,4 +46,17 @@ bool py_checktype(py_Ref self, py_Type type) { return TypeError("expected %t, got %t", type, self->type); } +bool py_isinstance(py_Ref obj, py_Type type){ + return py_issubclass(obj->type, type); +} + +bool py_issubclass(py_Type derived, py_Type base){ + pk_TypeInfo* types = pk_current_vm->types.data; + do { + if(derived == base) return true; + derived = types[derived].base; + } while(derived); + return false; +} + py_Type py_typeof(py_Ref self) { return self->type; } \ No newline at end of file diff --git a/src/public/error.c b/src/public/error.c index b96d4d27..c830a52f 100644 --- a/src/public/error.c +++ b/src/public/error.c @@ -1,3 +1,4 @@ +#include "pocketpy/objects/base.h" #include "pocketpy/pocketpy.h" #include "pocketpy/common/sstream.h" #include "pocketpy/interpreter/vm.h" @@ -6,29 +7,31 @@ bool py_checkexc() { pk_VM* vm = pk_current_vm; - return !py_isnil(&vm->last_exception); + return !py_isnil(&vm->curr_exception); } void py_printexc() { pk_VM* vm = pk_current_vm; - if(py_isnil(&vm->last_exception)) { + if(py_isnil(&vm->curr_exception)) { vm->_stdout("NoneType: None\n"); } else { - const char* name = py_tpname(vm->last_exception.type); - bool ok = py_str(&vm->last_exception); + const char* name = py_tpname(vm->curr_exception.type); + bool ok = py_str(&vm->curr_exception); if(!ok) abort(); const char* message = py_tostr(py_retval()); vm->_stdout("%s: %s\n", name, message); } } -void py_formatexc(char* out) {} +char* py_formatexc() { + pk_VM* vm = pk_current_vm; + if(py_isnil(&vm->curr_exception)) { + return NULL; + } + assert(false); +} bool py_exception(const char* name, const char* fmt, ...) { - pk_VM* vm = pk_current_vm; - // an error is already set - assert(py_isnil(&vm->last_exception)); - c11_sbuf buf; c11_sbuf__ctor(&buf); va_list args; @@ -41,11 +44,16 @@ bool py_exception(const char* name, const char* fmt, ...) { py_newstrn(message, res->data, res->size); c11_string__delete(res); bool ok = py_tpcall(tp_Exception, 1, message); + if(!ok) abort(); py_pop(); - if(!ok) abort(); - vm->last_exception = *py_retval(); + return py_raise(py_retval()); +} +bool py_raise(py_Ref exc) { + assert(py_isinstance(exc, tp_BaseException)); + pk_VM* vm = pk_current_vm; + vm->curr_exception = *exc; return false; } diff --git a/src/public/py_exception.c b/src/public/py_exception.c index 35ae75e3..56aa1111 100644 --- a/src/public/py_exception.c +++ b/src/public/py_exception.c @@ -1,3 +1,5 @@ +#include "pocketpy/objects/codeobject.h" +#include "pocketpy/objects/error.h" #include "pocketpy/pocketpy.h" #include "pocketpy/common/utils.h" @@ -12,20 +14,41 @@ typedef struct BaseExceptionFrame { } BaseExceptionFrame; typedef struct BaseException { - int ip_backup; - CodeObject* code_backup; + const Bytecode* ip_backup; + const CodeObject* code_backup; c11_vector /*T=BaseExceptionFrame*/ stacktrace; } BaseException; +void py_BaseException__record(py_Ref self, const Bytecode* ip, const CodeObject* code) { + BaseException* ud = py_touserdata(self); + ud->ip_backup = ip; + ud->code_backup = code; +} + +void py_BaseException__stpush(py_Ref self, pk_SourceData_ src, int lineno, const char *func_name){ + BaseException* ud = py_touserdata(self); + if(ud->stacktrace.count >= 7) return; + BaseExceptionFrame* frame = c11_vector__emplace(&ud->stacktrace); + PK_INCREF(src); + frame->src = src; + frame->lineno = lineno; + frame->name = func_name ? c11_string__new(func_name) : NULL; +} + static void BaseException__dtor(void* ud) { BaseException* self = (BaseException*)ud; + c11__foreach(BaseExceptionFrame, &self->stacktrace, it) { + PK_DECREF(it->src); + if(it->name) c11_string__delete(it->name); + } c11_vector__dtor(&self->stacktrace); } static bool _py_BaseException__new__(int argc, py_Ref argv) { py_Type cls = py_totype(argv); BaseException* ud = py_newobject(py_retval(), cls, 1, sizeof(BaseException)); - ud->ip_backup = -1; + c11_vector__ctor(&ud->stacktrace, sizeof(BaseExceptionFrame)); + ud->ip_backup = NULL; ud->code_backup = NULL; return true; } diff --git a/tests/41_exception.py b/tests/28_exception.py similarity index 100% rename from tests/41_exception.py rename to tests/28_exception.py diff --git a/tests/28_iter.py b/tests/29_iter.py similarity index 100% rename from tests/28_iter.py rename to tests/29_iter.py