diff --git a/include/pocketpy/pocketpy.h b/include/pocketpy/pocketpy.h index d84d34d3..cd9949be 100644 --- a/include/pocketpy/pocketpy.h +++ b/include/pocketpy/pocketpy.h @@ -116,11 +116,9 @@ bool py_issubclass(py_Type derived, py_Type base); /************* References *************/ #define py_offset(p, i) (py_Ref)((char*)p + ((i) << 4)) - -#define TypeError(...) false #define py_arg(i) py_offset(argv, i) #define py_checkargc(n) \ - if(argc != n) return TypeError() + if(argc != n) return TypeError("expected %d arguments, got %d", n, argc) py_GlobalRef py_tpmagic(py_Type type, py_Name name); #define py_bindmagic(type, __magic__, f) py_newnativefunc(py_tpmagic((type), __magic__), (f)) @@ -157,17 +155,14 @@ py_TmpRef py_getupvalue(py_StackRef argv); void py_setupvalue(py_StackRef argv, const py_Ref val); /// Gets the attribute of the object. -bool py_getattr(const py_Ref self, py_Name name, py_Ref out); -/// Gets the unbound method of the object. -bool py_getunboundmethod(const py_Ref self, - py_Name name, - bool fallback, - py_Ref out, - py_Ref out_self); +/// 1: success, 0: not found, -1: error +int py_getattr(const py_Ref self, py_Name name, py_Ref out); /// Sets the attribute of the object. bool py_setattr(py_Ref self, py_Name name, const py_Ref val); /// Deletes the attribute of the object. bool py_delattr(py_Ref self, py_Name name); +/// Gets the unbound method of the object. +bool py_getunboundmethod(py_Ref self, py_Name name, py_Ref out, py_Ref out_self); bool py_getitem(const py_Ref self, const py_Ref key, py_Ref out); bool py_setitem(py_Ref self, const py_Ref key, const py_Ref val); @@ -226,11 +221,22 @@ py_TmpRef py_getmodule(const char* name); bool py_import(const char* name); /************* Errors *************/ +bool py_exception(const char* name, const char* fmt, ...); /// Print the last error to the console. void py_printexc(); /// Format the last error to a string. void py_formatexc(char* out); +#define KeyError(q) py_exception("KeyError", "'%q'", (q)) +#define NameError(n) py_exception("NameError", "name '%n' is not defined", (n)) +#define TypeError(...) py_exception("TypeError", __VA_ARGS__) +#define ValueError(...) py_exception("ValueError", __VA_ARGS__) +#define IndexError(...) py_exception("IndexError", __VA_ARGS__) +#define AttributeError(self, n) \ + py_exception("AttributeError", "'%t' object has no attribute '%n'", (self)->type, (n)) +#define UnboundLocalError(n) \ + py_exception("UnboundLocalError", "local variable '%n' referenced before assignment", (n)) + /************* Operators *************/ /// Equivalent to `bool(val)`. /// Returns 1 if `val` is truthy, otherwise 0. @@ -301,6 +307,10 @@ pk_TypeInfo* pk_tpinfo(const py_Ref self); /// Return the reference or NULL if not found. py_GlobalRef py_tpfindmagic(py_Type, py_Name name); +/// Search the name from the given type to the base type. +/// Return the reference or NULL if not found. +py_GlobalRef py_tpfindname(py_Type, py_Name name); + /// Get the type object of the given type. py_GlobalRef py_tpobject(py_Type type); @@ -333,28 +343,28 @@ enum py_MagicNames { enum py_PredefinedTypes { tp_object = 1, - tp_type, + tp_type, // py_Type tp_int, tp_float, tp_bool, tp_str, - tp_list, - tp_tuple, - tp_slice, + tp_list, // c11_vector + tp_tuple, // N slots + tp_slice, // 3 slots (start, stop, step) tp_range, tp_module, tp_function, tp_nativefunc, tp_bound_method, - tp_super, + tp_super, // 1 slot + py_Type tp_exception, tp_bytes, tp_mappingproxy, tp_dict, - tp_property, + tp_property, // 2 slots (getter + setter) tp_star_wrapper, - tp_staticmethod, - tp_classmethod, + tp_staticmethod, // 1 slot + tp_classmethod, // 1 slot tp_none_type, tp_not_implemented_type, tp_ellipsis, diff --git a/src/common/sstream.c b/src/common/sstream.c index 25cdc65a..82d5eb7b 100644 --- a/src/common/sstream.c +++ b/src/common/sstream.c @@ -142,7 +142,7 @@ c11_string* c11_sbuf__submit(c11_sbuf* self) { } void pk_vsprintf(c11_sbuf* ss, const char* fmt, va_list args) { - while(fmt) { + while(*fmt) { char c = *fmt; if(c != '%') { c11_sbuf__write_char(ss, c); diff --git a/src/interpreter/ceval.c b/src/interpreter/ceval.c index 41635cc7..93038a95 100644 --- a/src/interpreter/ceval.c +++ b/src/interpreter/ceval.c @@ -5,12 +5,6 @@ #include "pocketpy/pocketpy.h" #include -int UnboundLocalError(py_Name name) { return -1; } - -int NameError(py_Name name) { return -1; } - -#define AttributeError(obj, name) false -#define BinaryOptError(op) false static bool stack_binaryop(pk_VM* self, py_Name op, py_Name rop); @@ -255,9 +249,21 @@ pk_FrameResult pk_VM__run_top_frame(pk_VM* self) { goto __ERROR; } case OP_LOAD_METHOD: { - // `py_getunboundmethod` never fails on `fallback=true` - py_getunboundmethod(TOP(), byte.arg, true, TOP(), SP()); - SP()++; + // [self] + bool ok = py_getunboundmethod(TOP(), byte.arg, TOP(), SP()); + if(ok){ + // [unbound, self] + SP()++; + }else{ + // fallback to getattr + int res = py_getattr(TOP(), byte.arg, TOP()); + if(res != 1){ + if(res == 0){ + AttributeError(TOP(), byte.arg); + } + goto __ERROR; + } + } DISPATCH(); } case OP_LOAD_SUBSCR: { @@ -276,7 +282,7 @@ pk_FrameResult pk_VM__run_top_frame(pk_VM* self) { } DISPATCH(); } - TypeError(); + TypeError("'%t' object is not subscriptable", SECOND()->type); goto __ERROR; } case OP_STORE_FAST: frame->locals[byte.arg] = POPX(); DISPATCH(); @@ -330,13 +336,14 @@ pk_FrameResult pk_VM__run_top_frame(pk_VM* self) { } DISPATCH(); } - TypeError(); + TypeError("'%t' object does not support item assignment", SECOND()->type); goto __ERROR; } case OP_DELETE_FAST: { py_Ref tmp = &frame->locals[byte.arg]; if(py_isnull(tmp)) { - UnboundLocalError(c11__getitem(uint16_t, &frame->co->varnames, byte.arg)); + py_Name name = c11__getitem(py_Name, &frame->co->varnames, byte.arg); + UnboundLocalError(name); goto __ERROR; } py_newnull(tmp); @@ -400,7 +407,7 @@ pk_FrameResult pk_VM__run_top_frame(pk_VM* self) { } DISPATCH(); } - TypeError(); + TypeError("'%t' object does not support item deletion", SECOND()->type); goto __ERROR; } /*****************************************/ @@ -536,7 +543,8 @@ pk_FrameResult pk_VM__run_top_frame(pk_VM* self) { if(byte.arg) py_newbool(TOP(), !res); DISPATCH(); } - TypeError(); + // TODO: fallback to __iter__? + TypeError("argument of type '%t' is not iterable", SECOND()->type); goto __ERROR; } /*****************************************/ @@ -718,7 +726,7 @@ static bool stack_binaryop(pk_VM* self, py_Name op, py_Name rop) { } } // eq/ne op never fails due to object.__eq__ - return BinaryOptError(byte.arg); + return py_exception("TypeError", "unsupported operand type(s) for '%n'", op); } bool py_binaryop(const py_Ref lhs, const py_Ref rhs, py_Name op, py_Name rop) { diff --git a/src/interpreter/py_number.c b/src/interpreter/py_number.c index 5c0e6526..dcd625d3 100644 --- a/src/interpreter/py_number.c +++ b/src/interpreter/py_number.c @@ -44,8 +44,6 @@ DEF_NUM_BINARY_OP(__ge__, >=, py_newbool, py_newbool) #undef DEF_NUM_BINARY_OP -static bool ValueError(const char* fmt, ...) { return false; } - static bool _py_int__neg__(int argc, py_Ref argv) { py_checkargc(1); int64_t val = py_toint(&argv[0]); diff --git a/src/interpreter/vm.c b/src/interpreter/vm.c index d145f6ca..ee0e0212 100644 --- a/src/interpreter/vm.c +++ b/src/interpreter/vm.c @@ -364,7 +364,7 @@ pk_FrameResult pk_VM__vectorcall(pk_VM* self, uint16_t ARGC, uint16_t KWARGC, bo } // handle `__call__` overload - if(py_getunboundmethod(p0, __call__, false, p0, p0 + 1)) { + if(py_getunboundmethod(p0, __call__, p0, p0 + 1)) { // [__call__, self, args..., kwargs...] pk_FrameResult res = pk_VM__vectorcall(self, ARGC, KWARGC, false); if(res == RES_ERROR) return RES_ERROR; @@ -400,4 +400,4 @@ void pk_ManagedHeap__mark(pk_ManagedHeap* self) { // vm->obj_gc_mark(vm->__c.error); // vm->__stack_gc_mark(vm->s_data.begin(), vm->s_data.end()); // if(self->_gc_marker_ex) self->_gc_marker_ex((pkpy_VM*)vm); -} \ No newline at end of file +} diff --git a/src/public/error.c b/src/public/error.c index f7b0aeeb..494d4e0e 100644 --- a/src/public/error.c +++ b/src/public/error.c @@ -1,6 +1,9 @@ #include "pocketpy/pocketpy.h" +#include "pocketpy/common/sstream.h" #include "pocketpy/interpreter/vm.h" +#include + void py_printexc(){ pk_VM* vm = pk_current_vm; if(vm->has_error){ @@ -13,4 +16,23 @@ void py_printexc(){ void py_formatexc(char *out){ +} + +bool py_exception(const char* name, const char* fmt, ...){ + pk_VM* vm = pk_current_vm; + assert(!vm->has_error); // an error is already set + vm->has_error = true; + + c11_sbuf buf; + c11_sbuf__ctor(&buf); + va_list args; + va_start(args, fmt); + pk_vsprintf(&buf, fmt, args); + va_end(args); + + c11_string* res = c11_sbuf__submit(&buf); + // vm->last_retval = py_newexception(name, res->data); + vm->_stderr("%s: %s\n", name, res->data); + c11_string__delete(res); + return false; } \ No newline at end of file diff --git a/src/public/py_ops.c b/src/public/py_ops.c index 757de0f7..9e6c298d 100644 --- a/src/public/py_ops.c +++ b/src/public/py_ops.c @@ -21,11 +21,11 @@ int py_bool(const py_Ref val) { return 1; } bool py_hash(const py_Ref val, int64_t* out) { return 0; } -bool py_getattr(const py_Ref self, py_Name name, py_Ref out) { return true; } +int py_getattr(const py_Ref self, py_Name name, py_Ref out) { return -1; } -bool py_setattr(py_Ref self, py_Name name, const py_Ref val) { return -1; } +bool py_setattr(py_Ref self, py_Name name, const py_Ref val) { return false; } -bool py_delattr(py_Ref self, py_Name name) { return -1; } +bool py_delattr(py_Ref self, py_Name name) { return false; } bool py_getitem(const py_Ref self, const py_Ref key, py_Ref out) { return -1; } diff --git a/src/public/vm.c b/src/public/vm.c index f72e091c..cb0597c7 100644 --- a/src/public/vm.c +++ b/src/public/vm.c @@ -26,7 +26,7 @@ void py_finalize() { pk_MemoryPools__finalize(); } -const char* pk_opname(Opcode op){ +const char* pk_opname(Opcode op) { const static char* OP_NAMES[] = { #define OPCODE(name) #name, #include "pocketpy/xmacros/opcodes.h" @@ -195,11 +195,34 @@ bool py_vectorcall(uint16_t argc, uint16_t kwargc) { py_Ref py_retval() { return &pk_current_vm->last_retval; } -bool py_getunboundmethod(const py_Ref self, - py_Name name, - bool fallback, - py_Ref out, - py_Ref out_self) { +bool py_getunboundmethod(py_Ref self, py_Name name, py_Ref out, py_Ref out_self) { + // NOTE: `out` and `out_self` may overlap with `self` + py_Type type; + // handle super() proxy + if(py_istype(self, tp_super)) { + self = py_getslot(self, 0); + type = *(py_Type*)py_touserdata(self); + } else { + type = self->type; + } + + py_Ref cls_var = py_tpfindname(type, name); + if(cls_var != NULL) { + switch(cls_var->type) { + case tp_function: *out_self = *self; break; + case tp_nativefunc: *out_self = *self; break; + case tp_staticmethod: + py_newnull(self); + *out = *py_getslot(cls_var, 0); + break; + case tp_classmethod: + *out_self = c11__getitem(pk_TypeInfo, &pk_current_vm->types, type).self; + *out = *py_getslot(cls_var, 0); + break; + } + return true; + } + // TODO: __getattr__ fallback return false; } @@ -209,7 +232,6 @@ pk_TypeInfo* pk_tpinfo(const py_Ref self) { } py_Ref py_tpfindmagic(py_Type t, py_Name name) { - assert(t); assert(py_ismagicname(name)); pk_TypeInfo* types = (pk_TypeInfo*)pk_current_vm->types.data; do { @@ -220,6 +242,16 @@ py_Ref py_tpfindmagic(py_Type t, py_Name name) { return NULL; } +py_Ref py_tpfindname(py_Type t, py_Name name) { + pk_TypeInfo* types = (pk_TypeInfo*)pk_current_vm->types.data; + do { + py_Ref res = py_getdict(&types[t].self, name); + if(res) return res; + t = types[t].base; + } while(t); + return NULL; +} + py_Ref py_tpmagic(py_Type type, py_Name name) { assert(py_ismagicname(name)); pk_VM* vm = pk_current_vm; @@ -241,7 +273,9 @@ bool py_callmagic(py_Name name, int argc, py_Ref argv) { assert(argc >= 1); assert(py_ismagicname(name)); py_Ref tmp = py_tpfindmagic(argv->type, name); - if(!tmp) return TypeError(name); + if(!tmp){ + return AttributeError(argv, name); + } if(tmp->type == tp_nativefunc) return tmp->_cfunc(argc, argv); return py_call(tmp, argc, argv); }